Menu
CoddyTech

Heap (binary heap)

Cập nhật lần cuối

Binary heap là một cây nhị phân đầy đủ (complete binary tree) luôn giữ giá trị nhỏ nhất (min-heap) hoặc lớn nhất (max-heap) ở gốc. Mô phỏng này là một min-heap: mỗi nút cha luôn nhỏ hơn hoặc bằng các nút con. Để thêm một giá trị, bạn đặt nó vào vị trí trống tiếp theo, rồi "đẩy nó lên" (sift up), tức là đổi chỗ với nút cha chừng nào nó còn nhỏ hơn, cho đến khi tính chất heap được khôi phục. Nhấn Phát ở trên để xem từng giá trị mới nổi lên đúng vị trí.

Vì heap là cây đầy đủ, nó được lưu gọn trong một mảng: các con của nút i nằm ở 2i+1 và 2i+2. Chèn và lấy ra phần tử nhỏ nhất tốn O(log n) (một đường đi từ gốc đến lá), còn xem phần tử nhỏ nhất chỉ tốn O(1), đúng thứ mà một hàng đợi ưu tiên (priority queue) cần.

Độ phức tạp thời gian và không gian

Thao tácĐộ phức tạpGhi chú
Xem min/max (peek)O(1)Luôn nằm ở gốc
Chèn (push)O(log n)Sift up trên một đường đi
Lấy ra min/maxO(log n)Sift down trên một đường đi
Xây dựng heapO(n)Heapify một lần cho tất cả
Không gianO(n)Lưu bằng mảng, không cần con trỏ

Từng bước (push)

BướcĐiều gì xảy ra
1Thêm giá trị mới vào cuối (lá trống tiếp theo).
2So sánh nó với nút cha.
3Nếu nó nhỏ hơn (min-heap), đổi chỗ để đẩy nó lên.
4Lặp lại cho đến khi nó không còn nhỏ hơn nút cha, hoặc đã lên tới gốc.

Ví dụ minh họa

Xây dựng một min-heap bằng cách push lần lượt từng giá trị của [5, 3, 8, 1, 4]:

PushMảng sau khi sift upThao tác
5[5]Giá trị đầu tiên trở thành gốc.
3[3, 5]3 < cha 5, nên đổi chỗ để đưa nó lên gốc.
8[3, 5, 8]8 > cha 5, nên nó ở lại làm lá.
1[1, 3, 8, 5]1 < cha 5, đổi chỗ; rồi 1 < cha 3, đổi chỗ để lên gốc.
4[1, 3, 8, 5, 4]4 > cha 3, nên nó ở yên; giá trị nhỏ nhất 1 vẫn ở gốc.

Khi nào nên dùng heap

Nên dùng khiTránh dùng khi
Bạn cần lấy phần tử nhỏ nhất hoặc lớn nhất nhiều lần từ một tập dữ liệu luôn thay đổi.Bạn cần tìm một giá trị bất kỳ, không chỉ giá trị cực trị: hãy dùng BST hoặc hash set.
Bạn đang cài đặt hàng đợi ưu tiên cho Dijkstra, A* hoặc một bộ lập lịch.Bạn cần dữ liệu luôn được sắp xếp hoàn toàn ở mọi thời điểm.
Bạn muốn chèn và lấy ra phần tử nhỏ nhất trong O(log n) với bố cục mảng gọn nhẹ.Bạn cần tra cứu hoặc xóa nhanh một phần tử cụ thể (không phải gốc).
Bạn cần gộp một luồng dữ liệu và lấy chúng ra theo độ ưu tiên.Tập dữ liệu rất nhỏ và duyệt tuyến tính vừa đơn giản hơn vừa đủ nhanh.

Code Heap (Priority Queue)

Một cách cài đặt Heap (Priority Queue) gọn gàng, chạy được ngay bằng Python, JavaScript, Java, C++, C. Chọn một ngôn ngữ, sao chép code hoặc mở sẵn trong Coddy Playground.

Code Heap (Priority Queue) bằng Python

