Коли використовувати зв'язаний список над списком масиву / масиву?


176

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


У Java, ArrayList та LinkedList використовують абсолютно той самий код, окрім конструктора. Ваш "список масивів ... який використовується так само легко, як і простіше, ніж пов'язаний список" не має сенсу. Наведіть приклад того, що ArrayList "простіше", ніж LinkedList.
С.Лотт

2
Перевірте це , а також, stackoverflow.com/questions/322715 / ...
NoName


3
С.Лотт Це неправда. Java ArrayList - це обгортка навколо масиву, додано деякі функції утиліти. Зв'язаний список, очевидно, пов'язаний список. developer.classpath.org/doc/java/util/ArrayList-source.html
kingfrito_5005

Відповіді:


261

Пов'язані списки бажані над масивами, коли:

  1. вам потрібні вставки / вилучення постійного часу зі списку (наприклад, у обчисленні в реальному часі, де передбачуваність часу абсолютно критична)

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

  3. вам не потрібен випадковий доступ до будь-яких елементів

  4. ви хочете мати змогу вставляти елементи в середину списку (наприклад, черги з пріоритетом)

Масиви бажані, коли:

  1. вам потрібен індексований / випадковий доступ до елементів

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

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

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

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


7
Хороший початок, але це виключає важливі речі: списки підтримки спільного використання структури, масиви щільніші та мають кращу локальність.
Дарій Бекон

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

40
З тих пір, коли у LinkedList є O (1) вставлення / видалення (на що я маю на увазі, що ви маєте на увазі, коли ви говорите про постійні вставки / видалення часу )? Вставляти речі в середину LinkedList завжди O (n)
Pacerier

28
У LinkedLists є вставки O (1), якщо ви вже знаходитесь у місці вставки (через ітератор). Не завжди.
Адам

4
Використання пов'язаних списків для черг з пріоритетом - дуже дурна ідея. Динамічні масиви, підтримувані масивом, дозволяють ввести амортизовану вставку O (lg n) та логарифмічне видалення в найгіршому випадку, і є одними з найшвидших практичних структур пріоритетних черг.
Фред Фоо

54

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

Пов’язані списки дійсно дешеві, щоб додавати або видаляти елементи будь-де та повторювати, але випадковий доступ - це O (n).


3
Видалення елементів з кінця масиву - це час контенту, як і вставлення / видалення елементів з будь-якого кінця пов'язаного списку. В середині ... не так і для одного.
Джої

1
@Joey не є вставкою / видаленням наприкінці пов'язаного списку O (n)? Якщо ви вже розміщені на передостанньому посиланні, вам знадобиться O (n) кроки, щоб знайти останній елемент, ні?
Алекс Мур-Ніємі

@ AlexMoore-Niemi: Для спільно пов'язаного списку так. Але у багатьох є посилання вперед і назад, і таким чином утримуйте покажчики в будь-якому кінці.
Joey

2
Наявлення подвійних списків призведе до того, що ви будете шукати вперед і назад, якщо ваш LL не призначив значення ... і все-таки найгірший сценарій - O (n)
securecurve

"Пов'язані списки дійсно дешеві, щоб додавати або видаляти елементи де завгодно і повторювати", це не зовсім вірно. Якщо я хочу видалити елемент, що знаходиться в середині пов’язаного списку, мені доведеться повторювати його від початку, поки я не дойду до цього елемента у списку. Його O (n / 2) час, коли n = кількість елементів у списку. З вашої відповіді це звучить так, ніби ви пропонуєте його постійний час O (1), як і в масиві. Постійний час додавати / видаляти із заголовка / кореневого вузла пов'язаного списку.
Явар Муртаза

22
Algorithm           ArrayList   LinkedList
seek front            O(1)         O(1)
seek back             O(1)         O(1)
seek to index         O(1)         O(N)
insert at front       O(N)         O(1)
insert at back        O(1)         O(1)
insert after an item  O(N)         O(1)

ArrayLists хороші для запису один раз читання-багато чи додатків, але погано додають / видаляють спереду або посередині.


14

