Heap là một cây nhị phân gắn nhãn với các nhãn là các giá trị thuộc tập hợp đợc sắp thứ tự tuyến tính, sao cho những điều kiện sau đây đợc thực hiện.
1. Tất cả các mức của cây đều đầy, trừ mức thấp nhất có thể thiếu một số đỉnh.
2. ở mức thấp nhất, tất cả các lá đều xuất hiện liên tiếp từ bên trái. 3. Giá trị của mỗi đỉnh không lớn hơn giá trị của các đỉnh con của nó.
Cần chú ý rằng, điều kiện 3 không đảm bảo heap là cây tìm kiếm nhị phân.
Hình 5.3 minh hoạ một số cây nhị phân với các giá trị của các đỉnh là các số nguyên đợc ghi trong mỗi đỉnh. Hình a là một heap. Còn các hình b, c, d không phải là heap. Cây trong b vi phạm điều kiện 1, hình c vi phạm điều kiện 2, còn hình d vi phạm điều kiện 3.
9 5 8 7 9 128 11 10 10 (a) (b) 2 5 4 3 6
(c) (d) Hình 5.3
Chú ý 1) Thuật ngữ heap ở đây là hoàn toàn khác, không liên quan gì đến thuật ngữ heap dùng để chỉ vùng nhớ động. 2) một số tác giả còn gọi heap là cây đợc sắp bộ phận (partially ordered tree).
Khi lấy giá trị u tiên làm giá trị của mỗi đỉnh, ta có thể sử dụng heap để biểu diễn hàng u tiên. Sau đây ta sẽ xét xem các phép toán đối với hàng u tiên đợc thực hiện trên heap nh thế nào.
Phép toán Insert
Để xen một phần tử mới vào heap, đầu tiên ta thêm một lá mới liền kề với các lá ở mức thấp nhất, nếu mức thấp nhất cha đầy; còn nếu mức thấp nhất đầy, thì ta thêm vào một lá ở mức mới sao cho các điều kiện của 1 và 2 của heap đợc bảo tồn. Hình 5.4a minh hoạ cây sau khi thêm một lá mới với giá trị u tiên là 3 vào heap trong hình 5.3 a. Tất nhiên là cây nhị phân trong hình 5.4 a nói chung không còn là heap, vì điều kiện 3 có thể bị vi phạm. Nếu sau khi thêm vào lá mới cây không còn là heap, thì ta theo đờng từ lá mới tới gốc cây. Nếu một đỉnh có giá trị u tiên nhỏ hơn đỉnh cha của nó, thì ta trao đổi đỉnh đó với cha của nó. Quá trình này đợc minh hoạ trong hình 5.4 b và c. Dễ dàng chứng minh đợc rằng, sau quá trình biến đổi trên ta có một heap (bài tập). 2 5 8 7 9 12 8 11 10 10 3 (a)
2 5 8 7 3 12 8 11 10 10 9 (b) 2 3 8 7 5 12 8 11 10 10 9 (c) Hình 5.4 Phép toán DeleteMin
Hiển nhiên là gốc của cây có giá trị u tiên nhỏ nhất. Tuy nhiên nếu loại bỏ gốc thì cây không còn là cây nữa. Do đó ta tiến hành nh sau : đặt vào gốc phần tử của hàng u tiên chứa trong lá ngoài cùng bên phải ở mức thống nhất, sau đó loại bỏ lá này khỏi cây. Hình 5.5 a minh hoạ cây nhận đợc từ cây trong hình 5.3 a sau phép biến đổi trên. Tới đây cây có thể không còn là heap, do điều kiện 3 của định nghĩa heap bị vi phạm ở gốc cây. Bây giờ ta đi từ gốc xuống. Giả sử tại một bớc nào đó ta đang ở đỉnh a và hai đỉnh con của nó là b và c. Giả sử đỉnh a có giá trị u tiên lớn hơn giá trị u tiên của ít nhất một trong hai đỉnh b và c. Để xác định, ta giả sử rằng giá trị u tiên của đỉnh b không lớn hơn giá trị u tiên của đỉnh c, pri (b) ≤ pri (c). Khi đó ta sẽ trao đổi đỉnh a với đỉnh b và đi xuống đỉnh b. Quá trình đi xuống sẽ dừng lại cùng lắm là khi ta
đạt tới một lá của cây. Ta có thể thấy quá trình diễn ra nh thế nào trong hình 5.5 b và c. 10 5 8 7 9 12 8 11 10 (a) 5 10 8 7 9 12 8 11 10 (b) 5 7 8 10 9 12 8 11 10 (c)
Bây giờ ta thử đánh giá thời gian cần thiết để thực hiện các phép toán Insert và DeleteMin đối với hàng u tiên đợc biểu diễn bởi heap. Giả sử hàng chứa n phần tử. Khi đó mọi đờng đi trong cây không chứa nhiều hơn 1 + logn đỉnh. Thủ tục thực hiện các phép toán đã trình bày ở trên đều chứa quá trình đó đitừ lá lên gốc hoặc ngợc lại. Trong quá trình mỗi đỉnh đòi hỏi một thời gian không đổi c nào đó. Do đó thời gian để thực hiện mỗi phép toán cùng lắm là c (1 + logn) ≤ 2c logn, với n ≥ 2, tức là 0 (logn).
Cài đặt heap bởi mảng
Giả sử ta đánh số các đỉnh của heap từ trên xuống dới và từ trái sang phải (trong cùng một mức), bắt đầu từ gốc có số hiệu là 1 (xem hình 5.6)
1
2 3
4 5 6 7
8 9 10 11
Hình 5.6
Từ các điều kiện 1 và 2 trong định nghĩa của heap, ta nhận thấy rằng, nếu một đỉnh có số hiệu là i, thì đỉnh con bên trái (nếu có) của nó là 2i, và đỉnh con bên phải (nếu có) của nó là 2i + 1. Hay nói cách khác cha của đỉnh i là đỉnh i div 2 với i > 1. Nếu ta sử dụng mảng H với chỉ số chạy từ 1 đến N (N là số lớn nhất các phần tử có trong hàng u tiên), thì các đỉnh của cây kể từ gốc lần lợt theo các mức và trong cùng một mức đợc kể từ trái sang phải, sẽ chứa trong các thành phần của mảng H [1], H [2], ..., H [n]. Do đó ta sẽ cài đặt hàng u tiên bởi mảng H cùng với một biến last để ghi chỉ số cuối cùng của thành phần mảng có chứa phần tử của hàng. Chúng ta có khai báo sau
const N = ...;
type PriQueue = record
last : integer
end;
{item là kiểu bản ghi nào đó mô tả phần tử của hàng u tiên}. var H : PriQueuen;
Việc khởi tạo một hàng rỗng đợc thực hiện bởi lệnh H.last : = 0
Từ các thuật toán thực hiện các phép toán Insert và DeleteMin đã trình bày, ta dễ dàng viết đợc các thủ tục thực hiện các phép toán trên hàng u tiên.
procedure Insert (x : item; var H : PriQueue);
var i : integer; temp : item;
begin
if H. last > = N then writeln ('hàng đầy') else begin
H. last : = H. last + 1; H. element [H. last] : = x; i : = H. last;
{i ghi lại số hiệu lá mới thêm vào}
while (i > 1) and (Pri(H.element [i]) <
Pri(H.element[i div 2])) do
begin
temp : = H. element [i];
H. element [i] : = H. element [i div 2]; H.element [i div 2] : = temp;
i : = i div 2
end
{vòng lặp while thực hiện quá trình đi từ lá mới lên}
end end;
procedure DeleteMin (var H : PriQueue; var x : item);
{loại bỏ phần tử có giá trị u tiên nhỏ nhất khỏi hàng và phần tử này đ- ợc lu vào biến x}
var i, j : integer;
{i ghi lại số hiệu của các đỉnh trong quá trình đi từ gốc xuống; j ghi lại một trong hai đỉnh con của i có giá trị u tiên nhỏ hơn đỉnh kia}.
temp : item;
Condition3 : boolean;
{biến condition3 chỉ ra điều kiện 3 của heap có thoả mãn hay không}
begin
if H. last = 0 then writeln (' hàng rỗng') else begin
x : = H. element [1];
H. element [1] : = H. element [ H.last]; H. last : = H. last + 1;
i = 1;
condition 3 : = false;
while (i < = H. last div 2) and (not condition 3) do begin
if 2 * i = H. last then j : = 2 * i
else if Pri (H. element [2 * i] < = Pri (H. element [2*i+1]
then j : = 2 *i else j : = 2 * i +1;
if Pri (H.element [i] > Pri (H. element [j] then begin
temp : = H.element [i];
H. element [i] : = H. element [j]; H. element [j] : = temp;
i : = j
end else condition3 : = true;
end; end;
end;
Trong thủ tục trên, vòng lặp while thực hiện quá trình đi từ gốc xuống và dừng lại khi điều kiện 3 đợc thoả mãn.