Чи кожен тип даних просто зводиться до вузлів з покажчиками?


21

Масив або вектор - це лише послідовність значень. Вони, безумовно, можуть бути реалізовані за допомогою пов'язаного списку. Це лише купа вузлів із вказівниками на наступний вузол.

Стеки та черги - це два абстрактних типи даних, які зазвичай викладаються на курсах Intro CS. Десь на уроці учням часто доводиться реалізовувати стеки та черги, використовуючи зв'язаний список як структуру даних, що означає, що ми повернулися до тієї самої ідеї "колекції вузлів".

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

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


5
Фундаментальна структура даних, на яку ви натякаєтеся, називається "клітиною проти"; ви можете створити з них будь-яку структуру даних. Якщо ви хочете знати, чому даний автор підручника не вирішив пояснити мінуси, запитайте цього автора, чому вони зробили такий вибір. Перейти від опису розташування вузлів до двійкового коду називається "компіляція" і є завданням "компілятора".
Ерік Ліпперт

18
Ви також можете стверджувати, що всі структури даних зводяться до масиву. Зрештою, всі вони опиняються в пам'яті, що є лише одним дуже великим масивом.
BlueRaja - Danny Pflughoeft

10
Ви не можете реалізувати масив за допомогою зв'язаного списку, якщо хочете зберегти індексацію . O(1)
svick

5
Вибачте, але говорити про «вузли та покажчики» означає, що ви вже піддалися їжі, що їхала. " Як всі реальні програмісти знають, єдиною корисною структурою даних є масив. Рядки, списки, структури, набори - це все особливі випадки масивів і таким чином можна легко поводитись так само, не псуючи вашу мову програмування всілякими ускладнень. "Посилання:" Реальні програмісти не використовують Паскаль ", від web.mit.edu/humor/Computers/real.programmers
alephzero

3
... але якщо серйозніше, важливим у структурах даних є те, що ви можете зробити з ними , а не як вони реалізуються. У 21-му столітті їх реалізація - це лише вправа програмування - а для ледачих педагогів той факт, що такі вправи легко оцінити, переважає той факт, що вони в кращому випадку безглузді, а в гіршому - позитивно шкідливі, якщо вони спонукають учнів думати, що " переосмислення коліс "- корисна діяльність в реальному програмуванні.
alephzero

Відповіді:


14

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

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

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

TL; DR: Контейнери - це в основному всі Вузли та Покажчики, але мають дуже специфічне використання і краще для чогось, а для інших - гірше.


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

13

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

О, дорогий ні. Ти мені шкодиш.

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

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

А також, наприклад, для графіків, які, безумовно, схожі на вузли та покажчики (ребра), у вас є два основних представлення, масив суміжності та списки суміжності. Не всі вказівники, які я собі уявляю.

Коли ви дійсно намагаєтесь зрозуміти структури даних, вам доведеться вивчити їхні хороші моменти та усунути слабкі сторони. Іноді представлення використовує масив для ефективності (або простору, або часу), іноді є вказівники (для гнучкості). Це справедливо навіть у тому випадку, коли у вас є хороші "консервовані" реалізації, такі як C ++ STL, тому що також там ви можете іноді вибирати основні представлення. Там завжди є компроміс.


10

Давайте зробимо аналогію з математикою. Розглянемо наступне речення: " - це суцільна функція". Функції дійсно визначаються з точки зору відносин, які визначаються через множини. Набір реальних чисел є унікальним повним повністю упорядкованим полем: всі ці поняття мають визначення більш простими термінами. Для того, щоб говорити про наступність, вам потрібна концепція сусідства, яка визначається відносно топології ... і так далі аж до аксіом ZFC.f:RR

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

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

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


10

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

Це приклад кодування булевих символів як функцій, записаних у ECMAScript:

const T   = (thn, _  ) => thn,
      F   = (_  , els) => els,
      or  = (a  , b  ) => a(a, b),
      and = (a  , b  ) => a(b, a),
      not = a          => a(F, T),
      xor = (a  , b  ) => a(not(b), b),
      iff = (cnd, thn, els) => cnd(thn, els)();

І це список мінусів:

const cons = (hd, tl) => which => which(hd, tl),
      first  = list => list(T),
      rest   = list => list(F);

Природні числа можуть бути реалізовані як функції ітератора.

Набір - це те саме, що і його характерна функція (тобто containsметод).

Зауважте, що вищезазначене кодування церков булевих форматів насправді є тим, як умовні умови реалізуються на мовах ОО, таких як Smalltalk, які не мають булевих, умовних або циклів як мовних конструкцій і реалізують їх виключно як функцію бібліотеки. Приклад у Scala:

sealed abstract trait Boolean {
  def apply[T, U <: T, V <: T](thn: => U)(els: => V): T
  def(other: => Boolean): Boolean
  def(other: => Boolean): Boolean
  val ¬ : Boolean

