Hai cái này có vẻ rất giống nhau và có cấu trúc gần như giống hệt nhau. Có gì khác biệt? Sự phức tạp thời gian cho các hoạt động khác nhau của mỗi là gì?
Hai cái này có vẻ rất giống nhau và có cấu trúc gần như giống hệt nhau. Có gì khác biệt? Sự phức tạp thời gian cho các hoạt động khác nhau của mỗi là gì?
Câu trả lời:
Heap chỉ đảm bảo rằng các phần tử ở mức cao hơn lớn hơn (đối với heap tối đa) hoặc nhỏ hơn (đối với heap tối thiểu) so với các phần tử ở mức thấp hơn, trong khi BST đảm bảo thứ tự (từ "trái" sang "phải"). Nếu bạn muốn các yếu tố được sắp xếp, đi với BST. bởi Dante không phải là một người đam mê
Heap tốt hơn ở findMin / findMax (O (1)), trong khi BST tốt ở tất cả các kết quả tìm thấy (O (logN)). Chèn là O (logN) cho cả hai cấu trúc. Nếu bạn chỉ quan tâm đến findMin / findMax (ví dụ: liên quan đến ưu tiên), hãy đi với heap. Nếu bạn muốn mọi thứ được sắp xếp, hãy đi với BST.
Cả cây tìm kiếm nhị phân và đống nhị phân đều là cấu trúc dữ liệu dựa trên cây.
Heaps yêu cầu các nút phải có một ưu tiên hơn con cái của họ. Trong một đống tối đa, con của mỗi nút phải nhỏ hơn chính nó. Điều này ngược lại với một đống tối thiểu:
Cây tìm kiếm nhị phân (BST) tuân theo một thứ tự cụ thể (đặt hàng trước, theo thứ tự, sau đặt hàng) giữa các nút anh chị em. Cây phải được sắp xếp, không giống như đống:
Tóm lược
Type BST (*) Heap
Insert average log(n) 1
Insert worst log(n) log(n) or n (***)
Find any worst log(n) n
Find max worst 1 (**) 1
Create worst n log(n) n
Delete worst log(n) log(n)
Tất cả thời gian trung bình trên bảng này giống như thời gian tồi tệ nhất của chúng ngoại trừ Chèn.
*
: ở mọi nơi trong câu trả lời này, BST == BST cân bằng, vì không cân bằng hút không có triệu chứng**
: sử dụng một sửa đổi tầm thường được giải thích trong câu trả lời này***
: log(n)
cho heap cây con trỏ, n
cho heap mảng độngƯu điểm của heap nhị phân so với BST
thời gian trung bình chèn vào một đống nhị phân là O(1)
, đối với BST là O(log(n))
. Đây là tính năng sát thủ của đống.
Ngoài ra còn có các đống khác đạt được O(1)
khấu hao (mạnh hơn) như Heap Fibros , và thậm chí trường hợp xấu nhất, như hàng đợi Brodal , mặc dù chúng có thể không thực tế vì hiệu suất không có triệu chứng: https://stackoverflow.com/questions/30782636 / are-Dailymotion-heaps-or-brodal-queues-used-in-Practice-where
đống nhị phân có thể được thực hiện một cách hiệu quả trên đầu của các mảng động hoặc cây dựa trên con trỏ, cây chỉ dựa trên BST. Vì vậy, đối với heap, chúng ta có thể chọn triển khai mảng hiệu quả hơn về không gian, nếu chúng ta có thể đủ khả năng thay đổi kích thước thời gian trễ.
tạo heap nhị phân là O(n)
trường hợp xấu nhất , O(n log(n))
đối với BST.
Lợi thế của BST so với đống nhị phân
tìm kiếm các yếu tố tùy ý là O(log(n))
. Đây là tính năng sát thủ của BST.
Đối với heap, nó O(n)
nói chung, ngoại trừ phần tử lớn nhất O(1)
.
Lợi thế "sai" của heap so với BST
Heap là O(1)
để tìm max, BST O(log(n))
.
Đây là một quan niệm sai lầm phổ biến, bởi vì việc sửa đổi BST để theo dõi phần tử lớn nhất và cập nhật nó bất cứ khi nào phần tử đó có thể được thay đổi: khi chèn một hoán đổi lớn hơn, khi loại bỏ tìm phần lớn thứ hai. https://stackoverflow.com/questions/7878622/can-we-use-binary-search-tree-to-simulation-heap-operation (được đề cập bởi Yeo ).
Trên thực tế, đây là một hạn chế của đống so với các BST: tìm kiếm hiệu quả duy nhất là cho phần tử lớn nhất.
Chèn heap nhị phân trung bình là O(1)
Nguồn:
Lập luận trực quan:
Trong một đống nhị phân, việc tăng giá trị tại một chỉ mục nhất định cũng O(1)
vì lý do tương tự. Nhưng nếu bạn muốn làm điều đó, có khả năng bạn sẽ muốn cập nhật thêm một chỉ mục cho các hoạt động heap https://stackoverflow.com/questions/17009056/how-to-im vây-ologn-decreas- key-oper-for-min-heap-dựa-ưu tiên-queu, ví dụ cho Dijkstra. Có thể không có chi phí thêm thời gian.
Thư viện chuẩn GCC C ++ chèn điểm chuẩn trên phần cứng thực
Tôi đã điểm chuẩn C ++ std::set
( BST cây đen đỏ ) và chèn std::priority_queue
( heap mảng động ) để xem liệu tôi có đúng về thời gian chèn hay không và đây là những gì tôi nhận được:
Vì vậy, rõ ràng:
thời gian chèn heap về cơ bản là không đổi.
Chúng ta có thể thấy rõ các điểm thay đổi kích thước mảng động. Vì chúng tôi đang tính trung bình cứ sau 10k chèn để có thể nhìn thấy mọi thứ ở mức nhiễu trên hệ thống , nên các đỉnh đó thực tế lớn hơn khoảng 10 nghìn lần so với hiển thị!
Biểu đồ thu phóng loại trừ về cơ bản chỉ các điểm thay đổi kích thước mảng và cho thấy hầu hết tất cả các phần chèn đều nằm dưới 25 nano giây.
BST là logarit. Tất cả các chèn đều chậm hơn nhiều so với chèn heap trung bình.
Phân tích chi tiết BST vs hashmap tại: https://stackoverflow.com/questions/18414579/what-data-structure-is-inside-stdmap-in-c/51945119#51945119
Thư viện chuẩn GCC C ++ chèn điểm chuẩn trên gem5
gem5 là một trình giả lập hệ thống đầy đủ, và do đó cung cấp một đồng hồ chính xác vô cùng với m5 dumpstats
. Vì vậy, tôi đã cố gắng sử dụng nó để ước tính thời gian cho các lần chèn riêng lẻ.
Diễn dịch:
Heap vẫn không đổi, nhưng bây giờ chúng ta thấy chi tiết hơn rằng có một vài dòng, và mỗi dòng cao hơn thì thưa thớt hơn.
Điều này phải tương ứng với độ trễ truy cập bộ nhớ được thực hiện cho các chèn cao hơn và cao hơn.
TODO Tôi thực sự không thể diễn giải BST đầy đủ vì nó trông không quá logarit và có phần bất biến hơn.
Tuy nhiên, với chi tiết lớn hơn này, chúng ta có thể thấy cũng có thể thấy một vài dòng riêng biệt, nhưng tôi không chắc chúng đại diện cho cái gì: Tôi có thể mong đợi dòng dưới sẽ mỏng hơn, vì chúng ta chèn dưới cùng?
Điểm chuẩn với thiết lập Buildroot này trên CPU aarch64 HPI .
BST không thể được thực hiện hiệu quả trên một mảng
Hoạt động heap chỉ cần bong bóng lên hoặc xuống một nhánh cây, vì vậy O(log(n))
trường hợp xấu nhất là hoán đổi, O(1)
trung bình.
Giữ BST cân bằng đòi hỏi phải xoay cây, có thể thay đổi phần tử trên cùng cho phần tử khác và sẽ yêu cầu di chuyển toàn bộ mảng xung quanh ( O(n)
).
Heaps có thể được thực hiện một cách hiệu quả trên một mảng
Chỉ số phụ huynh và trẻ em có thể được tính toán từ chỉ số hiện tại như được hiển thị ở đây .
Không có hoạt động cân bằng như BST.
Xóa min là thao tác đáng lo ngại nhất vì nó phải từ trên xuống. Nhưng nó luôn có thể được thực hiện bằng cách "tô màu xuống" một nhánh của đống như được giải thích ở đây . Điều này dẫn đến trường hợp xấu nhất O (log (n)), vì heap luôn được cân bằng tốt.
Nếu bạn đang chèn một nút duy nhất cho mỗi nút bạn xóa, thì bạn sẽ mất lợi thế của chèn trung bình O (1) không triệu chứng mà heaps cung cấp khi việc xóa sẽ chiếm ưu thế và bạn cũng có thể sử dụng BST. Tuy nhiên, Dijkstra cập nhật các nút nhiều lần cho mỗi lần xóa, vì vậy chúng tôi vẫn ổn.
Heap mảng động so với đống cây con trỏ
Heaps có thể được triển khai một cách hiệu quả trên đầu heap con trỏ: https://stackoverflow.com/questions/19720438/is-it-possible-to-make-ffic-pulum-basing-binary-heap-im THỰCations
Việc thực hiện mảng động là không gian hiệu quả hơn. Giả sử rằng mỗi phần tử heap chỉ chứa một con trỏ tới struct
:
việc thực hiện cây phải lưu trữ ba con trỏ cho mỗi phần tử: cha mẹ, con trái và con phải. Vì vậy, việc sử dụng bộ nhớ luôn luôn là 4n
(3 con trỏ cây + 1 struct
con trỏ).
Các BST cây cũng sẽ cần thêm thông tin cân bằng, ví dụ như màu đen-đỏ.
việc thực hiện mảng động có thể có kích thước 2n
chỉ sau khi nhân đôi. Vì vậy, trung bình nó sẽ được 1.5n
.
Mặt khác, heap cây có chèn trường hợp xấu nhất tốt hơn, bởi vì sao chép mảng động sao lưu để tăng gấp đôi kích thước của nó thì O(n)
trường hợp xấu nhất, trong khi đó, heap cây chỉ thực hiện phân bổ nhỏ mới cho mỗi nút.
Tuy nhiên, nhân đôi mảng sao lưu được O(1)
khấu hao, do đó, nó được xem xét đến độ trễ tối đa. Đề cập ở đây .
Triết học
Các BST duy trì một tài sản toàn cầu giữa cha mẹ và tất cả con cháu (trái nhỏ hơn, phải lớn hơn).
Nút trên cùng của BST là phần tử ở giữa, đòi hỏi kiến thức toàn cầu để duy trì (biết có bao nhiêu phần tử nhỏ hơn và lớn hơn).
Tài sản toàn cầu này đắt hơn để duy trì (log n insert), nhưng cung cấp các tìm kiếm mạnh mẽ hơn (tìm kiếm log n).
Heaps duy trì một tài sản địa phương giữa cha mẹ và con cái trực tiếp (cha mẹ> con cái).
Lưu ý hàng đầu của một đống là yếu tố lớn, chỉ đòi hỏi kiến thức địa phương để duy trì (biết cha mẹ của bạn).
Danh sách liên kết đôi
Một danh sách liên kết đôi có thể được xem là tập hợp con của heap trong đó mục đầu tiên có mức độ ưu tiên cao nhất, vì vậy hãy so sánh chúng ở đây:
O(1)
trường hợp xấu nhất vì chúng tôi có con trỏ tới các mục và việc cập nhật thực sự đơn giảnO(1)
trung bình, do đó tồi tệ hơn danh sách liên kết. Đánh đổi để có vị trí chèn chung hơn.O(n)
cho cả haiMột trường hợp sử dụng cho điều này là khi khóa của heap là dấu thời gian hiện tại: trong trường hợp đó, các mục mới sẽ luôn đi đến đầu danh sách. Vì vậy, chúng ta thậm chí có thể quên hoàn toàn dấu thời gian chính xác và chỉ giữ vị trí trong danh sách là ưu tiên.
Điều này có thể được sử dụng để thực hiện bộ đệm LRU . Giống như đối với các ứng dụng heap như Dijkstra , bạn sẽ muốn giữ một hashmap bổ sung từ khóa đến nút tương ứng của danh sách, để tìm nút nào cần cập nhật nhanh chóng.
So sánh các BST cân bằng khác nhau
Mặc dù thời gian chèn và tìm tiệm cận cho tất cả các cấu trúc dữ liệu thường được phân loại là "BST cân bằng" mà tôi đã thấy cho đến nay là như nhau, các BBST khác nhau có sự đánh đổi khác nhau. Tôi chưa nghiên cứu đầy đủ về vấn đề này, nhưng sẽ rất tốt nếu tóm tắt những sự đánh đổi này ở đây:
Xem thêm
Câu hỏi tương tự trên CS: Sự khác biệt giữa cây tìm kiếm nhị phân và đống nhị phân là gì?
Với cấu trúc dữ liệu người ta phải phân biệt các mức độ quan tâm.
Các cấu trúc dữ liệu trừu tượng (các đối tượng được lưu trữ, hoạt động của chúng) trong câu hỏi này là khác nhau. Một cái thực hiện một hàng đợi ưu tiên, cái còn lại là một bộ. Hàng đợi ưu tiên không quan tâm đến việc tìm phần tử tùy ý, chỉ phần tử có mức ưu tiên lớn nhất.
Việc thực hiện cụ thể của các cấu trúc. Ở đây, thoạt nhìn cả hai đều là cây (nhị phân), với các thuộc tính cấu trúc khác nhau. Cả thứ tự tương đối của các khóa và cấu trúc toàn cầu có thể khác nhau. (Hơi không chính xác, trong một BST
khóa được sắp xếp từ trái sang phải, trong một đống, chúng được sắp xếp từ trên xuống.) Vì IPlant nhận xét chính xác một đống cũng nên "hoàn thành".
Có một sự khác biệt cuối cùng trong việc thực hiện cấp thấp . Cây tìm kiếm nhị phân (không cân bằng) có một triển khai tiêu chuẩn bằng cách sử dụng các con trỏ. Một đống nhị phân ngược lại có một triển khai hiệu quả bằng cách sử dụng một mảng (chính xác là do cấu trúc bị hạn chế).