вектор проти списку в STL


238

Я помітив в Ефективній STL це

вектор - тип послідовності, яку слід використовувати за замовчуванням.

Що це означає? Здається, що ігноруйте ефективністьvector може все, що завгодно.

Чи може хтось запропонувати мені сценарій, коли vectorце неможливо здійснити, але listйого слід використовувати?


6
Хоча це не те, про що ви просили, варто зазначити, що дефолт до вектору також означає, що ви можете легко взаємодіяти зі старими кодами, бібліотеками С або бібліотеками без шаблонів, оскільки вектор - це тонка обгортка навколо "традиційного" динамічного масиву вказівника і розмір.

18
Bjarne Strostrup фактично зробив тест, де він генерував випадкові числа, а потім додав їх до списку та вектора відповідно. Вставки були зроблені таким чином, щоб список / вектор був упорядкований у всі часи. Хоча це, як правило, "список домену", вектор перевершує список за великим запасом. Причина в тому, що доступ до пам'яті є повільним, а кешування працює краще для послідовних даних. Це все доступно в його основній записці з "GoingNative 2012"
ухиляючись від


1
Якщо ви хочете побачити основний коментар Бьярна Струструпа, який @evading згадав, я знайшов його тут: youtu.be/OB-bdWKwXsU?t=2672
brain56

Відповіді:


98

Ситуації, коли ви хочете вставити багато елементів у будь-яку точку, окрім кінця послідовності.

Ознайомтеся з гарантіями складності для кожного контейнера різних типів:

Які гарантії складності стандартних контейнерів?


2
Вставлення елементів наприкінці також враховується, оскільки це може призвести до розподілу пам'яті та витрат на копіювання елементів. А також, вставляти еленети на початку вектора поруч із неможливим, listмаєpush_front
Notinlist

9
Ні, вставлення елементів у кінці вектора амортизується постійним часом. Розподіл пам'яті відбувається лише періодично, і ви можете попередньо виділити вектор, щоб запобігти цьому. Звичайно, якщо ви ОБОВ'ЯЗКОВО забезпечили послідовне введення постійного часу, я думаю, це все-таки проблема.
Брайан

16
@Notinlist - Чи для вас таке "поруч із неможливим"? v.insert (v.begin (), i)
Мануель

5
@Notinlist - Я згоден з вами, я просто не хотів, щоб ОП думала, що інтерфейсу немає, на випадок, якщо хтось захоче стріляти в ногу (продуктивність).
Мануель

16
Bjarne Strostrup фактично зробив тест, де він генерував випадкові числа, а потім додав їх до списку та вектора відповідно. Вставки були зроблені таким чином, щоб список / вектор був упорядкований у всі часи. Хоча це, як правило, "список домену", вектор перевершує список за великим запасом. Причина в тому, що доступ до пам'яті є повільним, а кешування працює краще для послідовних даних. Це все доступно в його основній записці від "GoingNative 2012"
ухиляючись

409

вектор:

  • Сумісна пам'ять.
  • Попередньо виділяйте простір для майбутніх елементів, тому потрібен додатковий простір поза тим, що необхідно для самих елементів.
  • Кожному елементу потрібно лише простір для самого типу елемента (без зайвих покажчиків).
  • Ви можете перерозподілити пам'ять на весь вектор у будь-який час, коли ви додасте елемент.
  • Вкладиші в кінці - постійний, амортизований час, але вставки в іншому місці - це дороге O (n).
  • Стирання в кінці вектора - постійний час, але для решти - це O (n).
  • Ви можете випадковим чином отримати доступ до його елементів.
  • Ітератори недійсні, якщо ви додаєте або видаляєте елементи до вектора або з нього.
  • Ви можете легко дістатись до нижнього масиву, якщо вам потрібен масив елементів.

