Apakah setiap tipe data hanya mendidih ke node dengan pointer?

21

Array atau vektor hanyalah urutan nilai. Mereka pasti dapat diimplementasikan dengan daftar tertaut. Ini hanya sekelompok node dengan pointer ke node berikutnya.

Tumpukan dan antrian adalah dua tipe data abstrak yang umum diajarkan dalam kursus Intro CS. Di suatu tempat di kelas, siswa sering harus mengimplementasikan tumpukan dan antrian menggunakan daftar tertaut sebagai struktur data yang mendasarinya, yang berarti kita kembali ke ide "kumpulan node" yang sama.

Antrian prioritas dapat dibuat menggunakan Heap. Tumpukan dapat dianggap sebagai pohon dengan nilai min di root. Segala jenis pohon, termasuk BST, AVL, tumpukan dapat dianggap sebagai kumpulan node yang dihubungkan oleh tepian. Node-node ini dihubungkan bersama di mana satu titik menunjuk ke yang lain.

Sepertinya setiap konsep data selalu dapat mendidih menjadi hanya node dengan pointer ke beberapa node lain yang sesuai. Apakah itu benar? Jika sesederhana ini, mengapa buku teks tidak menjelaskan bahwa data hanyalah sekelompok node dengan pointer? Bagaimana kita beralih dari node ke kode biner?

derekchen14
sumber
5
Struktur data dasar yang Anda singgung disebut "sel kontra"; Anda dapat membangun struktur data apa pun yang Anda sukai darinya. Jika Anda ingin tahu mengapa penulis buku teks tertentu tidak memilih untuk menjelaskan sel kontra, tanyakan pada penulis itu mengapa mereka membuat pilihan itu. Untuk beralih dari deskripsi susunan simpul ke kode biner disebut "kompilasi" dan merupakan tugas "kompiler".
Eric Lippert
18
Anda juga bisa memperdebatkan semua struktur data hingga menjadi array. Bagaimanapun, mereka semua berakhir di memori, yang hanya satu array yang sangat besar.
BlueRaja - Danny Pflughoeft
10
Anda tidak dapat mengimplementasikan array menggunakan daftar yang ditautkan jika Anda ingin tetap mengindeks . O(1)
svick
5
Maaf, tetapi berbicara tentang "simpul dan petunjuk" berarti Anda telah menyerah pada makan quiche. " Seperti yang diketahui oleh semua Pemrogram Nyata, satu-satunya struktur data yang berguna adalah Array. String, daftar, struktur, set - ini semua adalah kasus khusus dari array dan dapat diperlakukan dengan mudah tanpa mengacaukan bahasa pemrograman Anda dengan segala macam . komplikasi "Ref: "real programer tidak menggunakan Pascal", dari web.mit.edu/humor/Computers/real.programmers
alephzero
3
... tetapi yang lebih serius, hal penting tentang struktur data adalah apa yang dapat Anda lakukan dengannya , bukan bagaimana mereka diimplementasikan. Pada abad ke-21 menerapkannya sendiri hanyalah latihan pemrograman - dan bagi para pendidik yang malas, fakta bahwa latihan semacam itu mudah dinilai lebih tinggi daripada fakta bahwa latihan-latihan itu paling tidak ada gunanya, dan paling buruk secara berbahaya jika mereka mendorong siswa untuk berpikir bahwa " menciptakan kembali roda "adalah kegiatan yang berguna dalam pemrograman dunia nyata.
alephzero

Jawaban:

14

Nah, itulah dasarnya semua struktur data mendidih. Data dengan koneksi. Semua simpul itu buatan - sebenarnya tidak ada secara fisik. Di sinilah bagian biner masuk. Anda harus membuat beberapa struktur data dalam C ++ dan memeriksa di mana objek Anda mendarat di memori. Sangat menarik untuk belajar tentang bagaimana data diletakkan dalam memori.

Alasan utama begitu banyak struktur yang berbeda adalah bahwa mereka semua berspesialisasi dalam satu atau lain hal. Misalnya, biasanya lebih cepat untuk beralih melalui vektor daripada daftar tertaut, karena cara halaman ditarik dari memori. Daftar tertaut lebih baik untuk menyimpan tipe berukuran lebih besar karena vektor harus mengalokasikan ruang ekstra untuk slot yang tidak digunakan (ini diperlukan dalam desain vektor).