Щоб додати до інших відповідей, більшість реалізацій списку масивів залишають додаткову ємність в кінці списку, щоб нові елементи можна було додати до кінця списку в O (1) час. При перевищенні ємності списку масивів внутрішньо виділяється новий, більший масив, і всі старі елементи копіюються. Зазвичай новий масив вдвічі перевищує старий. Це означає, що в середньому додавання нових елементів до кінця списку масивів є операцією O (1) у цих реалізаціях. Тож навіть якщо ви не знаєте кількість елементів заздалегідь, список масивів все ще може бути швидшим, ніж пов'язаний список для додавання елементів, якщо ви додаєте їх наприкінці. Очевидно, що вставлення нових елементів у довільних місцях у список масиву все ще є операцією O (n).

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

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


1
Крім того, ви також можете реалізовувати пов'язані списки (у сенсі абстрактного типу даних), використовуючи динамічні масиви. Таким чином ви можете використовувати кеш комп'ютера, маючи амортизовані постійні часові вставки та вилучення на чолі списку, а також амортизувати постійні часові вставки та вилучення в середині списку, коли у вас є індекс елемента, після якого вставка повинна буде виконано або індекс елемента, який потрібно видалити (не потрібні зрушення / зміщення). Хорошим посиланням на це є CLRS 10.3 .
Доменіко Де Феліче

7

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

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


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

3

Все залежить від того, який тип операції ви виконуєте під час ітерації, всі структури даних торгуються між часом і пам’яттю, і залежно від наших потреб ми повинні вибрати правильний DS. Тому є деякі випадки, коли LinkedList швидше, ніж масив, і навпаки. Розглянемо три основні операції над структурами даних.

  • Пошук

Оскільки масив є структурою даних, заснованою на індексі, пошук array.get (index) займе O (1) час, поки пов'язаний список не є індексом DS, тому вам потрібно буде пройти до індексу, де index <= n, n - розмір пов'язаного списку, тому масив швидше пов'язаний список, коли у них є випадковий доступ до елементів.

Q. Отже, яка краса за цим?

Оскільки масиви є суміжними блоками пам’яті, великі шматки з них будуть завантажені в кеш при першому доступі, це робить порівняно швидким доступ до решти елементів масиву, наскільки ми отримуємо доступ до елементів масиву посилання, що збільшується, також збільшується, таким чином зменшується улов Пропускає, Кеш-локальність посилається на операції, що знаходяться в кеші, і, таким чином, виконується набагато швидше порівняно з пам'яттю, в основному У масиві ми максимізуємо шанси на отримання послідовного доступу до елементів кешу. Хоча пов'язані списки не обов'язково знаходяться в суміжних блоках пам'яті, немає гарантії, що елементи, що з'являються послідовно у списку, насправді розташовуються поруч один з одним у пам'яті, це означає меншу кількість показів кешу, наприклад

  • Введення

Це в LinkedList легко і швидко, оскільки вставка - це операція O (1) в LinkedList (на Java) порівняно з масивом. Враховуйте випадок, коли масив заповнений, нам потрібно скопіювати вміст у новий масив, якщо масив буде заповнений, що робить вставку елемент у ArrayList з O (n) в гіршому випадку, тоді як ArrayList також повинен оновити свій індекс, якщо ви вставите щось де завгодно, крім кінця масиву, у випадку пов'язаного списку нам не потрібно змінювати його розмір, вам просто потрібно оновити покажчики.

  • Видалення

Він працює як вставки та краще в LinkedList, ніж масив.


2

Це найпоширеніші втілення в життя Collection.

ArrayList:

  • вставити / видалити наприкінці зазвичай O (1) найгірший випадок O (n)

  • вставити / видалити посередині O (n)

  • отримати будь-яку позицію O (1)

LinkedList:

  • вставити / видалити в будь-якому положенні O (1) (зверніть увагу, якщо у вас є посилання на елемент)

  • отримати в середині O (n)

  • отримання першого або останнього елемента O (1)

Вектор: не використовуйте його. Це стара версія, схожа на ArrayList, але з усіма методами синхронізована. Це не правильний підхід для спільного списку в багатопотоковому середовищі.

