Как добиться удаления O (1) из списка std :: list

Вопрос в том, что является рекомендуемым способом использования std::list добиться O (1) стирания элементов списка?

Обычно, когда я выбираю двусвязный список, я хочу иметь возможность удалить элемент из списка за O (1) время, а затем переместить его в другой список за O (1) время. Если элемент имеет свой prev а также next указатели, нет никакой реальной уловки к выполнению работы. Если список представляет собой двусвязный циклический список, то удаление не обязательно требует знания списка, который содержит элемент.

Согласно Правила аннулирования итераторов, std::list итераторы очень долговечны. Таким образом, мне кажется, чтобы получить поведение, которое я желаю при использовании std::list на мой собственный предмет, чтобы спрятать итератор в моем классе, и список, содержащий.

class Item {
typedef std::shared_ptr<Item> Ptr;
struct Ref {
std::list<Ptr>::iterator iter_;
std::list<Ptr> *list_;
};
Ref ref_;
//...
};

Это имеет обратную сторону, что мне нужно будет создать свою собственную оформленную версию std::list что знает, чтобы обновить ref_ всякий раз, когда элемент добавляется в список. Я не могу придумать способ, который не требует встроенного итератора, так как его отсутствие означало бы, что стирание вначале вызовет операцию поиска O (n).

Каков рекомендуемый способ удаления O (1) с std::list? Или есть лучший подход к достижению цели?


В прошлом я достигал этого путем реализации собственной структуры данных списка, в которой элемент, помещенный в список, имеет свои собственные указатели next и prev. Управление этими указателями является естественным, так как они присущи самим операциям со списком (API для моей реализации списка настраивает указатели). Если я хочу вместо этого использовать STL, каков будет лучший способ сделать это? Я предлагаю соломенное предложение о встраивании итератора. Есть ли лучшие подходы?

Если требуется конкретный вариант использования, рассмотрите реализацию таймера. Когда таймер создан, он помещается в соответствующий список. Если он отменен, желательно эффективно его удалить. (Этот конкретный пример может быть решен с помощью маркировки вместо удаления, но это является допустимым способом осуществления отмены.) Дополнительные варианты использования доступны по запросу.


Еще одна альтернатива, которую я исследовал, заключалась в std::list с std::unordered_map создать специализированный список типов указателей. Это более тяжелый (из-за хеш-таблицы), но предоставляет контейнер, который довольно близок к стандартным контейнерам на уровне интерфейса, и дает мне O (1) стирание элементов списка. Единственная особенность, отсутствующая в предложении соломенного человека, — это указатель на список, который в настоящее время содержит элемент. Я поднял текущую реализацию в Просмотр Кода запросить комментарий.

6

Решение

std::list::erase гарантированно будет O (1).

Существует не так много других способов стереть элементы из стандартного списка. (std::list::remove и друзья не делают то же самое, поэтому они не считаются).

Если вы хотите удалить из стандартного списка, вам нужен итератор и сам список. Это то, что вы, кажется, уже имеете. Там не очень много свободы делать это по-другому. Я бы держал список списков отдельно от объектов, в отличие от того, что вы сделали, потому что зачем создавать объект, который может находиться только в одном списке за раз? Похоже на ненужное искусственное ограничение для меня. Но что бы ни приводило в действие ваш дизайн.

2

Другие решения

Может быть, вы могли бы перепроектировать свой интерфейс, чтобы раздавать итераторы вместо необработанных объектов? В случае вашего таймера пример:

class Timer {
// ...
};

typedef std::list<Timer>::iterator TimerRef;

class Timers {

public:

TimerRef createTimer(long time);
void cancelTimer(TimerRef ref);

private:

std::list<Timer> timers;
};

Конечно, вместо

timer.cancel();

пользователи класса теперь должны сказать

timers.cancelTimer(timerRef);

но в зависимости от вашего варианта использования, это не может быть проблемой.


Обновление: перемещение таймеров между списками:

class Timers {

public:

Timer removeTimer(TimerRef ref);
void addTimer(Timer const &timer);

// ...

};

Использование:

timers2.addTimer(timers1.removeTimer(timerRef));

По общему признанию это немного громоздко, но альтернативы — также.

