Có phải mọi loại dữ liệu chỉ cần đun sôi xuống các nút với con trỏ?


21

Một mảng hoặc vector chỉ là một chuỗi các giá trị. Họ chắc chắn có thể được thực hiện với một danh sách liên kết. Đây chỉ là một loạt các nút có con trỏ đến nút tiếp theo.

Ngăn xếp và hàng đợi là hai loại dữ liệu trừu tượng thường được dạy trong các khóa học Intro CS. Ở đâu đó trong lớp, sinh viên thường phải thực hiện các ngăn xếp và hàng đợi bằng cách sử dụng danh sách được liên kết làm cấu trúc dữ liệu cơ bản, điều đó có nghĩa là chúng tôi quay lại cùng một ý tưởng "tập hợp các nút".

Hàng đợi ưu tiên có thể được tạo bằng Heap. Một đống có thể được coi là một cây với giá trị tối thiểu ở gốc. Cây đủ loại, bao gồm BST, AVL, đống có thể được coi là một tập hợp các nút được kết nối bởi các cạnh. Các nút này được liên kết với nhau trong đó một nút trỏ đến một nút khác.

Dường như mọi khái niệm dữ liệu luôn có thể sôi sục chỉ với các nút có con trỏ đến một số nút thích hợp khác. Có đúng không? Nếu nó đơn giản, tại sao sách giáo khoa không giải thích rằng dữ liệu chỉ là một loạt các nút có con trỏ? Làm thế nào để chúng ta đi từ các nút để mã nhị phân?


5
Cấu trúc dữ liệu cơ bản mà bạn đang ám chỉ được gọi là "tế bào khuyết điểm"; bạn có thể xây dựng bất kỳ cấu trúc dữ liệu nào bạn thích từ chúng. Nếu bạn muốn biết lý do tại sao một tác giả sách giáo khoa nhất định không chọn giải thích các ô khuyết điểm, hãy hỏi tác giả đó tại sao họ lại đưa ra lựa chọn đó. Để đi từ một mô tả về sự sắp xếp các nút thành mã nhị phân được gọi là "biên dịch" và là nhiệm vụ của "trình biên dịch".
Eric Lippert

18
Bạn cũng có thể tranh luận tất cả các cấu trúc dữ liệu đun sôi thành một mảng. Rốt cuộc, tất cả đều kết thúc trong bộ nhớ, đó chỉ là một mảng rất lớn.
BlueRaja - Daniel Pflughoeft

10
Bạn không thể triển khai một mảng bằng danh sách được liên kết nếu bạn muốn tiếp tục lập chỉ mục . Ôi(1)
Svick

5
Xin lỗi, nhưng nói về "các nút và con trỏ" có nghĩa là bạn đã chịu thua ăn quiche. " Như tất cả các lập trình viên thực đều biết, cấu trúc dữ liệu hữu ích duy nhất là Mảng. Chuỗi, danh sách, cấu trúc, bộ - đây đều là những trường hợp đặc biệt của mảng và có thể được xử lý theo cách đó một cách dễ dàng mà không làm rối ngôn ngữ lập trình của bạn với tất cả các loại của các biến chứng. "Tham khảo:" Các lập trình viên thực sự không sử dụng Pascal ", từ web.mit.edu/humor/Computers/real.programmers
alephzero

3
... nhưng nghiêm túc hơn, điều quan trọng về cấu trúc dữ liệu là những gì bạn có thể làm với chúng , chứ không phải cách chúng được thực hiện. Trong thế kỷ 21, việc tự mình thực hiện chúng chỉ là một bài tập lập trình - và đối với những nhà giáo dục lười biếng, thực tế là những bài tập như vậy dễ dàng vượt trội hơn thực tế là chúng vô nghĩa nhất, và có hại nhất là tích cực nếu chúng khuyến khích học sinh nghĩ rằng " phát minh lại bánh xe "là một hoạt động hữu ích trong lập trình thế giới thực.
alephzero

Câu trả lời:


14

