2012-01-05 4 views
13

Я хотел бы использовать std::forward_listстанд :: forward_list и станд :: forward_list :: push_back

Потому что:

Forward список представляет собой контейнер, который поддерживает быструю вставку и удаление элементов из любого места от контейнера

Но нет * std :: forward_list :: push_back * реализация.

Существует ли высокоэффективный способ добавления поддержки по той или иной причине?

+4

Обратите внимание, что двунаправленный 'std :: list' также поддерживает« быструю вставку и удаление элементов из любого места из контейнера »и имеет« push_back ». Стоимость - дополнительный указатель на запись. Является ли память настолько плотной, что вы не можете ее использовать? –

+0

Зачем вам это нужно? Вы хотите развить свой список в обе стороны? Нельзя ли использовать 'push_front()' так же легко? –

+0

Я хочу продолжить сортировку списка –

ответ

16

std::forward_list поддерживает быструю установку и вставку, , но не обход конца. Чтобы реализовать .push_back, вам сначала нужно будет дойти до конца списка, то есть O (N), а не быстро, что, вероятно, поэтому не реализовано.

 

Вы можете найти итератор к последнему элементу приращением .before_begin N раз

auto before_end = slist.before_begin(); 
for (auto& _ : slist) 
    ++ before_end; 

, а затем использовать .insert_after или .emplace_after вставить элемент:

slist.insert_after(before_end, 1234); 
+12

Конечно, в' forward_list' могут быть внесены поправки, сохранив дополнительный итератор, указывающий на его конец. Таким образом, 'push_back' можно было бы сделать за время O (1). –

+0

Итак, нет никакой причины для реализации 'push_back'? –

+0

@larsmans: Я считаю, что это превратится в 'std :: list'. – kennytm

6

Там есть нет push_back, потому что список не отслеживает заднюю часть списка , только фронт.

Вы можете написать обертку вокруг списка, который поддерживает итератор для последнего элемента, и реализует push_back с использованием либо insert_after или push_front в зависимости от того, список пуст. Это будет довольно сложно, если вы хотите поддерживать более сложные операции (например, sort и splice_after).

В качестве альтернативы, если вам не нужно push_back, чтобы быть быстрым, это легко сделать в линейном режиме.

Если память не очень плотная, лучшим решением является использование list. Он имеет те же рабочие характеристики, что и forward_list, и двунаправленный, поддерживающий push_back, а также push_front; стоимость - дополнительный указатель на элемент.

+0

+1, только моя идея. 'sort' не будет так тяжело, btw; просто соберите базовый 'forward_list' и найдите новый конец. Поскольку сортировка равна O (* n * lg * n *), она доминирует над окончанием поиска. –

+0

@larsmans: Это правда. Может быть сложнее «сращивать» в постоянное время (если вы сплайсируете обычный 'forward_list', который не отслеживает его конец). –

+0

Да, «сращивание» было бы болью, чтобы получить право. –

5

Точка std :: forward_list должна быть ультра-усеченной версией std :: list, поэтому она не хранит итератор до последнего элемента. Если вам нужно, вы должны поддерживать его самостоятельно, например, так:

forward_list<int> a; 

auto it = a.before_begin(); 

for(int i = 0; i < 10; ++i) 
    it = a.insert_after(it, i); 
26

Я рекомендую против std::forward_list так же, как я рекомендую против std::list практически во всех ситуациях. Лично я никогда не обнаружил ситуации в моем коде, где связанный список был лучшей структурой данных.

В C++ ваш сбор данных по умолчанию должен быть std::vector. Это дает вам push_back, если это то, что вам действительно нужно. Это технически не дает вам эффективного удаления и вставки из середины, если вы только посмотрите на абстрактные измерения сложности большой О из этой одной операции.В реальном мире, однако, std::vector по-прежнему выигрывает даже для вставки и удаления в середине.

В качестве примера, Bjarne Stroustrup создал тест из 100 000 элементов std::list против std::vector. Он будет искать каждый элемент и удалять его. Затем он найдет точку вставки и вставит в середину. Он мог бы использовать двоичный поиск на std::vector, но не сделал сравнение «более справедливым».