2

Нет способа удалить O (1) из std :: list.

Вы можете рассмотреть возможность использования intrusive listгде узлы списка непосредственно встроены в структуры, как вы уже сделали.

ты можешь использовать повышение :: навязчивым или сверните свое, также проверьте этот

1

Вот «полное» решение с использованием встроенного iterator, Некоторые личные качества используются, чтобы помочь уменьшить беспорядок в классе:

template <typename T> class List;

template <typename T>
class ListTraits {
protected:
typedef std::list<std::shared_ptr<T>> Impl;
typedef typename Impl::iterator Iterator;
typedef typename Impl::const_iterator ConstIterator;
typedef typename Impl::reverse_iterator Rotareti;
typedef typename Impl::const_reverse_iterator ConstRotareti;
typedef std::map<const List<T> *, typename Impl::iterator> Ref;
};

Как показано, реализация списка будет использовать std::list, но базовый тип значения будет std::shared_ptr, То, что я хочу, это разрешить экземпляр T эффективно выводить свои собственные iterator, чтобы достичь O (1) стирание. Это делается с помощью Ref запомнить итератор элемента после его вставки в список.

template <typename T>
class List : public ListTraits<T> {
template <typename ITER> class IteratorT;
typedef ListTraits<T> Traits;
typename Traits::Impl impl_;
public:
typedef typename Traits::Impl::size_type size_type;
typedef typename Traits::Impl::value_type pointer;
typedef pointer value_type;
typedef IteratorT<typename Traits::Iterator> iterator;
typedef IteratorT<typename Traits::ConstIterator> const_iterator;
typedef IteratorT<typename Traits::Rotareti> reverse_iterator;
typedef IteratorT<typename Traits::ConstRotareti> const_reverse_iterator;
class Item;
~List () { while (!empty()) pop_front(); }
size_type size () const { return impl_.size(); }
bool empty () const { return impl_.empty(); }
iterator begin () { return impl_.begin(); }
iterator end () { return impl_.end(); }
const_iterator begin () const { return impl_.begin(); }
const_iterator end () const { return impl_.end(); }
reverse_iterator rbegin () { return impl_.rbegin(); }
reverse_iterator rend () { return impl_.rend(); }
const_reverse_iterator rbegin () const { return impl_.rbegin(); }
const_reverse_iterator rend () const { return impl_.rend(); }
pointer front () const { return !empty() ? impl_.front() : pointer(); }
pointer back () const { return !empty() ? impl_.back() : pointer(); }
void push_front (const pointer &e);
void pop_front ();
void push_back (const pointer &e);
void pop_back ();
void erase (const pointer &e);
bool contains (const pointer &e) const;
};

это List следует в основном за интерфейсом, похожим на очередь Но элемент может быть удален из любой позиции в списке. Простые функции в основном просто делегируют основному std::list, Но push_*() а также pop_*() методы также запоминают iterator,

template <typename T>
template <typename ITER>
class List<T>::IteratorT : public ITER {
friend class List<T>;
ITER iter_;
IteratorT (ITER i) : iter_(i) {}
public:
IteratorT () : iter_() {}
IteratorT & operator ++ () { ++iter_; return *this; }
IteratorT & operator -- () { --iter_; return *this; }
IteratorT operator ++ (int) { return iter_++; }
IteratorT operator -- (int) { return iter_--; }
bool operator == (const IteratorT &x) const { return iter_ == x.iter_; }
bool operator != (const IteratorT &x) const { return iter_ != x.iter_; }
T & operator * () const { return **iter_; }
pointer operator -> () const { return *iter_; }
};

Это реализация вспомогательного шаблона, используемого для определения типов итераторов для List, Что он делает по-другому, так это то, что * а также -> операторы определены таким образом, что итератор ведет себя так, как будто он T * а не std::shared_ptr<T> * (что обычно делает основной итератор).

template <typename T>
class List<T>::Item {
friend class List<T>;
mutable typename Traits::Ref ref_;
};

Тип T что происходит от List<T>::Item можно добавить в List<T>, Этот базовый класс содержит Ref экземпляр, используемый для запоминания итератора при добавлении элемента в список.