Sebagai catatan tambahan, struktur data menarik yang mungkin ingin Anda lihat adalah Tabel Hash. Ini tidak cukup mengikuti sistem Nodes dan Pointer yang Anda gambarkan.

TL; DR: Wadah pada dasarnya semua Node dan Pointer tetapi memiliki kegunaan yang sangat spesifik dan lebih baik untuk sesuatu dan lebih buruk untuk orang lain.

pengguna3853544
sumber
1
Takeaway saya adalah bahwa sebagian besar data memang dapat direpresentasikan sebagai sekelompok node dengan pointer. Namun, itu bukan karena (a) pada tingkat fisik, bukan itu yang terjadi dan (b) pada tingkat konseptual, berpikir tentang nilai-nilai sebagai daftar tertaut tidak berguna untuk alasan tentang data yang mendasarinya. Lagipula itu semua hanya abstraksi untuk menyederhanakan pemikiran kita, jadi sebaiknya pilih juga abstraksi terbaik untuk suatu situasi bahkan jika yang lain bisa bekerja.
derekchen14
13

Sepertinya setiap konsep data selalu dapat mendidih menjadi hanya node dengan pointer ke beberapa node lain yang sesuai.

Oh, sayang tidak. Anda menyakitiku.

Seperti yang saya coba jelaskan di tempat lain (" Apa perbedaan antara pohon pencarian biner dan tumpukan biner? ") Bahkan untuk struktur data tetap ada beberapa tingkatan untuk memahaminya.

Seperti antrian prioritas yang Anda sebutkan, jika Anda hanya ingin menggunakannya, ini adalah tipe data abstrak. Anda menggunakannya untuk mengetahui objek apa yang disimpannya, dan pertanyaan apa yang dapat Anda ajukan untuk dilakukan. Itu lebih banyak struktur data sebagai kantong item. Pada tingkat selanjutnya implementasi yang terkenal, tumpukan biner, dapat dipahami sebagai pohon biner, tetapi tingkat terakhir adalah untuk alasan efisiensi diimplementasikan sebagai array. Tidak ada node dan pointer di sana.

Dan juga untuk grafik misalnya, yang tentu saja terlihat seperti node dan pointer (edge), Anda memiliki dua representasi dasar, array adjacency dan daftar adjacency. Tidak semua petunjuk saya bayangkan.

Ketika benar-benar mencoba memahami struktur data, Anda harus mempelajari poin bagus dan kelemahannya. Kadang-kadang representasi menggunakan array untuk efisiensi (baik ruang atau waktu) kadang-kadang ada petunjuk (untuk fleksibilitas). Ini berlaku bahkan ketika Anda memiliki implementasi "kalengan" yang bagus seperti C ++ STL, karena di sana Anda juga dapat memilih terkadang representasi yang mendasarinya. Selalu ada trade-off di sana.

Hendrik Jan
sumber
10

Mari kita membuat analogi dengan matematika. Pertimbangkan kalimat berikut: " adalah fungsi kontinu". Fungsi benar-benar didefinisikan dalam istilah hubungan, yang didefinisikan dalam istilah set. Himpunan bilangan real adalah bidang unik lengkap yang benar-benar dipesan: semua konsep ini memiliki definisi dalam istilah yang lebih sederhana. Untuk berbicara tentang kontinuitas, Anda memerlukan konsep lingkungan, yang didefinisikan dalam kaitannya dengan topologi ... dan seterusnya hingga ke aksioma ZFC.f:RR

Tidak ada yang mengharapkan Anda untuk mengatakan semua itu hanya untuk mendefinisikan fungsi kontinu, jika tidak, tidak ada yang bisa menyelesaikan pekerjaan sama sekali. Kami hanya "percaya" bahwa seseorang membuat pekerjaan yang membosankan bagi kami.

Setiap struktur data yang dapat Anda pikirkan bermuara pada objek dasar yang dihadapi model komputasi dasar Anda, bilangan bulat dalam beberapa register jika Anda menggunakan mesin akses acak, atau simbol pada beberapa rekaman jika Anda menggunakan mesin Turing.

Kami menggunakan abstraksi karena mereka membebaskan pikiran kami dari hal-hal sepele, memungkinkan kami untuk berbicara tentang masalah yang lebih kompleks. Sangat masuk akal untuk hanya "percaya" bahwa struktur-struktur itu bekerja: menyelinap ke dalam setiap detail adalah - kecuali Anda memiliki alasan khusus untuk melakukannya - latihan yang sia-sia yang tidak menambah apa pun pada argumen Anda.