Vâng, đó là cơ bản tất cả các cấu trúc dữ liệu sôi lên. Dữ liệu với các kết nối. Các nút đều là nhân tạo - chúng không thực sự tồn tại về mặt vật lý. Đây là nơi phần nhị phân xuất hiện. Bạn nên tạo một vài cấu trúc dữ liệu trong C ++ và kiểm tra xem các đối tượng của bạn nằm ở đâu trong bộ nhớ. Có thể rất thú vị để tìm hiểu về cách dữ liệu được trình bày trong bộ nhớ.

Lý do chính cho rất nhiều cấu trúc khác nhau là tất cả chúng chuyên về một thứ này hay thứ khác. Ví dụ, thông thường nhanh hơn để lặp qua một vectơ thay vì danh sách được liên kết, do cách các trang được kéo từ bộ nhớ. Một danh sách được liên kết là tốt hơn để lưu trữ các loại có kích thước lớn hơn vì các vectơ phải phân bổ thêm không gian cho các vị trí không sử dụng (điều này là bắt buộc trong thiết kế của một vectơ).

Là một lưu ý phụ, một cấu trúc dữ liệu thú vị mà bạn có thể muốn xem xét là Bảng Hash. Nó không hoàn toàn theo hệ thống Nút và Con trỏ mà bạn đang mô tả.

TL; DR: Container về cơ bản tất cả các Nút và Con trỏ nhưng có cách sử dụng rất cụ thể và tốt hơn cho một cái gì đó và tồi tệ hơn cho những thứ khác.


1
Điều đáng nói của tôi là hầu hết dữ liệu thực sự có thể được biểu diễn dưới dạng một loạt các nút với con trỏ. Tuy nhiên, chúng không phải vì (a) ở cấp độ vật lý, đó không phải là những gì xảy ra và (b) ở cấp độ khái niệm, nghĩ rằng các giá trị như một danh sách được liên kết không hữu ích cho lý do về dữ liệu cơ bản. Tất cả chỉ là trừu tượng để đơn giản hóa suy nghĩ của chúng ta, vì vậy cũng có thể chọn cách trừu tượng tốt nhất cho một tình huống ngay cả khi một người khác có thể làm việc.
derekchen14

13

Dường như mọi khái niệm dữ liệu luôn có thể sôi sục chỉ với các nút có con trỏ đến một số nút thích hợp khác.

Ôi, không. Anh đang làm em đau.

Giống như tôi đã cố gắng giải thích ở nơi khác (" Sự khác biệt giữa cây tìm kiếm nhị phân và đống nhị phân là gì? ") Ngay cả đối với cấu trúc dữ liệu cố định, có một số cấp độ để hiểu nó.

Giống như hàng đợi ưu tiên mà bạn đề cập, nếu bạn chỉ muốn sử dụng nó, thì đó là một kiểu dữ liệu trừu tượng. Bạn sử dụng nó để biết loại đối tượng mà nó lưu trữ và những câu hỏi bạn có thể yêu cầu nó làm. Đó là cấu trúc dữ liệu nhiều hơn như một túi các mặt hàng. Ở cấp độ tiếp theo, việc thực hiện nổi tiếng của nó, heap nhị phân, có thể được hiểu là cây nhị phân, nhưng cấp độ cuối cùng là vì lý do hiệu quả được thực hiện như một mảng. Không có nút và con trỏ ở đó.

Và đối với các biểu đồ, ví dụ, chắc chắn trông giống như các nút và con trỏ (các cạnh), bạn có hai biểu diễn cơ bản, mảng kề và danh sách kề. Không phải tất cả con trỏ tôi tưởng tượng.

Khi thực sự cố gắng để hiểu cấu trúc dữ liệu, bạn phải nghiên cứu điểm tốt và điểm yếu của họ. Đôi khi một đại diện sử dụng một mảng cho hiệu quả (không gian hoặc thời gian) đôi khi có con trỏ (cho tính linh hoạt). Điều này đúng ngay cả khi bạn có các triển khai "đóng hộp" tốt như C ++ STL, bởi vì ở đó bạn cũng có thể chọn đôi khi các biểu diễn cơ bản. Luôn có một sự đánh đổi ở đó.