  final val unary_! = ¬
  final def &(other: => Boolean) =(other)
  final def |(other: => Boolean) =(other)
}

case object True extends Boolean {
  override def apply[T, U <: T, V <: T](thn: => U)(els: => V): U = thn
  override def(other: => Boolean) = other
  override def(other: => Boolean): this.type = this
  override final val ¬ = False
}

case object False extends Boolean {
  override def apply[T, U <: T, V <: T](thn: => U)(els: => V): V = els
  override def(other: => Boolean): this.type = this
  override def(other: => Boolean) = other
  override final val ¬ = True
}

object BooleanExtension {
  import scala.language.implicitConversions
  implicit def boolean2Boolean(b: => scala.Boolean) = if (b) True else False
}

import BooleanExtension._

(2 < 3) { println("2 is less than 3") } { println("2 is greater than 3") }
// 2 is less than 3

2
@Hamsteriffic: Спробуйте це: саме так реалізуються умови для мов OO, таких як Smalltalk. У Smalltalk немає булевих, умовних або циклів як мовної конструкції. Усі вони суто реалізовані як бібліотеки. Розум все ще не роздутий? Вільям Кук вказує на те, що повинно було бути очевидним давно, але насправді його не помітили: оскільки OO - це все, що стосується абстракцій поведінки, а поведінкова абстракція - єдиний вид абстракції, який існує в λ-обчисленні, звідси випливає, що всі програми, написані в λ-обчислення необхідні ОО. Ерго, λ-обчислення є найстарішим і…
Jörg W Mittag

… Найчистіша мова ОО!
Йорг W Міттаг

1
Поганий день з Smalltalk перемагає хороший день із C ++ :-)
Боб Джарвіс - Відновіть Моніку

@ JörgWMittag Я не думаю, що ваш висновок випливає з вашого припущення, я не думаю, що ваше припущення є навіть істинним, і я точно не думаю, що ваш висновок є правдивим.
Майлз Рут

4

Багато (більшість?) Структур даних побудовані з вузлів та покажчиків. Масиви - ще один важливий елемент деяких структур даних.

Зрештою, кожна структура даних - це лише купа слів у пам'яті, або просто купа біт. Те, як вони структуровані, і як ми інтерпретуємо та використовуємо їх, має значення.


2
Зрештою, біти - це купа електричних сигналів на дроті, або світлових сигналів волоконно-оптичного кабелю, або конкретно намагнічених частинок на блюді, або радіохвиль певної довжини хвилі, або, або, або. Тож питання полягає в тому, наскільки глибоко ви хочете пройти? :)
Wildcard

2

Впровадження структур даних завжди зводиться до вузлів і покажчиків, так.

Але навіщо зупинятися на цьому? Реалізація вузлів і покажчиків зводиться до бітів.

Реалізація бітів зводиться до електричних сигналів, магнітного накопичувача, можливо, фібероптичних кабелів тощо (одним словом, фізика.)

Це скорочення заяви абсурду заяви: "Усі структури даних зводяться до вузлів та покажчиків". Це правда, але стосується лише впровадження.


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

Ми можемо піти трохи далі, якщо зрозуміємо, що між моделлю та реалізацією не існує єдиної лінії розмежування. Це схоже (якщо не тотожне) поняттю "шари абстракції".

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

Однак нижні шари абстракції самі по собі мають деталі реалізації.

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

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

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

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


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


2

Масив або вектор - це лише послідовність значень. Вони, безумовно, можуть бути реалізовані за допомогою пов'язаного списку. Це лише купа вузлів із вказівниками на наступний вузол.

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

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

Θ(н)Θ(1)Θ(1)в середньому, ціною щонайменше постійного коефіцієнта додаткової пам'яті, просто округлення фактично виділеного розміру масиву до, наприклад, найближчої потужності 2. Але якщо вам потрібно зробити багато вставок та / або видалення елементів у середині списку, фізичний масив може бути не найкращою реалізацією для вашої структури даних. Однак досить часто ви можете замінити вставки та видалення свопами, що коштує дешево.

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

Θ(1)операція розвороту). Однак на практиці ці особливості рідко є корисними для подолання недоліків, які включають додаткову складність впровадження та несумісність зі стандартними схемами вивезення сміття .


1

Якщо це так просто, чому підручники не пояснюють, що дані - це лише купа вузлів з покажчиками?

Бо це не те, що означає "дані". Ви плутаєте абстрактні ідеї з реалізаціями. "Дані" - це дуже абстрактна ідея: це просто інша назва "інформації". Купа зв'язаних вузлів з покажчиками (так звана "зв'язана структура даних") - набагато конкретніша ідея: це специфічний спосіб подання та організації інформації.

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

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

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