Giáo trình Cấu trúc dữ liệu và giải thuật - Chương 11: Hàng ưu tiên
Định nghĩa: Một hàng ưu tiên các phần tử kiểu T gồm các phần tử của T, kèm
các tác vụ sau:
1. Tạo mới một đối tượng hàng rỗng.
2. priority_append: Thêm một phần tử mới vào hàng, giả sử hàng chưa đầy
(tùy vào độ ưu tiên của phần tử dữ liệu mới nó sẽ được đứng ở một vị trí thích
hợp).
3. priority_serve: Loại một phần tử ra khỏi hàng, giả sử hàng chưa rỗng
(phần tử bị loại là phần tử đến lượt được xem xét theo quy ước độ ưu tiên đã
định).
4. priority_retrieve: Xem phần tử tại đầu hàng (phần tử sắp được xem xét).
i cây B2 còn lại thành một cây B3. Vì H3 chưa có cây nhị thức B3 nên B3 cuối cùng này sẽ là thành phần của H3. H1 H2 Hình 11.10- Hai hàng nhị thức H1 và H2. Hình 11.11 - Kết hợp hai cây nhị thức B1 trong H1 và H2. Hình 11.12- Kết quả trộn H1 và H2 thành H3. Việc kết hợp hai cây nhị thức tốn O(1), với O(log N) cây nhị thức trong mỗi hàng nhị thức, việc trộn hai hàng nhị thức cũng tốn O(log N) trong trường hợp xấu nhất. Để tăng tính hiệu quả, chúng ta lưu các cây nhị thức trong mỗi hàng nhị thức theo thứ tự tăng dần của chiều cao các cây. Việc thêm một phần tử mới vào hàng nhị thức tương tự như đối với heap lệch trái: trộn hàng nhị thức có duy nhất phần tử cần thêm với hàng nhị thức đã có. Việc loại phần tử nhỏ nhất cũng đơn giản: tìm phần tử nhỏ nhất trong số các nút gốc của các cây nhị thức. Giả sử đó là cây Bk. Sau khi loại bỏ nút gốc của cây Bk, chúng ta còn lại các cây con của nó: B0, B1, Bk-1. Gọi rừng gồm các cây con này là H’, và H’’ là hàng nhị thức ban đầu không kể BK. Việc cuối cùng cần làm là trộn H’ và H’’. Chúng ta có thể tự kiểm tra rằng các phép thêm và loại này đều tốn O(log N) trong trường hợp xấu nhất. 16 18 12 21 24 65 14 26 23 51 24 65 13 14 26 16 18 23 51 24 65 13 12 21 24 65 14 26 16 18 23 Chương 11 – Hàng ưu tiên Giáo trình Cấu trúc dữ liệu và Giải thuật 298 Hình 11.13- Quá trình thêm các phần tử 1, 2,, 7 vào hàng nhị thức. 1 2 3 4 1 1 2 1 2 1 2 3 1 2 3 1 2 3 4 1 2 3 4 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 7 1 2 3 4 5 6 7 Chương 11 – Hàng ưu tiên Giáo trình Cấu trúc dữ liệu và Giải thuật 299 H H’’ H’ Hình 11.14- Quá trình loại phần tử nhỏ nhất trong hàng nhị thức H. Hiện thực của hàng nhị thức Việc tìm phần tử nhỏ nhất cần duyệt qua các gốc của các cây nhị thức trong hàng nhị thức (12, 23 và 13 trong hình 11.14). Chúng ta có thể dùng danh sách liên kết để chứa các nút gốc này. Danh sách sẽ có thứ tự theo chiều cao của các cây nhị thức để phục vụ cho phép trộn hai hàng nhị thức được dễ dàng. Tương tự, các nút con của nút gốc của một cây nhị thức cũng được chứa trong một danh sách liên kết (14, 24 và 21 trong hình 11.14), để khi loại bỏ nút gốc (nút 12) thì phần còn lại cũng có cấu trúc tương tự như một hàng nhị thức mới, rất thuận lợi trong phép loại phần tử nhỏ nhất trong hàng nhị thức. Hình 11.15- Hàng nhị thức H 23 51 24 65 13 12 21 24 65 14 26 16 18 23 51 24 65 13 21 24 65 14 26 16 18 23 51 24 65 13 21 24 65 14 26 16 18 23 51 24 65 13 12 21 24 65 14 26 16 18 Chương 11 – Hàng ưu tiên Giáo trình Cấu trúc dữ liệu và Giải thuật 300 Hình 11.16- Hiện thực liên kết của hàng nhị thức H. Trong hình vẽ trên chúng ta thấy hình elipse nét rời biểu diễn các danh sách liên kết các nút mà chúng ta thường phải duyệt qua. Hình elipse nét rời lớn chứa các gốc của các cây nhị thức trong hàng nhị thức, khi cần tìm phần tử nhỏ nhất trong hàng nhị thức chúng ta tìm trong danh sách này. Hình elipse nét rời nhỏ chứa các nút con của nút gốc trong một cây nhị thức. Trong quá trình hình thành hàng nhị thức trong một số thao tác dữ liệu, khi kết hợp hai cây nhị thức có cùng chiều cao (hình 11.18), chúng ta cần nối một trong hai cây thành cây con của cây còn lại, mà cây con mới này cũng chính là cây con có chiều cao lớn nhất so với các cây con đã có. Việc chèn cây con mới vào đầu của danh sách liên kết sẽ thuận tiện hơn, vì vậy chúng ta cho danh sách này có thứ tự giảm dần theo chiều cao của các cây con (hình 11.18). Ngoài ra, mỗi nút trong cây nhị thức sẽ có một con trỏ cho phép truy xuất đến danh sách các con của nó. Tóm lại, hiện thực đơn giản và hiệu quả cho hàng nhị thức thật đơn giản như hình 11.18, đó là cấu trúc của một cây nhị phân, mỗi nút có con trỏ trái chỉ đến nút con đầu tiên của nó và con trỏ phải chỉ đến nút anh em của nó. Hình 11.17- Gốc các cây nhị thức được chứa trong mảng liên tục. Hình 11.17 là một phương án thay danh sách liên kết trên cùng bởi mảng liên tục. Chúng ta có thể dùng mảng liên tục cấp phát động để khắc phục nhược điểm do không biết trước chiều cao của cây nhị thức cao nhất trong hàng nhị thức. Việc dùng mảng liên tục cho phép tìm nhị phân phần tử nhỏ nhất, tuy nhiên sẽ là lãng phí lớn khi hàng nhị thức gồm quá ít cây nhị thức với nhiều chiều cao khác nhau. 13 12 14 24 65 21 23 51 24 65 16 26 18 13 12 14 24 65 21 23 51 24 65 16 26 18 Chương 11 – Hàng ưu tiên Giáo trình Cấu trúc dữ liệu và Giải thuật 301 Hình 11.18- Kết hợp hai cây nhị thức B2 thành một cây nhị thức B3. Dưới đây là phần mã giả cho các khai báo cấu trúc của cây nhị thức và hàng nhị thức. Các tác vụ kết hợp hai cây nhị thức, trộn hai hàng nhị thức, và loại phần tử nhỏ nhất trong hàng nhị thức cũng được trình bày bằng mã giả. //Phần mã giả cho khai báo và một số tác vụ cho hàng nhị thức. struct Binomial_Node DataType data Binomial_Node* leftChild Binomial_Node* nextSibling end struct struct Binomial_Tree Binomial_Tree combineTrees(ref Binomial_Tree T1, ref Binomial_Tree T2) Binomial_Node* root int notEmpty() // Trả về 1 nếu cây không rỗng, ngược lại trả về 0. end struct class Binomial_Queue public: Binomial_Queue(Binomial_Node* p, int k)// k là số nút trong hàng nhị thức. int empty Binomial_Queue merge(ref Binomial_Queue H1, ref Binomial_Queue H2) protected: int count // Tổng số nút trong tất cả các cây nhị thức. Binomial_Tree Trees[MAX] end class Binomial_Tree Binomial_Tree::CombineTrees(ref Binomial_Tree T1, ref Binomial_Tree T2); /* pre: T1 và T2 khác rỗng. post: T1 chứa kết quả kết hợp hai cây T1 và T2, T2 rỗng. Trả về cây T1. */ { 1. if (T1.root->data > T2.root->data) 1. Tráo T1 và T2 cho nhau 2. T2.root->nextSibling = T1.root->leftChild // Chèn cây T2 vào đầu danh // sách các cây con của gốc của T1 3. T1.root->leftChild = T2.root // Cập nhật nút con trái cho nút gốc của T1 4. T2.root = NULL 5. return T1 } 14 16 26 18 12 24 21 65 12 24 21 65 14 16 26 18 Chương 11 – Hàng ưu tiên Giáo trình Cấu trúc dữ liệu và Giải thuật 302 • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • Binomial_Queue Binomial_Queue::merge(ref Binomial_Queue H1, ref Binomial_Queue H2) /* post: H1 chứa kết quả trộn hai hàng nhị thức H1 và H2, H2 rỗng. Trả về hàng H1. uses: hàm Binomial_Tree::notEmpty() trả về 1 nếu cây không rỗng, ngược lại trả về 0. */ { Binomial_Tree T1, T2, carry int i = 0 1. H1.count = H1.count + H2.count 2. loop (H2 chưa rỗng hoặc carry không rỗng) 1. T1 = H1.Trees[i] 2. T2 = H2.Trees[i] 3. case (T1.notEmpty() + 2*T2.notEmpty() + 4*carry.notEmpty()) 1. case 0: // Không có cây nào 2. case 1: // Chỉ có cây T1 1. break 3. case 2: // Chỉ có cây T2 1. H1.Trees[i] = T2 2. H2.Trees[i].root = NULL 3. break 4. case 4: // Chỉ có cây carry 1. H1.Trees[i] = carry 2. carry.root = NULL 3. break 5. case 3: // Có cây T1 và T2 1. carry = combineTrees(T1, T2) 2. H1.Trees[i].root = NULL 3. H2.Trees[i].root = NULL 4. break 6. case 5: // Có cây T1 và carry 1. carry = combineTrees(T1, carry) 2. H1.Trees[i].root = NULL 3. break 7. case 6:// Có cây T2 và carry 1. carry = combineTrees(T2, carry) 2. H2.Trees[i].root = NULL 3. break 8. case 7:// Có cả 3 cây T1, T2, và carry 1. H1.Trees[i] = carry 2. carry = combineTrees(T1, T2) 3. H2.Trees[i].root = NULL 4. break 4. endcase 5. i = i + 1 3. endloop 4. return H1 } Chương 11 – Hàng ưu tiên Giáo trình Cấu trúc dữ liệu và Giải thuật 303 Tóm lại, trong chương này chúng ta đã xem xét một số cách hiện thực của hàng ưu tiên. Heap nhị phân vừa đơn giản vừa hiệu quả vì không sử dụng con trỏ, nó chỉ hơi tốn nhiều vùng nhớ chưa sử dụng đến mà thôi. Chúng ta đã nghiên cứu thêm tác vụ trộn và bổ sung thêm ba phương án khác của hàng ưu tiên. Heap lệch trái là một ví dụ khá hay về giải thuật đệ quy. Skew heap giống heap lệch trái nhưng đơn giản hơn, với hy vọng rằng các ứng dụng có tính ngẫu nhiên thì các trường hợp xấu nhất sẽ không thường xuyên xảy ra. Cuối cùng hàng nhị thức cho thấy một ý tưởng đơn giản mà lại giới hạn được chi phí cho giải thuật khá tốt. Binomial_Queue::Binomial_Queue(Binomial_Node* p, int k) /* pre: p là địa chỉ nút đầu tiên của một cấu trúc liên kết các cây nhị thức Bk,Bk-1,,B0. post: Hàng nhị thức mới được tạo thành từ các cây này. */ { 1. count = (1 << (k+1)) –1 2. loop (k >= 0) 1. Trees[k] = p 2. p = p->nextSibling 3. Trees[k]->nextSibling = NULL// Cắt rời các cây con để đưa vào mảng liên // tục các cây nhị thức cho hàng nhị thức mới. 4. k = k – 1 3. endloop } Chương 11 – Hàng ưu tiên Giáo trình Cấu trúc dữ liệu và Giải thuật 304
File đính kèm:
- giao_trinh_cau_truc_du_lieu_va_giai_thuat_chuong_11_hang_uu.pdf