Когда следует использовать вектор / список?

17

Я могу понять, когда использовать списки, но не понимаю, когда лучше использовать векторы, чем использовать списки в видеоиграх: когда лучше иметь быстрый произвольный доступ?

(И я понимаю, почему быстрее добавлять / удалять в списках, потому что он просто удаляет / добавляет указатели, но все равно должен найти соответствующий элемент ...)

jokoon
источник
Повторно добавил векторный тег к этому - если список является допустимым тегом, то вектор тоже.
Kylotan,
Предположительно вектор был удален, потому что он используется для обозначения математического вектора, а не std :: vector.
1
как насчет nix их обоих и ставь container.
deft_code
@ Килотан: Это как сказал Джо. Этот вопрос определенно касался векторов, но он не относится к тегу vector.
doppelgreener
1
Так мы удалим какой-либо двусмысленный тег? Это звучит как неправильное решение для меня - лучше, если ваш поиск обнаружит слишком много информации, чем недостаточно. Пропустить нежелательные результаты проще, чем мозговой штурм, чтобы найти синонимы.
Килотан

Ответы:

29

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

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

Редактировать: просто, чтобы прояснить немного больше, конечно, само собой разумеется, что любой вопрос «который быстрее» должен быть протестирован на любой платформе с любыми наборами данных, которые соответствуют вашим конкретным потребностям. Если мне просто нужна коллекция элементов, я просто использую vector (или deque, что почти то же самое), если нет веской причины не делать этого.

тетрада
источник
1
Я думаю, что это сильно зависит от ваших потребностей: если вам никогда не требуется доступ к определенному элементу и вам просто нужно прочитать все из них, и вы часто добавляете и удаляете элементы, список является лучшим решением.
Фредерик Имбо
11
@ Фредерик: Это стандартная мудрость C ++, но это также почти всегда неправильно. Векторы, особенно когда имеешь дело с векторами указателей (которые вы почти всегда используете для игр), очень быстро удаляют вещи из середины - это линейное время, но это очень и очень небольшие накладные расходы на элемент. Также намного быстрее последовательно выполнять итерации по вектору.
1
Я не уверен, к какой именно структуре вы подходите - такая оптимизация требует конкретных примеров, чтобы сказать что-то однозначно. Например, моей первой реакцией на ваш вариант использования будет неупорядоченный набор, который позволяет одинаково быстро вставлять, удалять и искать; в моем опыте наборы отлично подходят для объектов в редакторах. Но поскольку редакторы предъявляют гораздо более жесткие требования к производительности в реальном времени - например, это хорошо, если на нажатие кнопки «Удалить» требуется 1/20-я секунда или 1/2-я секунда 10% времени - этот уровень оптимизации тоже редко к ним относится.
1
@ Frédérick Imbeault Modified - не большая проблема, это Add \ Remove, которая вызывает проблемы. От точки добавления \ удаленного до конца вектора копируется, чтобы вектор оставался смежным. Если порядок элементов не имеет значения, вы можете поменять удаленный элемент на последний элемент, затем вытолкните его для удаления и добавьте в конец для добавления.
каменный металл
4
Конечно, взятие адреса элемента в векторе и его сохранение не безопасно. Но, вообще говоря, вы вряд ли когда-либо делаете это, вместо этого предпочитая иметь вектор указателей элементов и копировать указатель (или что-то подобное).
Тетрад
8

Используйте список, когда аннулирование итератора, вызванное изменением середины вашей структуры данных, может вызвать проблему, или вам нужно держать свои элементы отсортированными, чтобы трюк swap и pop для быстрого удаления средней коллекции не работал, и у вас большой количество удалений середины коллекции.

Вы также можете рассмотреть возможность использования Deque. Он имеет характеристики производительности, схожие с векторными, но не требует векторной смежной памяти и немного более гибок.

stonemetal
источник
+1 за то, что вы единственный, кто упомянул deques - вы получаете непрерывную память и преимущества скорости поиска векторов, с быстрой вставкой / удалением на обоих концах.
5

Ваш выбор должен отражать ваши потребности. Все элементы векторов непрерывны в памяти, и списки имеют указатели на следующие / предыдущие элементы, поэтому у каждого из них есть свои преимущества / недостатки:

Списки:

  • Каждый элемент занимает 2 целых числа, чтобы указать на предыдущий и следующий элементы, поэтому чаще всего это 8 байт больше для каждого элемента в вашем списке
  • Вставка линейна по времени: O (n)
  • Удалить это постоянная операция: O (1)
  • Доступ к элементу x является линейным по времени: O (n)

Векторы:

  • Требуется меньше памяти (нет указателей на другие элементы, это простой математический алгоритм)
  • Удалить линейно по времени: O (n)
  • Доступ к элементу x постоянен: O (1) (это потому, что элементы постоянно находятся в памяти, поэтому это простая математическая операция vectorPtr + (x * bytesOfTheType))
  • Вставка может выполняться за линейное время, но чаще всего это постоянная операция: O (1) (это потому, что вектор в массиве, но всегда резервирует в 2 раза больше своей емкости, когда массив заполнен, поэтому копирование массива происходит не часто)

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

Проверьте этот пост на stackoverflow, он представляет действительно хороший график с базовыми вопросами о ваших потребностях, который приводит вас к конкретному контейнеру в зависимости от ваших ответов:

/programming/366432/extending-stdlist

Фредерик Имбо
источник
3
Этот график действительно должен начинаться с узла «Вы храните указатели и меньше тысячи? Нет -> вектор».
Да, может быть, вы и правы, я не учел тип хранимых он также должен быть проанализирован.
Фредерик Имбо
2

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

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

Мнение Tetrads может быть правдой, но это зависит от языка программирования, который используется. Я вижу, что вы пометили свой вопрос c++, но я попытался дать ответ, который не зависит от языка.

bummzack
источник
Ну, я мог бы также включить в него C, но в C таких контейнеров нет, но об этом стоит подумать: есть ли STL-подобная библиотека для C?
Jokoon
В прошлом я использовал glib ( library.gnome.org/devel/glib ), который реализует ряд стандартных структур данных для C. Я ненавидел его, потому что он обычно слишком многословен и отчаянно хочет быть C ++, но он зрелый и стабильный.
0

В консольных играх мы никогда не используем std :: list, потому что:

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

даже std :: vector теряет популярность на консолях, потому что:

  1. Вы часто заботитесь только о положении всех объектов. например, вы хотите столкнуть объекты друг с другом, и в этом случае вам все равно, какого они цвета. поэтому вы хотите, чтобы все позиции были смежными в памяти, и вы хотите, чтобы цвета были где-то далеко, чтобы не загрязнять кэш. но std :: vector требует, чтобы вы сохраняли все для каждого объекта в непрерывном фрагменте памяти (например, position, color.), поэтому, если вы выполняете работу, которая читает только позиции, вам нужно также прочитать все цвета в кеш, даже если вы не используете их. это расточительно.
bmcnett
источник
5
@bmcnett "[] .. но std :: vector требует, чтобы вы сохранили все для каждого объекта в непрерывном куске памяти" - это не вопрос контейнера, это вопрос вашей компоновки данных, вы можете иметь все позиции в непрерывном фрагменте памяти с помощью std :: vector:struct point{float x, y, z, w}; std::vector<point> positions;
Maik Semder
моя школа будет любить это :)
Jokoon
2
-1, потому что этот ответ ничего не добавляет к обсуждению списка и вектора уже здесь, и его утверждение о векторном загрязнении кэша неверно, как говорит Майк.
Вы неправильно поняли мое требование. я никогда не говорил, что среди всех контейнеров std :: vector особенно виновен в загрязнении кэша. все контейнеры и даже массивы примерно одинаково виновны в этом. std :: vector теряет популярность, потому что сами объекты C ++ теряют популярность. C ++ требует, чтобы данные каждого объекта были непрерывными в памяти. вы можете обойти это, избегая объектов C ++, например, используя std :: vector <position>, как упомянуто выше.
bmcnett
3
За исключением pointобъекта C ++ (как есть std::vector, так же просто, как float). Я знаю разницу, которую вы пытаетесь провести, но вы плохо объясняете это.