Результаты показывают сильную победу для std::vector, даже в этой ситуации, когда std::list должен быть сильным. Простое перемещение std::list занимает намного больше времени, так как далеко друг от друга в памяти находятся все объекты. std::list не относится к кэшу, что, возможно, является самым важным для современных процессоров.

The complete talk by Bjarne Stroustrup

Thorough explanation of the effects, with benchmarks at multiple sizes

Обратите внимание, что эта вторая ссылка здесь приведены некоторые ситуации, где вы, возможно, захотите использовать std::list, например, когда размер элементов велик. Тем не менее, я был в ситуации, когда у меня много элементов в определенном порядке и нужно было удалить некоторые.

Эти элементы были больше, чем любой встроенный тип, но не огромный, возможно, 20-30 байтов каждый на 32-разрядном компьютере). Количество элементов было достаточно большим, так что вся моя структура данных составляла несколько сотен MiB. Сбор данных представлял собой набор значений, которые теоретически могли бы быть действительными на основе известной в настоящее время информации. Алгоритм повторяется по всем элементам и удаленным элементам, которые больше не могут быть действительными на основе новой информации, причем каждый проход, вероятно, удаляет около 80% оставшихся элементов.

Моя первая реализация была простой подход std::vector, где я удалил недействительные элементы по мере прохождения. Это работало для небольших наборов тестовых данных, но когда я пытался делать настоящую вещь, было слишком медленно, чтобы быть полезным. Я переключился на std::list в качестве контейнера, но использовал тот же алгоритм, и я видел значительные улучшения производительности. Однако было слишком медленно, чтобы быть полезным. Победившее изменение состояло в том, чтобы вернуться к std::vector, но вместо удаления элементов, которые были плохими, я создал новый std::vector, и любые найденные мной элементы были помещены в этот std::vector, а затем в конце функции Я бы просто отказался от старого std::vector и использовал новый, и это дало мне примерно такую ​​же скорость по сравнению с std::list, поскольку std::list дал мне над моей оригинальной реализацией std::vector, и это было достаточно быстро, чтобы быть полезным.

+0

Или .. сохраняйте «мертвый» флаг для каждого элемента, который вы обновляете вместо удаления элементов, только уплотняете вектор, когда количество мертвых элементов достигает определенного предела. Не будет работать с примитивными типами в векторе, конечно, но очень интересным ответом, та. – gbjbaanb

+1

Ничего страшного, но я не понимаю, как это вообще отвечает на вопрос. Существует много ситуаций, когда список, связанный по отдельности, будет делать все; на самом деле я думаю, что любая реализация за кулисами использует по существу односвязный список фреймов для представления стека времени выполнения в одном потоке (кадры могут быть последовательно в памяти, но не равны по размеру, поэтому их нельзя адресовать напрямую, как в векторе). Обычно, если вам просто нужна простая структура стека или очереди, вы можете реализовать это легко, используя список (и дополнительный указатель на его конец в случае очереди). –

+2

@MarcvanLeeuwen Он решает проблему, а не отвечает на вопрос. Проблема в том, что они хотят создать высокопроизводительную структуру данных для вставки и удаления. 'std :: forward_list' редко является структурой данных для достижения целей в целом. Что касается вашего конкретного примера, 'std :: forward_list' там тоже не помогает (если я не ошибаюсь, что вполне возможно). Если кадры не равны по размеру, это подразумевает динамическое распределение. Связанный список указателей почти никогда не является лучшей структурой данных, чем вектор указателей. –

1

Unfortunatelly Я не могу добавить комментарий (низкая репутация), но я просто хотел упомянуть, что одним из преимуществ списка forward_list & является то, что операции вставки-удаления не делают недействительными итераторы. У меня было приложение, в котором коллекция элементов росла, итерации и обработки отдельных элементов. Отсутствие недействительности итератора позволило мне реализовать сегментное сканирование (начало списка как начало сегмента и начало последнего сегмента как конец).

Смежные вопросы