Sự khác biệt giữa cây tìm kiếm nhị phân và đống nhị phân là gì?


91

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:


63

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.

bởi xysun


Tôi nghĩ BST tốt hơn trong findMin & findMax stackoverflow.com/a/27074221/764592
Yeo

10
Tôi nghĩ rằng đây chỉ là một quan niệm sai lầm phổ biến. Một cây nhị phân có thể dễ dàng sửa đổi để tìm min và max như được chỉ bởi Yeo. Đây thực sự là một hạn chế của heap: tìm thấy hiệu quả duy nhất là tối thiểu hoặc tối đa. Lợi thế thực sự của heap là chèn trung bình O (1) như tôi giải thích: stackoverflow.com/a/29548834/895245
Ciro Santilli 改造 心 心

Theo video này , bạn có thể có các giá trị lớn hơn ở cấp độ thấp hơn, miễn là lớn hơn không phải là hậu duệ của cấp dưới.
whoan

Heap được sắp xếp từ gốc đến lá và BST được sắp xếp từ trái sang phải.
Sâu Joshi

34

Cả cây tìm kiếm nhị phânđố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:

Heap nhị phân tối đa

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:

Cây tìm kiếm nhị phân

Ôi(đăng nhậpviết sai rồi)
Ôi(1)Ôi(đăng nhậpviết sai rồi)


1
Ôi(đăng nhậpviết sai rồi)

32

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ỏ, ncho 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 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:

  • cấp độ cây dưới cùng có nhiều yếu tố theo cấp số nhân hơn cấp độ cao nhất, vì vậy các yếu tố mới gần như chắc chắn sẽ đi ở phía dưới
  • chèn heap bắt đầu từ dưới cùng , BST phải bắt đầu từ đầu

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:

nhập mô tả hình ảnh ở đây

  • mã điểm chuẩn
  • kịch bản cốt truyện
  • dữ liệu cốt truyện
  • đã thử nghiệm trên Ubuntu 19.04, GCC 8.3.0 trên máy tính xách tay Lenovo ThinkPad P51 với CPU: CPU Intel Core i7-7820HQ (4 lõi / 8 luồng, cơ sở 2,90 GHz, bộ nhớ cache 8 MB), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16GiB , 2400 Mbps), SSD: Samsung MZVLB512HAJQ-000L7 (512GB, 3.000 MB / s)

Vì vậy, rõ ràng:

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ẻ.

nhập mô tả hình ảnh ở đây

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 structcon 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 2nchỉ 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:

  • chèn:
    • Chức vụ:
      • danh sách được liên kết đôi: mục được chèn phải là đầu tiên hoặc cuối cùng, vì chúng ta chỉ có các con trỏ tới các phần tử đó.
      • heap binary: mục được chèn có thể kết thúc ở bất kỳ vị trí nào. Ít hạn chế hơn danh sách liên kết.
    • thời gian:
      • danh sách liên kết đôi: 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ản
      • heap binary: O(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.
  • tìm kiếm: O(n)cho cả hai

Mộ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:

  • Cây đỏ đen . Xuất hiện là BBST được sử dụng phổ biến nhất kể từ năm 2019, ví dụ: đây là BBST được sử dụng bởi triển khai GCC 8.3.0 C ++
  • Cây AVL . Có vẻ cân bằng hơn một chút so với BST, vì vậy có thể tốt hơn cho việc tìm độ trễ, với chi phí tìm thấy đắt hơn một chút. Wiki tóm tắt: "Cây AVL thường được so sánh với cây đen Red do cả hai đều hỗ trợ cùng một tập hợp hoạt động và mất [cùng một thời gian] cho các hoạt động cơ bản. Đối với các ứng dụng chuyên sâu tra cứu, cây AVL nhanh hơn cây đen đỏ vì Chúng cân bằng chặt chẽ hơn. Tương tự như cây đen đỏ, cây AVL cân bằng chiều cao. Nhìn chung, cả hai đều không cân bằng trọng lượng hoặc không cân bằng cho bất kỳ mu <1/2; nghĩa là các nút anh chị em có thể có rất nhiều số lượng con cháu khác nhau. "
  • WAVL . Bài viết gốc đề cập đến những lợi thế của phiên bản đó về các giới hạn trong hoạt động tái cân bằng và xoay vòng.

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ì?


1
Câu trả lời chính xác. Ứng dụng phổ biến của heap là các phần tử trung vị, k min, top k. Đối với các hoạt động phổ biến nhất này, hãy loại bỏ min sau đó chèn (thông thường chúng ta có một đống nhỏ với một vài thao tác chèn thuần túy). Vì vậy, có vẻ như trong thực tế, đối với các thuật toán này, nó không vượt trội so với BST.
yura

1
Câu trả lời đặc biệt !!! Bằng cách sử dụng deque làm cấu trúc heap cơ bản, bạn có thể giảm đáng kể thời gian thay đổi kích thước, mặc dù đó vẫn là trường hợp xấu nhất O (n) vì nó cần phải phân bổ lại mảng con trỏ (nhỏ hơn) cho khối.
Bulat

13

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.

  1. 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.

  2. 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 BSTkhó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".

  3. 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ế).


1

Trên các câu trả lời trước, heap phải có thuộc tính cấu trúc heap; cây phải đầy, và lớp dưới cùng, không thể luôn luôn đầy, phải được lấp đầy từ trái sang phải, không có khoảng trống.

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.