HashMap

вставити / видалити / отримати за допомогою ключа в O (1)

TreeSet вставити / видалити / містить у O (журнал N)

HashSet вставити / вилучити / містить / розмір у O (1)


1

Насправді локальність пам'яті має величезний вплив на продуктивність у реальній обробці.

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

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


0

Гм, Arraylist може бути використаний у таких випадках, як я думаю:

  1. ви не впевнені, скільки елементів буде присутнім
  2. але вам потрібно отримати доступ до всіх елементів випадковим чином через індексацію

Наприклад, вам потрібно імпортувати та отримати доступ до всіх елементів у списку контактів (розмір яких вам невідомий)


0

Використовуйте зв'язаний список для сортування Radix по масивах та для поліномних операцій.


0

1) Як пояснено вище, операції вставки та видалення дають хороші показники (O (1)) у LinkedList порівняно з ArrayList (O (n)). Отже, якщо є необхідність частого додавання та видалення в додатку, тоді LinkedList - найкращий вибір.

2) Операції пошуку (отримання методу) швидкі в Arraylist (O (1)), але не в LinkedList (O (n)), тому якщо менше операцій додавання та видалення та більше вимог пошукових операцій, ArrayList стане найкращою ставкою.


0

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

Якщо масив буде видалений із верхнього списку, то складність є o (n), тому що всі індекси елементів масиву доведеться зміщувати.

З пов'язаним списком це o (1), тому що вам потрібно створити лише вузол, перепризначити голову та призначити посилання наступній як попередній заголовок.

Коли часто вставляти чи видаляти в кінці списку, масиви є кращими, оскільки складність буде o (1), не потрібно повторне деіндекс, але для пов'язаного списку це буде o (n), тому що вам потрібно йти з голови до останнього вузла.

Я думаю, що пошук і в пов'язаному списку, і в масивах буде o (log n), тому що ви, ймовірно, будете використовувати двійковий пошук.


0

Я зробив деякий бенчмаркінг і виявив, що клас списку насправді швидший, ніж LinkedList для випадкової вставки:

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 20000;
            Random rand = new Random(12345);

            Stopwatch watch = Stopwatch.StartNew();
            LinkedList<int> ll = new LinkedList<int>();
            ll.AddLast(0);
            for (int i = 1; i < count; i++)
            {
                ll.AddBefore(ll.Find(rand.Next(i)),i);

            }
            Console.WriteLine("LinkedList/Random Add: {0}ms", watch.ElapsedMilliseconds);

            watch = Stopwatch.StartNew();
            List<int> list = new List<int>();
            list.Add(0);
            for (int i = 1; i < count; i++)
            {
                list.Insert(list.IndexOf(rand.Next(i)), i);

            }
            Console.WriteLine("List/Random Add: {0}ms", watch.ElapsedMilliseconds);

            Console.ReadLine();
        }
    }
}

Для пов'язаного списку потрібно 900 мс і для класу списку 100 мс.

Він створює списки наступних цілих чисел. Кожне нове ціле число вставляється після випадкового числа, яке вже є у списку. Можливо, клас List використовує щось краще, ніж просто масив.


Список - це інтерфейс, а не клас
боргмайстер

0

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

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

Бінарні дерева та двійкові дерева пошуку, хеш-таблиці та спроби - це деякі структури даних, в яких, принаймні, на С, вам потрібні пов'язані списки як основний компонент для їх створення.

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


0

Просту відповідь на питання можна дати, скориставшись цими пунктами:

  1. Масиви слід використовувати, коли потрібен збір елементів даних подібного типу. Тоді як пов'язаний список - це сукупність елементів, пов'язаних з даними змішаного типу, відомих як вузли.

  2. У масиві можна відвідати будь-який елемент за час O (1). В той час, як у зв'язаному списку нам потрібно буде пройти весь зв'язаний список від голови до потрібного вузла з урахуванням часу O (n).

  3. Для масивів певний розмір потрібно оголосити спочатку. Але пов'язані списки мають динамічний розмір.

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