10

Chúng ta hãy làm tương tự với toán học. Hãy xem xét câu sau: " là một hàm liên tục". Các hàm thực sự được định nghĩa theo các quan hệ, được định nghĩa theo các tập hợp. Tập hợp các số thực là trường hoàn toàn theo thứ tự hoàn toàn: tất cả các khái niệm này có định nghĩa theo các thuật ngữ đơn giản hơn. Để nói về tính liên tục, bạn cần có khái niệm về vùng lân cận, được xác định liên quan đến cấu trúc liên kết ... và cứ tiếp tục đi đến các tiên đề của ZFC.f:RR

Không ai mong bạn nói tất cả những điều đó chỉ để xác định một hàm liên tục, nếu không thì không ai có thể hoàn thành bất kỳ công việc nào cả. Chúng tôi chỉ "tin tưởng" rằng ai đó đã tạo ra công việc nhàm chán cho chúng tôi.

Mọi cấu trúc dữ liệu bạn có thể nghĩ đến sẽ tập trung vào các đối tượng cơ bản mà mô hình tính toán cơ bản của bạn xử lý, số nguyên trong một số thanh ghi nếu bạn sử dụng máy truy cập ngẫu nhiên hoặc ký hiệu trên một số băng nếu bạn sử dụng máy Turing.

Chúng tôi sử dụng trừu tượng bởi vì chúng giải phóng tâm trí của chúng tôi khỏi những vấn đề tầm thường, cho phép chúng tôi nói về những vấn đề phức tạp hơn. Hoàn toàn hợp lý khi chỉ "tin tưởng" rằng các cấu trúc đó hoạt động: xoắn ốc vào từng chi tiết duy nhất là - trừ khi bạn có một lý do cụ thể để làm điều đó - một bài tập vô ích không thêm bất cứ điều gì vào lập luận của bạn.


10

Đây là một ví dụ ngược lại: trong tính toán, mọi kiểu dữ liệu đều tập trung vào các hàm. calcul-tính toán không có nút hoặc con trỏ, điều duy nhất nó có là các hàm, do đó mọi thứ phải được thực hiện bằng các hàm.

Đây là một ví dụ về mã hóa booleans dưới dạng hàm, được viết bằng 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)();

Và đây là danh sách khuyết điểm:

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

Số tự nhiên có thể được thực hiện như các hàm lặp.

Một tập hợp là điều tương tự như hàm đặc trưng của nó (tức là containsphương thức).

Lưu ý rằng Mã hóa Giáo hội Booleans ở trên thực ra là cách các điều kiện được triển khai trong các ngôn ngữ OO như Smalltalk, không có booleans, điều kiện hoặc vòng lặp khi xây dựng ngôn ngữ và thực hiện chúng hoàn toàn như một tính năng của thư viện. Một ví dụ trong 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
@Rouletteiffic: Hãy thử điều này: đó thực sự là cách các điều kiện được triển khai trong các ngôn ngữ OO như Smalltalk. Smalltalk không có booleans, điều kiện hoặc vòng lặp như một cấu trúc ngôn ngữ. Tất cả những thứ đó được thực hiện hoàn toàn như các thư viện. Tâm vẫn không thổi? William Cook đã chỉ ra một điều đáng lẽ đã rõ ràng từ lâu nhưng không thực sự được chú ý: vì OO hoàn toàn là về sự trừu tượng hóa hành vi, và sự trừu tượng hóa hành vi là loại trừu tượng duy nhất tồn tại trong-tính toán, nó tuân theo tất cả các chương trình được viết trong calcul-tính toán là do OO cần thiết. Ergo,-compus là người già nhất và
Mạnh

Ngôn ngữ OO tinh khiết nhất!
Jörg W Mittag

1
Một ngày tồi tệ với Smalltalk đánh bại một ngày tốt lành với C ++ :-)
Bob Jarvis - Tái lập lại

