Mục đích của heap trong các bài các bài toán tin là ta có thể truy xuất phần tử lớn nhất hay nhỏ nhất có trong một mảng lưu trữ hiện tại.. Đống có vai trò quan trọng trong nhiều thuật t
Trang 1CẤU TRÚC HEAP VÀ ỨNG DỤNG
I LỜI NÓI ĐẦU
Có thể nói Heap là 1 cấu trúc hữu dụng vào bậc nhất trong giải toán Trong khoa học máy tính, đống (tiếng Anh: heap) là một cấu trúc dữ liệu dựa trên cây thỏa mãn tính chất đống: nếu B là nút con của A thì khóa(A)≥khóa(B) (HEAP MAX) Một hệ quả của tính chất này là khóa lớn nhất luôn nằm ở nút gốc Do đó một đống như vậy thường được gọi là đống-max Nếu mọi phép so sánh bị đảo ngược khiến cho khóa nhỏ nhất luôn nằm ở nút gốc thì đống đó gọi là đống-min Thông thường mỗi nút có không quá hai nút con Mục đích của heap trong các bài các bài toán tin là
ta có thể truy xuất phần tử lớn nhất hay nhỏ nhất có trong một mảng lưu trữ hiện tại
Do đó có 2 loại là heap_max và heap_min Đống là một cách thực hiện kiểu dữ liệu trừu tượng mang tên hàng đợi ưu tiên Đống có vai trò quan trọng trong nhiều thuật toán
Một số ứng dụng của Heap:
+ Heapsort: Một trong những phương pháp sắp xếp tại chỗ tốt nhất
+ Thuật toán lựa chọn: có thể sử dụng đống để tìm phần tử lớn nhất, nhỏ nhất, trung
vị, phần tử lớn thứ k, trong thời gian tuyến tính.
+ Thuật toán cho đồ thị: nhiều thuật toán cho đồ thị sử dụng cấu trúc dữ liệu đống chẳng hạn như thuật toán Dijkstra, hay thuật toán Prim.
Các thao tác thường gặp trên đống là:
+ Tìm-max (tìm-min): tìm khóa lớn nhất trong đống-max (tìm khóa nhỏ nhất trong đống-min).
+ Xóa-max (xóa-min): xóa nút gốc của đống-max (đống-min)
+ tăng-khóa (giảm-khóa): thay đổi giá trị một khóa trong đống-max (đống-min)
+ chèn: chèn thêm một khóa mới vào đống
+ hợp: hợp hai đống lại thành một đống mới chứa tất cả các khóa của cả hai
II CẤU TRÚC HEAP
Trang 2Heap được biểu diễn bằng cây nhị phân, nút k trên cây sẽ có hai nút con là 2*k
và 2*k+1, có cha là k div 2 Heap[k] tùy theo loại heap mà nó lớn hơn con nó hay cha
nó Mỗi khi ta thay đổi một phần tử bất kì trong heap thì ta sẽ cập nhật lại heap trong khoảng thời gian log n Có hai thao tác đối với heap là upheap và downheap
Heap sẽ cần biến nh để quản lí số lượng phần tử của heap.
Ví dụ với HEAP MAX
Các thao tác thường dùng trong xử lý HEAP:
- Up_heap: nếu 1 nút lớn hơn cha của nó thì di chuyển nó lên trên
- Down_heap: nếu 1 phần tử nhỏ hơn 1 con của nó thì di chuyển nó xuống dưới
- Push: đưa 1 phần tử vào HEAP bằng cách thêm 1 nút vào cây và up_heap nút đó
- Del: loại 1 phần tử khỏi HEAP bằng cách chuyển nó xuống cuối heap và loại bỏ, sau đó chỉnh sửa lại heap sao cho thoả mãn các điều kiện của HEAP.
Trong code x là vị trí, a là giá trị, nh là số phần tử, pa là cha của x, c là con của
if(c > nh)return; // không có con thì thoát
if(c < nh and heap[c+1] > heap[c]) c++;
// n u nó còn con 2 thì ch n con l n ế ọ ớ để khi kéo nó
// lên đảm b o th ng này lúc làm cha s l n h nả ằ ẽ ớ ơ
Trang 3// th ng con không ằ được làm cha
if(heap[x] < heap[c]){ // n u kéo ế được thì kéo
Heap được sử dụng trong thuật toán Dijkstra, Kruskal, Heap Sort nhằm giảm
độ phức tạp thuật toán Heap còn có thể sử dụng trong các bài toán dãy số, QHĐ, đồ thị Với những ví dụ sau ta sẽ thấy phần nào sự đa dạng và linh hoạt trong sử dụng Heap
III BÀI TẬP ỨNG DỤNG CẤU TRÚC HEAP
1 KMIN http://vn.spoj.com/problems/KMIN
Ý tưởng: Ban đầu sort không giảm hai dãy a,b Cho a[1] + b[i] với i = 1 -> n vào 1 heap_min Duyệt k lần , mỗi lần lấy gốc ra rồi cho a[i+1] + b[j] vào với i, j là vị trí của nút vừa lấy ra, nếu i = n thì thôi.
1 const fi='';
2 fo='';
3 type mang=array[ 50000] of longint;
4 sum=record i, j: longint; end;
16 for i:=1 to m do readln(f, a[ ]);
17 for i:=1 to n do readln(f, b[ ]);
18 close( )
19 end;
20 }
Trang 4{ -21 function cmp(x, y: sum): boolean;
23 exit( [x.i +b[x.j >=a[y.i +b[y.j]);
24 end;
25 -}
{ -26 procedure swap(i, j: longint)
45 if j>top then exit;
46 if j<top) and cmp(heap[ ], heap[j+1]) then inc( )
47 if not cmp(heap[ ], heap[ ]) then swap(i, j)
48 downheap( )
49 end;
50 }
{ -51 procedure push(x: longint)
65 procedure sort(var x: mang; L, H: longint)
66 var i, j, key, tg: longint;
73 while x[ ]<key do inc( )
74 while x[ ]>key do dec( )
75 if i<=j then
76 begin
Trang 62 HEAP1 http://vn.spoj.com/problems/HEAP1
Ý tưởng: Đẩy vào heap_min , lặp n-1 lần, mỗi lần lấy hai thằng gốc và đẩy tổng hai
thằng này vào, tăng res lên hai thằng này, tức là lấy thằng gốc rồi del(1) rồi lại lấy thằng gốc rồi lại del(1), xong rồi push(x+y) vào.
28 if j>top then exit;
29 if j<top) and ( [ ]>a[j+1]) then inc( )
30 if a[ ]>a[ ] then swap(i, j)
31 downheap( )
32 end;
33 }
{ -34 procedure push(x: longint)
Trang 7Ý tưởng: Mỗi lần “+”V thì đẩy phần tử V vào heap bằng thủ tục Push Thao tác
“-” : Nếu danh sách đang không rỗng thì thao tác này loại bỏ tất cả các phần tử lớn
nhất của danh sách bằng thủ tục Del đến khi nào đầu heap nhỏ hơn Max hiện tại.
Trang 826 if j>top then exit;
27 if j<top) and ( [ ]<a[j+1]) then inc( )
28 if a[ ]<a[ ] then swap(i, j)
29 downheap( )
30 end;
31 }
{ -32 procedure push(x: longint)
Trang 996 for i:=n-1 downto do
97 if a[ ]<>a[i+1 then write(f, a[ ], ' ')
Ý tưởng: Dùng 2 heap, 1 heap (HA) lưu các phần tử từ thứ 1 tới N div 2 và heap còn
lại (HB) lưu các phần tử từ N div 2 +1 tới N sau khi đã sort lại tập thành tăng dần
HA là Heap-max còn HB là Heap-min Như vậy phần tử trung vị luôn là gốc HB (N lẻ) hoặc gốc của cả HA và HB (n chẵn) Thao tác MEDIAN do đó chỉ có độ phức tạp O(1) Còn thao tác PUSH sẽ được làm trong O(logN)
- Nếu gtr đưa vào nhỏ hơn hoặc bằng HA[1] đưa vào HA ngược lại đưa vào HB Số phần tử N của tập tăng lên 1.
- Nếu HA có lớn hơn (/nhỏ hơn N) div 2 phần tử thì POP 1 phần tử từ HA (/HB) đưa vào heap còn lại Sau quá trình trên thì HA và HB vẫn đảm bảo đúng theo định nghĩa ban đầu Bài toán được giải quyết với độ phức tạp O(MlogM).
1 //uses sysutils;
Trang 1010 heapa, heapb: heap;
11 n, k, seed, add, topa, topb: longint;
36 if j>topa then exit;
37 if j<topa) and (heapa[ ] gt<heapa[j+1 gt) then inc( )
38 if heapa[ ] gt>=heapa[ ] gt then exit;
39 swapa(i, j)
40 downheapa( )
41 end;
42 }
{ -43 procedure pusha(i: longint)
53 heapa[ ]:=heapa[topa]
54 pos[heapa[ ] vt]:=i;
Trang 1160 procedure swapb(i, j: longint)
66 pos[heapb[ ] vt]:=i+(k+1 div ;
67 pos[heapb[ ] vt]:=j+(k+1 div ;
81 if j>topb then exit;
82 if j<topb) and (heapb[ ] gt>heapb[j+1 gt) then inc( )
83 if heapb[ ] gt<=heapb[ ] gt then exit;
84 swapb(i, j)
85 downheapb( )
86 end;
87 }
{ -88 procedure pushb(i: longint)
90 inc(topb)
91 heapb[topb]:=a[ ]
92 pos[ [ ] vt]:=topb+(k+1 div ;
98 heapb[ ]:=heapb[topb]
99 pos[heapb[ ] vt]:=i+(k+1 div ;
115 while a[ ] gt<key do inc( )
116 while a[ ] gt>key do dec( )
117 if i<=j then
Trang 12155 heapa[ ]:=a[(k+1 div + -i]
156 pos[ [(k+1 div + -i] vt]:=i;
168 if pos[ ]<=(k+1 div then popa(pos[ ])
169 else popb(pos[ ] (k+1 div )
170 if i=n-k+1 then break;
171 {a[i+k].vt:=i+k;
172 a[i+k].gt:=(a[i+k-1].gt*mul+add) mod 6 55 36; }
173 if a[i+k] gt<=heapa[ ] gt then pusha(i+k)
174 else pushb(i+k)
175 if topb>k-(k+1 div then
176 begin
Trang 13177 inc(topa)
178 heapa[topa]:=heapb[ ]
179 pos[heapa[topa] vt]:=topa;
186 heapb[topb]:=heapa[ ]
187 pos[heapb[topb] vt]:=topb+(k+1 div ;
Ý tưởng: Đầu tiên ta sắp xếp lại thứ tự thời gian của các cuộc ném bóng Bài này
ta dùng 3 heap với các ý nghĩa như sau: 1 heap min, 1 heap max lưu chỉ sô các lỗ còn trống, 1 heap quản lí các ô đang có bóng xoáy ở trên, heap này là 1 heap min với giá trị khóa là thời gian quả bong rơi xống lỗ Đầu tiên 2 heap min và max đầy (tât cả các ô còn trông) Cho 1 vòng for qua các thứ tự ném, đến 1 lượt ta phải làm
2 việc sau:
1: Loại bỏ từ Heap quản lí các ô có bóng những ô nào có thời gian rơi xuống lỗ ≤ thời gian ném bóng của lượt này, sau đó thêm vào heap min và heap max những ô tìm được những ô này lần lượt lấy ra từ heap quản lý.
Trang 142: Thực hiện ném: nếu từ bên trái thì dựa vào heap min để tìm ô trống có chỉ số nhỏ nhất, nếu ném bên phải thì ngược lại Khi tìm được thì loại bỏ khỏi 2 heap này phần tử tìm được, thêm phần tử vào heap quản lý gồm 2 thông tin chỉ số của lỗ và thời gian rơi xuống lỗ Cứ làm như thế cho đến khi hết bóng hoặc không còn lỗ nào trống thì dừng lại Chú ý cần phải lưu số hiệu các ô trong hai heap để tiện cho việc loại bỏ.
16 a : array [1 mmax] of lannem;
17 hmi, hma: array [1 mmax] of longint;
18 hql : array [1 mmax] of longint;
19 vti, vta: array [1 mmax] of longint;
20 vthql : array [1 mmax] of longint;
21 d : array [1 mmax] of longint;
22 bb : array [1 mmax] of longint;
Trang 1546 procedure readf; inline;
Trang 19330 a_nem(a[i] time, a[i] xoay, i);
331 if not hoa then
Trang 20341 inc(slB);
342 b_nem(a[i] time, a[i] xoay, i);
343 if not hoa then
370 if a[i] ten= 'A' then
371 writeln(g, 'Alice takes the hole: ', bb[i]);
372 if a[i] ten= 'B' then
373 writeln(g, 'Bob takes the hole: ', bb[i]);
381 writeln(g, 'Alice loses at her turn: ', slA);
382 writeln(g, 'Game lasts: ', tc,' minute(s)');
Ý tưởng: Dùng thuật toán MORE: Sắp xếp các công việc theo thời điểm cuối thực
hiện công việc Thực hiện vòng lặp Xét các công việc cho đến khi gặp công việc
Trang 21quá hạn: i Tìm trong dãy các việc đã sắp xếp thì dùng heap lấy ra công việc có thời thực hiện lớn nhất trong các công việc trước đó và pop ra khỏi heap, loại bỏ công việc j có thời gian làm lâu nhất Nếu ko có i thì dừng vòng lặp Kết hợp dùng cấu trúc HEAP độ phức tạp của bài toán là O(n log n)
12 d, p, cs: array [1 mmax ] of longint;
13 h : array [1 mmax ] of key;
14 dau : array [1 mmax ] of boolean;
22 assign(f, fi); reset(f);
23 assign(g, fo); rewrite(g);
Trang 24+ Lấy ra nhanh chóng các phần tử lớn nhất (hoặc nhỏ nhất) của một dãy.
+ Khá tốt để sắp xếp những dãy có số lượng phần tử lớn trong mộtthời gian ngắn + Cho phép tích lũy các thông tin dần dần trong quá trình sắp xếp.
Chuyên đề chỉ dừng lại giới thiệu được một số ít các bài tập với Heap, còn rất nhiều bài tập khác ứng dụng heap mà chuyên đề này chưa nhắc tới Thời gian viết chuyên đê có hạn, không tránh khỏi sai sót, rất mong được sự góp ý của thầy cô và các em học sinh
Trang 25
V TÀI LIỆU THAM KHẢO
1 Giải thuật và lập trình – T.S Lê Minh Hoàng – ĐHSP Hà Nội
2 Website: www vnoi.info
3 Một số vấn đề đáng chú ý trong môn tin học – Phan Công Minh
4 Cấu trúc heap – doraemonvodanh