堆与堆排序以及应用
堆是一种数据结构,是一棵完全二叉树,一般用于维护数据。
Pascal中一般用数组模拟堆,方法同数组模拟二叉树(C++似乎直接用STL就行了,不过本人太弱不太了解C++【一脸尴尬】)
堆分为小根堆和大根堆(也可以叫小顶堆和大顶堆),小根堆就是堆顶元素为所有元素中最小的,大根堆同理。
堆排序便是使用堆进行排序,时间复杂度为 O(n)~O(n log n) 之间,是一种稳定排序。
这里以小根堆为例:
插入&维护:每读入一个数,将这个数插入堆的末尾,然后向上维护堆,即逐层向上比较,如果发现节点的父节点 > 该节点,则交换,直到该节点的父节点 <= 该节点,结束维护。
删除&维护:输出时,每次输出堆顶元素,并将堆尾元素赋值到堆顶,删除堆尾元素,并向下维护堆,即逐层向下比较,每次与该节点的两个子节点中较小的比较,如果该节点 > 两个节点中较小的则交换,否则结束维护。
如下面一组数据:
输入
5
1 3 7 3 6
输出
1 3 3 6 7
代码如下:
var n,i,x,sum,x1,x2:longint;
heap:array[0..100000]of longint;
procedure swap(var a,b:longint);
var t:longint;
begin
t:=a;a:=b;b:=t;
end;
procedure insert(x:longint); //插入元素
var i:longint;
begin
inc(heap[0]);
heap[heap[0]]:=x;
i:=heap[0];
while (i div 2>0)and(heap[i div 2]>heap[i]) do //向下维护堆
begin
swap(heap[i],heap[i div 2]);
i:=i div 2;
end;
end;
procedure del; //删除堆顶元素
var i,mid:longint;
begin
heap[1]:=heap[heap[0]];
dec(heap[0]);
i:=1;
if (i*2=heap[0])or(heap[i*2]<heap[i*2+1]) then mid:=i*2
else mid:=i*2+1; //mid指向两个子节点中较小那个
while (mid<=heap[0])and(heap[mid]<heap[i]) do
begin
swap(heap[i],heap[mid]);
i:=mid;
if (i*2=heap[0])or(heap[i*2]<heap[i*2+1]) then mid:=i*2
else mid:=i*2+1;
end;
end;
begin
readln(n);
for i:=1 to n do
begin
read(x);
insert(x);
end;
for i:=1 to n do
begin
write(heap[1],' '); //每次输出堆顶元素
del; //删除堆顶元素
end;
writeln;
end.
下面看一道例题(有点水别介意)
合并果子(来自洛谷)
建议使用堆排序,每次取两个最小的合并
代码(和上面差不多):
var n,i,x,sum,x1,x2:longint;
heap:array[0..100000]of longint;
procedure swap(var a,b:longint);
var t:longint;
begin
t:=a;a:=b;b:=t;
end;
procedure insert(x:longint);
var i:longint;
begin
inc(heap[0]);
heap[heap[0]]:=x;
i:=heap[0];
while (i div 2>0)and(heap[i div 2]>heap[i]) do
begin
swap(heap[i],heap[i div 2]);
i:=i div 2;
end;
end;
procedure del;
var i,mid:longint;
begin
heap[1]:=heap[heap[0]];
dec(heap[0]);
i:=1;
if (i*2=heap[0])or(heap[i*2]<heap[i*2+1]) then mid:=i*2
else mid:=i*2+1;
while (mid<=heap[0])and(heap[mid]<heap[i]) do
begin
swap(heap[i],heap[mid]);
i:=mid;
if (i*2=heap[0])or(heap[i*2]<heap[i*2+1]) then mid:=i*2
else mid:=i*2+1;
end;
end;
begin
readln(n);
for i:=1 to n do
begin
read(x);
insert(x);
end;
for i:=1 to n-1 do
begin
x1:=heap[1];del; //取最小的
x2:=heap[1];del; //取次小的
inc(sum,x1+x2); //累加
insert(x1+x2); //将合并后的一堆插入堆
end;
writeln(sum);
end.
Pascal中虽然堆代码巨长无比,但能优化很多算法(如Dijkstra最短路等),建议大家掌握。