список:

  • Непомітна пам'ять.
  • Немає попередньо виділеної пам'яті. Накладні витрати на пам'ять для самого списку постійні.
  • Кожному елементу потрібен додатковий простір для вузла, який містить елемент, включаючи вказівники на наступний та попередні елементи у списку.
  • Ніколи не потрібно перерозподіляти пам'ять для всього списку лише тому, що ви додаєте елемент.
  • Вкладиші та стирання коштують дешево, незалежно від того, де в списку вони зустрічаються.
  • Поєднувати списки зі сплайсінгами недорого.
  • Ви не можете отримати довільний доступ до елементів, тому потрапляння на певний елемент у списку може бути дорогим.
  • Ітератори залишаються дійсними навіть тоді, коли ви додаєте або видаляєте елементи зі списку.
  • Якщо вам потрібен масив елементів, вам доведеться створити новий і додати їх усі до нього, оскільки не існує базового масиву.

Як правило, використовуйте вектор, коли вам не важливо, який тип послідовного контейнера ви використовуєте, але якщо ви робите багато вставок або стирань в та з будь-якого місця контейнера, крім кінця, вам захочеться використовувати список. Або якщо вам потрібен випадковий доступ, тоді вам потрібно буде вектор, а не список. Крім цього, є, природно, випадки, коли вам потрібно буде те чи інше на основі вашої заявки, але загалом це хороші вказівки.


2
Також виділення з безкоштовного магазину не є безкоштовним. :) Додавання нових елементів у вектор виконує виділення O (log n) вільного магазину, але ви можете зателефонувати, reserve()щоб зменшити його до O (1). Додавання нових елементів до списку (тобто не спланування їх) виконує O (n) виділення безкоштовного магазину.
bk1e

7
Інша думка полягає в тому, що listзвільняє пам’ять, коли ви стираєте елементи, але vectorце не робить. A vectorне знижує його ємність, коли ви зменшуєте її розмір, якщо ви не використовуєте swap()трюк.
bk1e

@ bk1e: Я дуже хочу знати твою хитрість в резерві () та
свопі

2
@nXqd: якщо вам потрібно додати N елементів до вектора, зателефонуйте v.reserve (v.size () + N), щоб він виконував лише один розподіл у вільному магазині. Своп () Хитрість тут: stackoverflow.com/questions/253157/how-to-downsize-stdvector
bk1e

1
@simplename Ні. Правильно сказане. вектор виділяє додатковий простір поза простором для елементів, що перебувають у векторі; потім додаткова ємність використовується для вирощування вектора, так що вирощування його амортизується O (1).
Джонатан М Девіс

35

Якщо вам не потрібно часто вставляти елементи, вектор буде більш ефективним. Він має набагато кращу локальність процесорного кешу, ніж список. Іншими словами, доступ до одного елемента робить великою ймовірністю, що наступний елемент присутній у кеші, і його можна отримати без необхідності читати повільну ОЗУ.


32

Більшість відповідей тут пропускають одну важливу деталь: для чого?

Що ви хочете зберегти у контейнері?

Якщо це колекція ints, то std::listви втратите в кожному сценарії, незалежно від того, чи зможете перерозподілити їх, ви видалите лише з передньої частини і т. Д. Списки проходять повільніше, кожна вставка коштує вам взаємодії з розподільником. Бути вкрай важко підготувати приклад, де list<int>б’ється vector<int>. І навіть тоді,deque<int> може бути кращим або закритим, а не просто використовувати списки, що матиме більший обсяг пам'яті.

Однак якщо ви маєте справу з великими некрасивими крапками даних - і їх небагато - ви не хочете перерозподіляти місця при вставлянні, а копіювання через перерозподіл було б катастрофою - тоді, можливо, вам буде краще з list<UglyBlob>ніж vector<UglyBlob>.

І все-таки, якщо ви перейдете на vector<UglyBlob*>або навіть vector<shared_ptr<UglyBlob> >, знову - список буде відставати.

Отже, шаблон доступу, кількість цільових елементів тощо все ще впливає на порівняння, але, на мій погляд, розмір елементів - вартість копіювання тощо.


1
Ще одне відображення, яке я мав, читаючи Майєрса "Ефективний STL": своєрідною властивістю list<T>є можливість spliceв (O) (1) . Якщо вам потрібно сплайнування постійного часу, список також може бути структурою за вибором;)
Tomasz Gandor