@ JörgWMittag Tôi không nghĩ rằng kết luận của bạn xuất phát từ giả định của bạn, tôi không nghĩ giả định của bạn là đúng và tôi chắc chắn không nghĩ kết luận của bạn là đúng.
Miles Rout

4

Nhiều cấu trúc dữ liệu (hầu hết?) Được xây dựng các nút và con trỏ. Mảng là một yếu tố quan trọng khác của một số cấu trúc dữ liệu.

Cuối cùng, mọi cấu trúc dữ liệu chỉ là một loạt các từ trong bộ nhớ, hoặc chỉ là một bó bit. Đó là cách chúng được cấu trúc và cách chúng tôi diễn giải và sử dụng chúng mới là vấn đề.


2
Cuối cùng, các bit là một loạt các tín hiệu điện trên dây hoặc tín hiệu ánh sáng trong cáp quang hoặc các hạt được từ hóa cụ thể trên đĩa, hoặc sóng vô tuyến có bước sóng cụ thể, hoặc, hoặc, hoặc. Vì vậy, câu hỏi là, bạn muốn đi sâu như thế nào? :)
tự đại diện

2

Việc thực hiện các cấu trúc dữ liệu luôn sôi sùng sục xuống các nút và con trỏ, vâng.

Nhưng tại sao dừng lại ở đó? Thực hiện các nút và con trỏ sôi xuống bit.

Việc thực hiện các bit sôi xuống các tín hiệu điện, lưu trữ từ tính, có lẽ là cáp quang, v.v. (Trong một từ, vật lý.)

Đây là reductio ad absurdum của tuyên bố, "Tất cả các cấu trúc dữ liệu sôi lên với các nút và con trỏ." Đó là sự thật nhưng nó chỉ liên quan đến việc thực hiện.


Chris Date rất có thể phân biệt giữa triển khaimô hình , mặc dù bài tiểu luận của ông nhắm vào cơ sở dữ liệu nói riêng.

Chúng ta có thể đi xa hơn một chút nếu chúng ta nhận ra rằng không có một đường phân chia duy nhất giữa mô hình và việc thực hiện. Điều này tương tự (nếu không giống hệt) với khái niệm "các lớp trừu tượng".

Tại một lớp trừu tượng nhất định, các lớp "bên dưới" bạn (các lớp mà bạn đang xây dựng) chỉ là "chi tiết triển khai" cho trừu tượng hoặc mô hình mà bạn đang xử lý.

Tuy nhiên, bản thân các lớp trừu tượng thấp hơn có chi tiết thực hiện.

Nếu bạn đọc hướng dẫn sử dụng cho một phần mềm, bạn đang tìm hiểu về lớp trừu tượng "được trình bày" bởi phần mềm đó, trên đó bạn có thể xây dựng các bản tóm tắt của riêng mình (hoặc chỉ thực hiện các hành động như gửi tin nhắn).

Nếu bạn tìm hiểu các chi tiết triển khai của phần mềm, bạn sẽ tìm hiểu cách người sáng tạo củng cố nền tảng trừu tượng mà họ xây dựng. "Chi tiết triển khai" có thể bao gồm các cấu trúc dữ liệu và thuật toán, trong số những thứ khác.

Tuy nhiên, bạn sẽ không coi phép đo điện áp là một phần của "chi tiết triển khai" cho bất kỳ phần mềm cụ thể nào, mặc dù điều này làm cơ sở cho cách "bit" và "byte" và "lưu trữ" thực sự hoạt động trên máy tính vật lý.

Tóm lại, cấu trúc dữ liệu là một lớp trừu tượng để suy luận và thực hiện các thuật toán và phần mềm. Thực tế là lớp trừu tượng này được xây dựng trên các chi tiết triển khai ở mức thấp hơn như các nút và con trỏ là đúng nhưng không liên quan trong lớp trừu tượng.


