Я рідко використовую вказівник хвоста для пов'язаних списків і частіше використовую поодинокі зв'язані списки частіше там, де достатньо складового типу "push / pop" вставки та вилучення (або просто лінійного вилучення з середини). Це тому, що в моїх звичайних випадках використання вказівник хвоста насправді дорогий, подібно до того, як доробити окремо пов'язаний список у подвійно пов'язаний список дорого.
Часто моє загальне використання випадків для спільно пов'язаного списку може зберігати сотні тисяч зв'язаних списків, які містять лише кілька вузлів списку. Я також не використовую покажчики для пов'язаних списків. Я замість цього використовую індекси в масиві, оскільки індекси можуть бути 32-бітовими, наприклад, займаючи половину простору 64-бітного вказівника. Я також, як правило, не виділяю вузли списку по одному, а замість цього, знову ж таки, просто використовую великий масив для зберігання всіх вузлів, а потім використовую 32-бітні індекси для з'єднання вузлів разом.
Як приклад, уявіть відеоігри, що використовують сітку 400х400, щоб розділити мільйон частинок, які рухаються навколо та відскакують одна від одної для прискорення виявлення зіткнень. У цьому випадку досить ефективний спосіб зберігання - це зберігання 160 000 однозначно пов'язаних списків, що в моєму випадку означає 160 000 32-бітових цілих чисел (~ 640 кілобайт) і одне 32-бітове ціле число на частину. Тепер, коли частинки рухаються по екрану, все, що нам потрібно зробити, - це оновити кілька 32-бітових цілих чисел для переміщення частинки з однієї комірки в іншу, наприклад:
... з next
індексом ("покажчиком") вузла частинок, який служить або індексом до наступної частинки в клітині, або до наступної вільної частинки, щоб повернути, якщо частка загинула (в основному, це вільна реалізація алокатора списку за допомогою індексів):
Вилучення лінійного часу з комірки насправді не є накладними, оскільки ми обробляємо логіку частинок шляхом повторення через частинки в клітині, тому подвійно пов'язаний список просто додасть накладні витрати, які не вигідні все в моєму випадку так само, як хвіст мені теж не піде на користь.
Хвостовий покажчик удвічі збільшить об'єм пам'яті в мережі, а також збільшить кількість пропусків кешу. Він також потребує вставки, щоб вимагати відгалуження перевірити, чи список порожній замість того, щоб він був без гілки. Створення цього подвійно пов'язаного списку дозволило б удвічі перевищити список кожної частинки. У 90% часу я використовую пов'язані списки, це для таких випадків, і тому вказівник хвоста насправді буде досить дорогим для зберігання.
Отже, 4-8 байт насправді не є тривіальним у більшості контекстів, у яких я в першу чергу використовую пов'язані списки. Просто хотілося занести туди, оскільки якщо ви використовуєте структуру даних для зберігання навантажувальних елементів, то 4-8 байт не завжди може бути настільки мізерним. Я фактично використовую пов'язані списки, щоб зменшити кількість пам'яті та кількість необхідної пам'яті, на відміну, скажімо, для зберігання 160 000 динамічних масивів, які ростуть для сітки, яка б використовувала вибухонебезпечну пам'ять (як правило, один вказівник плюс два цілі числа принаймні на комірку сітки разом із виділеннями на одну клітинку сітки на відміну від лише одного цілого і нульового розподілу на одну клітинку).
Я часто знаходжу багатьох людей, які тягнуться до зв'язаних списків за їхньою складністю у постійному часі, пов’язаній із видаленням передньої / середньої та передньої / середньої вставки, коли ЛЛ часто є поганим вибором у цих випадках через їх загальну відсутність суміжності. Де LL мені красиві з точки зору продуктивності - це можливість просто переміщати один елемент із одного списку в інший, просто маніпулюючи кількома вказівниками, і бути в змозі досягти структури даних змінного розміру без розподільника пам'яті змінного розміру (оскільки кожен вузол має однаковий розмір, ми можемо використовувати вільні списки, наприклад). Якщо кожен вузол списку розподіляється індивідуально проти розподільника загального призначення, зазвичай це стосується, коли пов'язані списки стають значно гіршими порівняно з альтернативами, і це "
Я б замість цього запропонував, що для більшості випадків, коли зв'язані списки служать дуже ефективною оптимізацією за прямими альтернативами, найкорисніші форми, як правило, однозначно пов'язані, потрібен лише вказівник голови та не потребує розподілу пам'яті загального призначення на вузол і може замість цього просто просто об'єднувати пам'ять, вже виділену на вузол (з великого масиву, який вже був виділений заздалегідь, наприклад). Також кожна SLL зазвичай зберігає дуже малу кількість елементів у таких випадках, як краї, підключені до вузла графіка (багато крихітних пов'язаних списків на відміну від одного масивного пов'язаного списку).
Варто також пам’ятати, що в наші дні у нас завантажується DRAM, але це другий найповільніший тип пам’яті. Ми все ще знаходимось на кшталт 64 КБ на ядро, якщо мова йде про кеш L1 з 64-байтовими лініями кеша. Як результат, ці маленькі байтові заощадження можуть дійсно мати значення в критичній для продуктивності області, як симулятор частинок вище, коли їх помножують мільйони разів, якщо це означає різницю між збереженням удвічі більшої кількості вузлів у кеш-лінії чи ні, наприклад