quicksort
sumber
10

Inilah contoh tandingan: di λ-calculus, setiap tipe data bermuara pada fungsi. λ-calculus tidak memiliki node atau pointer, satu-satunya yang dimilikinya adalah fungsi, oleh karena itu semuanya harus diimplementasikan menggunakan fungsi.

Ini adalah contoh penyandian boolean sebagai fungsi, ditulis dalam 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)();

Dan ini adalah daftar kontra:

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

Bilangan alami dapat diimplementasikan sebagai fungsi iterator.

Himpunan adalah hal yang sama dengan fungsi karakteristiknya (yaitu containsmetode).

Perhatikan bahwa Pengodean Gereja Boolean di atas sebenarnya adalah bagaimana kondisional diterapkan dalam bahasa OO seperti Smalltalk, yang tidak memiliki boolean, kondisional, atau loop ketika bahasa membangun dan mengimplementasikannya murni sebagai fitur perpustakaan. Contoh dalam 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
Jörg W Mittag
sumber
2
@Hamsteriffic: Coba ini: itu sebenarnya bagaimana conditional diterapkan dalam bahasa OO seperti Smalltalk. Smalltalk tidak memiliki boolean, kondisional, atau loop sebagai bahasa konstruksi. Semua itu murni diimplementasikan sebagai perpustakaan. Pikiran masih belum meledak? William Cook menunjukkan sesuatu yang seharusnya sudah jelas sejak lama tetapi tidak benar-benar diperhatikan: karena OO adalah semua tentang abstraksi perilaku, dan abstraksi perilaku adalah satu-satunya jenis abstraksi yang ada dalam kalkulus, maka semua program yang ditulis dalam λ-kalkulus adalah karena kebutuhan OO. Ergo, λ-calculus adalah yang tertua dan ...
Jörg W Mittag
... bahasa OO murni!
Jörg W Mittag
1
Hari yang buruk dengan Smalltalk mengalahkan hari yang baik dengan C ++ :-)
Bob Jarvis - Reinstate Monica
@ JörgWMittag Saya tidak berpikir bahwa kesimpulan Anda mengikuti dari asumsi Anda, saya tidak berpikir asumsi Anda bahkan benar, dan saya pasti tidak berpikir kesimpulan Anda benar.
Miles Rout
4

Banyak (kebanyakan?) Struktur data dibangun dari node dan pointer. Array adalah elemen penting dari beberapa struktur data.

Pada akhirnya, setiap struktur data hanya sekumpulan kata dalam memori, atau hanya sekumpulan bit. Begitulah cara mereka disusun dan bagaimana kita menafsirkan dan menggunakannya yang penting.

DW
sumber
2
Pada akhirnya, bit adalah sekumpulan sinyal listrik pada kawat, atau sinyal cahaya pada kabel serat optik, atau partikel bermagnet khusus pada piring, atau gelombang radio dengan panjang gelombang tertentu, atau, atau, atau. Jadi pertanyaannya adalah, seberapa dalam Anda ingin melangkah? :)
Wildcard
2

Implementasi struktur data selalu bermuara pada node dan pointer, ya.

Tapi mengapa berhenti di situ? Implementasi node dan pointer bermuara pada bit.

Implementasi bit bermuara pada sinyal listrik, penyimpanan magnetik, mungkin kabel serat optik, dll. (Singkatnya, fisika.)

Ini adalah reductio ad absurdum dari pernyataan, "Semua struktur data mendidih menjadi node dan pointer." Memang benar — tetapi ini hanya terkait dengan implementasi.


Chris Date sangat mampu membedakan antara implementasi dan model , meskipun esainya ditujukan untuk database pada khususnya.

Kita dapat melangkah sedikit lebih jauh jika kita menyadari bahwa tidak ada garis pemisah tunggal antara model dan implementasi. Ini mirip (jika tidak identik) dengan konsep "lapisan abstraksi."

Pada lapisan abstraksi tertentu, lapisan "di bawah" Anda (lapisan tempat Anda membangun) hanyalah "detail implementasi" untuk abstraksi atau model yang Anda tangani.

Namun, lapisan bawah abstraksi sendiri memiliki detail implementasi.

Jika Anda membaca manual untuk perangkat lunak, Anda belajar tentang lapisan abstraksi yang "disajikan" oleh perangkat lunak tersebut, di mana Anda dapat membuat abstraksi sendiri (atau hanya melakukan tindakan seperti mengirim pesan).