+1 - Він навіть не має бути UglyBlob- Підійде навіть об'єкти з допомогою всього кілька членів струнних можуть легко бути занадто дорогими , щоб скопіювати, тому перерозподілу будуть коштувати. Також: Не нехтуйте простором над головою, це vectorможе призвести до експоненціального зростання об'єктів утримування розміром у кілька десятків байт (якщо ви не можете reserveзаздалегідь).
Мартін Ба

Щодо vector<smart_ptr<Large>>порівняння list<Large>- я б сказав, якщо вам потрібен випадковий доступ до елементів, це vectorмає сенс. Якщо вам не потрібен випадковий доступ, listздається простішим і має працювати однаково.
Мартін Ба

19

Однією з особливостей std :: list є сплайсинг (пов'язування або переміщення частини цілого списку в інший список).

Або, можливо, якщо ваш вміст копіювати дуже дорого. У такому випадку може бути дешевше, наприклад, сортувати колекцію за списком.

Також зауважте, що якщо колекція невелика (а вміст копіювати не особливо дорого), вектор може все-таки перевершити список, навіть якщо ви вставляєте та стираєте де-небудь. Список виділяє кожен вузол окремо, і це може бути значно дорожче, ніж переміщення декількох простих об'єктів.

Я не думаю, що є дуже жорсткі правила. Це залежить від того, що ви в основному хочете зробити з контейнером, а також від того, наскільки великим ви будете розраховувати контейнер і тип, який міститься. Вектор, як правило, козирує список, оскільки він розподіляє його вміст як єдиний суміжний блок (це в основному динамічно розподілений масив, і в більшості випадків масив є найефективнішим способом зберігати купу речей).


1
+1. Сплайсування не помічено, але, на жаль, не є постійним часом, як бажано. : (((Не може бути, якщо список :: розмір є постійним часом.)

Я цілком впевнений, що список :: size є (дозволено бути) лінійним саме з цієї причини.
UncleBens

1
@Roger: list::sizeне обов'язково постійний час. Див stackoverflow.com/questions/228908/is-listsize-really-on і gcc.gnu.org/ml/libstdc++/2005-11/msg00219.html
Potatoswatter

@Potatoswatter: Якщо стандарт нечіткий і що, отже, ви не можете розраховувати на "сумісні" реалізації, це стає ще більшою проблемою. Ви буквально повинні уникати stdlib, щоб отримати портативну та надійну гарантію.

@Roger: так, на жаль. Мій поточний проект сильно покладається на операції сплайсингу, і ця структура майже пряма C. Навіть більше, на жаль, в N3000 послідовність spliceміж різними списками вказана як лінійна складність і sizeконкретно є постійною. Отже, для розміщення новачків, які повторюють sizeчи що завгодно, цілий клас алгоритмів недоступний для STL або будь-якого "сумісного" контейнера періоду.
Potatoswatter

13

Ну, студенти мого класу, здається, зовсім не в змозі пояснити мені, коли ефективніше використовувати вектори, але вони виглядають цілком щасливими, коли радять мені використовувати списки.

Це я так розумію

Списки : кожен елемент містить адресу наступного або попереднього елемента, тому за допомогою цієї функції ви можете рандомізувати елементи, навіть якщо вони не відсортовані, порядок не зміниться: це ефективно, якщо пам'ять буде фрагментована. Але це також має ще одну дуже велику перевагу: ви можете легко вставляти / видаляти елементи, адже єдине, що вам потрібно зробити, - це змінити деякі покажчики. Недолік: Щоб прочитати випадковий один елемент, вам потрібно перейти з одного елемента на інший, поки не знайдете правильну адресу.

Вектори : При використанні векторів пам'ять набагато більш організована, як звичайні масиви: кожен n-й елемент зберігається відразу після (n-1) -го елемента і до (n + 1) -го елемента. Чому це краще, ніж перелік? Тому що це дозволяє швидкий випадковий доступ. Ось як: якщо ви знаєте розмір елемента у векторі, і якщо вони є суміжними в пам'яті, ви можете легко передбачити, де знаходиться n-й елемент; вам не доведеться переглядати весь елемент списку, щоб прочитати той, який ви хочете, з вектором ви безпосередньо читаєте його зі списком, який ви не можете. З іншого боку, змінювати векторний масив або змінювати значення набагато повільніше.

Списки є більш доречними для відстеження об'єктів, які можна додати / вилучити в пам'яті. Вектори доцільніше, коли ви хочете отримати доступ до елемента з великої кількості одиниць.

Я не знаю, як списки оптимізовані, але ви повинні знати, що якщо ви хочете швидкого доступу до читання, ви повинні використовувати вектори, тому що, наскільки добре STL скріплює списки, він не буде таким швидким у доступі до читання, ніж вектор.


"змінити векторний масив або змінити значення набагато повільніше" - як читати, це, здається, суперечить тому, що ви говорили раніше, про те, що вектор схильний до хорошої продуктивності через їх низького рівня та суміжного характеру. Ви мали в виду , що Перерозподілvector викликано зміною його розміру може бути повільним? потім домовились, але у випадках, коли reserve()їх можна використовувати, це дозволяє уникнути цих проблем.
підкреслюй_

10

В основному вектор - це масив з автоматичним управлінням пам'яттю. Дані є суміжними в пам'яті. Спроба вставити дані в середину - це дорога операція.

У списку дані зберігаються у незв'язаних місцях пам'яті. Вставлення в середину не передбачає копіювання деяких даних, щоб звільнити місце для нового.

Щоб відповісти конкретніше на ваше запитання, я цитую цю сторінку

вектори, як правило, є найбільш ефективними в часі для доступу до елементів та додавання або видалення елементів з кінця послідовності. Для операцій, які передбачають вставлення або видалення елементів у позиціях, відмінних від кінця, вони виконуються гірше, ніж деки та списки, і мають менш послідовні ітератори та посилання, ніж списки.


9

Будь-який час не можна визначити ітераторами недійсними.


2
Але ніколи не прискорюйтесь до цього висновку про ітераторів, не запитуючи, чи достатньо стійких посилань на dequeатрибути.
Potatoswatter

8

Коли в середині послідовності багато вставок або видалень. наприклад, менеджер пам'яті.


тому різниця між ними полягає лише в ефективності, а не у функціональному питанні.
skydoor

Вони обидва моделюють послідовність елементів курсу. Існує невелика різниця у використанні, як згадує @dirkgently, але ви повинні переглянути складність ваших "часто виконуваних" операцій, щоб вирішити, яку послідовність вибрати (@Martin відповідь).
АраК

@skydoor - Є кілька функціональних відмінностей. Наприклад, тільки вектор підтримує випадковий доступ (тобто може бути індексований).
Мануель

2
@skydoor: ефективність означає ефективність. Низька продуктивність може зруйнувати функціональність. Продуктивність - це перевага C ++.
Potatoswatter

4

Збереження дійсності ітераторів є однією з причин використання списку. Інша справа, коли ви не хочете, щоб вектор перерозподілявся під час натискання елементів. Це може бути керовано інтелектуальним використанням резерву (), але в деяких випадках просто використовувати або доцільніше просто використовувати список.


4

Коли ви хочете переміщати об'єкти між контейнерами, ви можете використовувати list::splice.

Наприклад, алгоритм розподілу графіків може мати постійну кількість об'єктів, рекурсивно розділених між зростаючою кількістю контейнерів. Об'єкти слід ініціалізувати один раз і завжди залишатися в одних і тих же місцях у пам'яті. Набагато швидше їх переставити за допомогою перестановки, ніж шляхом перерозподілу.

Редагувати: коли бібліотеки готуються до впровадження C ++ 0x, загальний випадок сплайсування підпорядкованості списку набуває лінійної складності з довжиною послідовності. Це тому, що splice(зараз) потрібно перебирати послідовність, щоб підрахувати кількість елементів у ній. (Оскільки у списку потрібно записати його розмір.) Просто підрахунок та повторне посилання списку все ще швидше, ніж будь-яка альтернатива, а сплаування всього списку чи одного елемента - це особливі випадки з постійною складністю. Але, якщо у вас є довгі послідовності для сплайсингу, вам, можливо, доведеться копатися для кращого, старомодного, невідповідного контейнера.


2

Єдине жорстке правило, де list потрібно використовувати, - це те, де потрібно розподілити покажчики на елементи контейнера.

На відміну від vector, ви знаєте, що пам'ять елементів не буде перерозподілена. Якщо це може бути, то у вас можуть бути вказівники на невикористану пам'ять, що в кращому випадку є великим ні-ні, а в гіршому - гіршимSEGFAULT .

(Технічно vectorз *_ptrтакож буде працювати , але в цьому випадку ви емуліруетеlist , так що це тільки семантику.)

Інші м'які правила пов'язані з можливими проблемами продуктивності вставлення елементів у середину контейнера, після чого listбуло б кращим.


2

Зробити це просто:
На кінець дня, коли ви плутаєте вибір контейнерів у C ++, використовуйте це графічне зображення (Скажіть спасибі мені): - введіть опис зображення тут

Вектор
1. Вектор заснований на заразну пам'ять
2. вектора шлях для невеликого набору даних
3. вектор виконувати швидкий при обході за даними множинного
4. вектора вставки делеции повільно на величезному наборі даних , але швидко для дуже невеликих

List-
1. Список заснований на динамічній пам'яті
2. Список шлях для надто великої набору даних
3. Список порівняно повільно на обході невеликий набір даних , але швидко на величезний набір даних
4. список вставки делеции швидко на величезний набір даних, але повільний на менших


1

Списки є лише обгорткою для подвійного LinkedList у stl, таким чином пропонуючи функцію, яку ви можете очікувати від d-linklist, а саме вставки та видалення O (1). У той час як вектори заразна послідовність даних, яка працює як динамічний масив.PS- простіше пройти.


0

Що стосується вектора та списку , то основні відмінності, які випливають із мене, полягають у наступному:

вектор

  • Вектор зберігає свої елементи у суміжній пам'яті. Тому можливий випадковий доступ всередині вектора, що означає, що доступ до елемента вектора є дуже швидким, оскільки ми можемо просто помножити базову адресу на індекс елемента, щоб отримати доступ до цього елемента. Насправді для цього потрібно лише O (1) або постійний час.

  • Оскільки вектор в основному обертає масив, кожен раз, коли ви вставляєте елемент у вектор (динамічний масив), він повинен змінити розмір себе, знайшовши новий суміжний блок пам'яті для розміщення нових елементів, що затратно за часом.

  • Він не потребує додаткової пам'яті для зберігання будь-яких покажчиків на інші елементи всередині нього.

список

  • Список зберігає його елементи в безперервній пам'яті. Таким чином, випадковий доступ не можливий всередині списку, що означає, що для доступу до його елементів нам потрібно використовувати покажчики та пересувати список, який повільніше відносно вектора. Це займає O (n) або лінійний час, який повільніше, ніж O (1).

  • Оскільки в списку використовується неперервна пам'ять, час, необхідний для вставки елемента всередині списку, набагато ефективніший, ніж у випадку його векторного аналога, оскільки уникає перерозподілу пам'яті.

  • Він витрачає додаткову пам'ять для зберігання покажчиків на елемент до і після конкретного елемента.

Отже, маючи на увазі ці відмінності, ми зазвичай вважаємо пам'ять , частий випадковий доступ та вставлення, щоб визначити переможець списку вектора проти певного сценарію.


0

Список є подвійно пов'язаним списком, тому його легко вставити та видалити. Ми повинні просто змінити кілька покажчиків, тоді як у векторному, якщо ми хочемо вставити елемент посередині, то кожен елемент після нього повинен зміщуватися на один індекс. Крім того, якщо розмір вектора повний, він повинен спочатку збільшити його розмір. Тож це дорога операція. Тому там, де потрібно вводити та видаляти операції частіше, у такому списку справ слід використовувати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.