Một phần lớn của việc thực sự hiểu một hệ thống là nắm bắt cách các lớp trừu tượng khớp với nhau. Vì vậy, điều quan trọng là phải hiểu cách cấu trúc dữ liệu được thực hiện. Nhưng thực tế là chúng được thực hiện, không có nghĩa là sự trừu tượng của các cấu trúc dữ liệu không tồn tại.


2

Một mảng hoặc vector chỉ là một chuỗi các giá trị. Họ chắc chắn có thể được thực hiện với một danh sách liên kết. Đây chỉ là một loạt các nút có con trỏ đến nút tiếp theo.

Một mảng hoặc một vectơ có thể được thực hiện với một danh sách được liên kết, nhưng hầu như không bao giờ nên như vậy.

nnΘ(n)Θ(đăng nhậpn)Θ(1)(tức là một khối tuần tự của bộ nhớ truy cập ngẫu nhiên). Ngoài ra, trên CPU, việc truy cập mảng thực tế đơn giản hơn rất nhiều để thực hiện và thực thi nhanh hơn và việc lưu trữ sẽ tốn ít bộ nhớ hơn do không phải lãng phí bất kỳ khoảng trống nào trên các con trỏ giữa các nút riêng biệt.

Θ(n)Θ(1)Θ(1)trung bình, với chi phí tối đa là một yếu tố không đổi của bộ nhớ bổ sung, chỉ bằng cách làm tròn kích thước được phân bổ thực tế của mảng lên tới mức công suất gần nhất bằng 2. Nhưng nếu bạn cần thực hiện nhiều thao tác chèn và / hoặc loại bỏ các phần tử ở giữa danh sách của bạn, một mảng vật lý có thể không phải là triển khai tốt nhất cho cấu trúc dữ liệu của bạn. Mặc dù vậy, khá thường xuyên, bạn có thể thay thế chèn và xóa bằng các giao dịch hoán đổi, giá rẻ.

Nếu bạn mở rộng phạm vi của mình một chút, để bao gồm các mảng liền kề vật lý trong hộp công cụ của bạn, hầu như tất cả các cấu trúc dữ liệu thực tế thực sự có thể được thực hiện với các mảng cùng với các nút và con trỏ.

Θ(1)hoạt động đảo ngược). Tuy nhiên, trên thực tế, các tính năng này hiếm khi đủ hữu ích để khắc phục nhược điểm của nó, bao gồm độ phức tạp triển khai bổ sung và không tương thích với các sơ đồ thu gom rác tiêu chuẩn .


1

Nếu nó đơn giản, tại sao sách giáo khoa không giải thích rằng dữ liệu chỉ là một loạt các nút có con trỏ?

Bởi vì đó không phải là "dữ liệu" nghĩa là gì. Bạn đang kết hợp những ý tưởng trừu tượng với việc thực hiện. "Dữ liệu" là một ý tưởng rất trừu tượng: Nó chỉ là một tên gọi khác của "thông tin". Một loạt các nút được liên kết với các con trỏ (hay còn gọi là "cấu trúc dữ liệu được liên kết") là một ý tưởng cụ thể hơn nhiều: Đó là một cách cụ thể để biểu diễn và sắp xếp thông tin.

Một số tóm tắt dữ liệu cho vay rất tốt cho việc triển khai "được liên kết". Không có nhiều cách tốt để thực hiện tính chất phân nhánh của cây hoàn toàn tổng quát mà không sử dụng các nút và con trỏ rõ ràng (hoặc, một số đẳng cấu của các nút và con trỏ.) Nhưng sau đó, có những trừu tượng khác mà bạn sẽ không bao giờ thực hiện bằng cách sử dụng các nút và con trỏ. Số điểm nổi đến trong tâm trí.

Ngăn xếp và hàng đợi rơi ở đâu đó ở giữa. Có những lúc nó rất có ý nghĩa để thực hiện liên kết của một ngăn xếp. Có những lúc khác, việc sử dụng một mảng và một "con trỏ ngăn xếp" sẽ có ý nghĩa hơn nhiều.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.