template <typename T>
inline void List<T>::push_front (const pointer &e) {
const Item &item = *e;
typename Traits::Ref::iterator i = item.ref_.find(this);
if (i == item.ref_.end()) {
item.ref_[this] = impl_.insert(impl_.begin(), e);
} else if (front() != e) {
impl_.erase(i->second);
i->second = impl_.insert(impl_.begin(), e);
}
}

template <typename T>
inline void List<T>::pop_front () {
if (!empty()) {
const Item &item = *front();
item.ref_.erase(this);
impl_.pop_front();
}
}

Этот код иллюстрирует, как выполняется памятка. При выполнении push_front(), элемент сначала проверяется, чтобы увидеть, содержится ли он уже. Если это не так, он вставляется, и полученный итератор добавляется в ref_ объект. В противном случае, если это еще не фронт, то элемент удаляется и снова вставляется спереди, а запомненный итератор обновляется. pop_front() удаляет запомненный итератор, а затем вызывает pop_front() на std::list,

template <typename T>
inline void List<T>::push_back (const pointer &e) {
const Item &item = *e;
typename Traits::Ref::iterator i = item.ref_.find(this);
if (i == item.ref_.end()) {
item.ref_[this] = impl_.insert(impl_.end(), e);
} else if (back() != e) {
impl_.erase(i->second);
i->second = impl_.insert(impl_.end(), e);
}
}

template <typename T>
inline void List<T>::pop_back () {
if (!empty()) {
const Item &item = *back();
item.ref_.erase(this);
impl_.pop_back();
}
}

push_back() а также pop_back() похожи на push_front() а также pop_front(),

template <typename T>
inline void List<T>::erase (const pointer &e) {
const Item &item = *e;
typename Traits::Ref::iterator i = item.ref_.find(this);
if (i != item.ref_.end()) {
item.ref_.erase(i);
impl_.erase(i->second);
}
}

erase() рутина извлекает запомненный итератор и использует его для выполнения стирания.

template <typename T>
inline bool List<T>::contains (const pointer &e) const {
const Item &item = *e;
typename Traits::Ref::iterator i = item.ref_.find(this);
return i != item.ref_.end();
}

Так как элемент во многих отношениях является собственным итератором, find() метод не должен быть необходим в этой версии List, Но вместо этого это contains() метод, который используется, чтобы увидеть, является ли элемент членом списка.

Теперь представленное решение использует std::map связать экземпляры списка с итераторами. Это поддерживает дух O (1), если число списков, в которые одновременно входит элемент, относительно мало.

Я попробую свои силы в boost::intrusive версия следующая.

1

Ужасная правда: хотя связанный список является мощной структурой, std::list не может полностью использовать свои возможности.

Вы не можете стереть объект с std::list используя только итераторы, потому что list должен освободить узел, и вы должны знать, где находится распределитель, который выделил память (подсказка: он находится в списке).

У навязчивых контейнеров есть много преимуществ по сравнению со стандартным связанным списком, например, самосознание ;-), возможность хранить полиморфные объекты по значению, и они делают возможным приемы со списком (например, наличие отдельных объектов в нескольких списках). Так как вы не используете std::list в любом случае, вы можете отказаться от использования std::list В общем и используйте сторонний контейнер, или сверните свой.

(Кроме того, ваше решение также навязчиво, так как ваш тип должен быть получен из List<T>::Item, который устанавливает определенные требования к типу, который std::list не делает)

0

Не может быть сделано В списке используются прямые и / или обратные указатели, указывающие на «смежные» элементы списка. Следовательно, вам нужно будет повторять каждый раз, когда вы что-то делаете.

Если вы хотите O (1) для какой-либо коллекции, заставьте ваш контент работать с массивом.

Тем не менее, для сканирования, хотя список из менее чем 100 элементов занимает незаметный период времени, если вы не делаете это тысячу раз в секунду.

Редактировать:

Если вы знаете, какой узел вы хотите удалить, это легко. Я не уверен, как именно работает список STD, но в теоретическом смысле узел должен быть в состоянии удалить себя, установив 1 или 2 указателя вперед и назад соседних узлов так, чтобы они указывали друг на друга.

-1
По вопросам рекламы [email protected]