Python
1class MinHeap:2    def __init__(self):3        self.data = []4
5    def push(self, value):6        # Append at the end, then bubble up to restore order7        self.data.append(value)8        i = len(self.data) - 19        while i > 0:10            parent = (i - 1) // 211            if self.data[parent] <= self.data[i]:12                break13            self.data[i], self.data[parent] = self.data[parent], self.data[i]14            i = parent15
16    def pop(self):17        # Move the last leaf to the root, then sift it down18        top = self.data[0]19        last = self.data.pop()20        if self.data:21            self.data[0] = last22            self._sift_down(0)23        return top24
25    def _sift_down(self, i):26        n = len(self.data)27        while True:28            smallest = i29            left, right = 2 * i + 1, 2 * i + 230            if left < n and self.data[left] < self.data[smallest]:31                smallest = left32            if right < n and self.data[right] < self.data[smallest]:33                smallest = right34            if smallest == i:35                return36            self.data[i], self.data[smallest] = self.data[smallest], self.data[i]37            i = smallest38
39
40heap = MinHeap()41for value in [5, 3, 8, 1, 9, 2]:42    heap.push(value)43
44print("Heap array:     ", heap.data)45print("Popped in order:", [heap.pop() for _ in range(6)])
Chạy code này trong Playground Python

Câu hỏi thường gặp về heap

Heap dùng để làm gì?

Heap dùng để cài đặt hàng đợi ưu tiên, nền tảng của thuật toán đường đi ngắn nhất Dijkstra, các bộ lập lịch tác vụ và mô phỏng sự kiện. Heap cũng là động cơ của heapsort. Bất cứ khi nào bạn cần lấy phần tử nhỏ nhất hoặc lớn nhất nhiều lần từ một tập dữ liệu luôn thay đổi, heap là công cụ phù hợp.

Heap khác cây tìm kiếm nhị phân như thế nào?

Cả hai đều là cây nhị phân, nhưng BST giữ thứ tự sắp xếp đầy đủ từ trái sang phải (cho phép tìm kiếm có thứ tự), còn heap chỉ bảo đảm quan hệ cha con (min hoặc max ở gốc). Heap cho phép truy cập giá trị cực trị trong O(1); BST cho phép tìm bất kỳ giá trị nào trong O(log n).

Tại sao heap được lưu trong mảng?
Vì heap luôn là cây nhị phân đầy đủ, các nút của nó ánh xạ hoàn hảo vào chỉ số mảng: các con của chỉ số i nằm ở 2i+1 và 2i+2, còn cha nằm ở (i-1)/2. Cách này không cần lưu con trỏ đến nút con và tận dụng bộ nhớ đệm (cache) rất tốt.
Heap có giống mảng đã sắp xếp không?
Không. Heap chỉ bảo đảm mỗi nút cha nhỏ hơn (min-heap) hoặc lớn hơn (max-heap) các nút con, nên các nút anh em và họ hàng không theo thứ tự nào cả. Mảng đã sắp xếp có thứ tự hoàn toàn nhưng chèn tốn O(n), trong khi heap chèn trong O(log n) mà vẫn cho truy cập ngay giá trị cực trị.
Khi nào nên dùng heap thay vì chỉ sắp xếp mảng?
Hãy dùng heap khi dữ liệu liên tục thay đổi và bạn chỉ cần giá trị nhỏ nhất hoặc lớn nhất hiện tại: mỗi lần chèn và lấy ra tốn O(log n), thay vì sắp xếp lại cả mảng. Nếu dữ liệu cố định và bạn cần mọi phần tử theo thứ tự, một lần sắp xếp O(n log n) đơn giản hơn và thường nhanh hơn.
Xây dựng heap từ n phần tử có tốn O(n log n) không?
Không, nếu bạn xây dựng tất cả cùng lúc. Chèn n phần tử từng cái một tốn O(n log n), nhưng heapify từ dưới lên, tức là sift down từ nút cha cuối cùng đến gốc, chỉ tốn O(n) tổng cộng, vì phần lớn các nút nằm gần đáy và chỉ phải đi xuống một quãng ngắn.
Hình minh họa các ngôn ngữ lập trình của Coddy

Thành thạo thuật toán cùng Coddy

Bắt đầu