Jika Anda mempelajari detail implementasi perangkat lunak, Anda akan belajar bagaimana pembuatnya mendukung abstraksi yang mereka buat. "Detail implementasi" mungkin termasuk struktur data dan algoritma, antara lain.

Namun, Anda tidak akan menganggap pengukuran tegangan sebagai bagian dari "detail implementasi" untuk setiap bagian perangkat lunak tertentu, meskipun ini mendasari bagaimana "bit" dan "byte" dan "penyimpanan" benar-benar bekerja pada komputer fisik.

Singkatnya, struktur data adalah lapisan abstraksi untuk alasan tentang dan menerapkan algoritma dan perangkat lunak. Fakta bahwa lapisan abstraksi ini dibangun di atas detail implementasi tingkat yang lebih rendah seperti node dan pointer adalah benar tetapi tidak relevan dalam lapisan abstraksi.


Sebagian besar pemahaman sistem adalah memahami bagaimana lapisan abstraksi cocok bersama. Jadi, penting untuk memahami bagaimana struktur data diimplementasikan. Tapi fakta bahwa mereka sedang dilaksanakan, tidak berarti bahwa abstraksi dari struktur data tidak ada.

Wildcard
sumber
2

Array atau vektor hanyalah urutan nilai. Mereka pasti dapat diimplementasikan dengan daftar tertaut. Ini hanya sekelompok node dengan pointer ke node berikutnya.

Array atau vektor dapat diimplementasikan dengan daftar tertaut, tetapi hampir tidak pernah seharusnya.

nnΘ(n)Θ(logn)Θ(1)(yaitu blok berurutan dari memori akses acak). Juga, pada CPU, mengakses array aktual jauh lebih mudah untuk diimplementasikan dan lebih cepat untuk dieksekusi, dan menyimpannya membutuhkan lebih sedikit memori karena tidak perlu membuang ruang pada pointer di antara node yang terpisah.

Θ(n)Θ(1)Θ(1)rata-rata, dengan biaya paling banyak faktor konstan memori tambahan, hanya dengan membulatkan ukuran sebenarnya yang dialokasikan array hingga misalnya kekuatan terdekat dari 2. Tetapi jika Anda perlu melakukan banyak penyisipan dan / atau pemindahan elemen di tengah daftar Anda, array fisik mungkin bukan implementasi terbaik untuk struktur data Anda. Namun, cukup sering, Anda dapat mengganti insersi dan kepindahan dengan swap, yang murah.

Jika Anda sedikit memperluas ruang lingkup Anda, untuk memasukkan array yang berdekatan secara fisik dalam kotak peralatan Anda, hampir semua struktur data praktis memang dapat diimplementasikan dengan yang bersama-sama dengan node dan pointer.

Θ(1)operasi pembalikan). Namun dalam praktiknya, fitur-fitur tersebut jarang cukup berguna untuk mengatasi kekurangannya, yang mencakup kompleksitas implementasi tambahan dan ketidakcocokan dengan skema pengumpulan sampah standar .

Ilmari Karonen
sumber
1

Jika sesederhana ini, mengapa buku teks tidak menjelaskan bahwa data hanyalah sekelompok node dengan pointer?

Karena bukan itu yang dimaksud dengan "data". Anda menggabungkan ide-ide abstrak dengan implementasi. "Data" adalah ide yang sangat abstrak: Itu hanya nama lain untuk "informasi." Sekelompok node terkait dengan pointer (alias, "struktur data terkait") adalah ide yang jauh lebih konkret: Ini adalah cara spesifik untuk mewakili dan mengatur informasi.

Beberapa abstraksi data cocok untuk implementasi "yang terhubung". Tidak ada banyak cara yang baik untuk mengimplementasikan sifat percabangan dari pohon yang sepenuhnya umum tanpa menggunakan node dan pointer eksplisit (atau, beberapa isomorfisma node dan pointer.) Tetapi kemudian, ada abstraksi lain yang Anda tidak akan pernah menerapkan menggunakan node dan pointer. Angka-angka titik mengambang muncul di pikiran.

Tumpukan dan antrian jatuh di antara keduanya. Ada kalanya sangat masuk akal untuk melakukan implementasi stack yang tertaut. Ada saat-saat lain yang jauh lebih masuk akal untuk menggunakan array dan "stack pointer" tunggal.

Solomon Lambat
sumber