堆与堆排序

堆与堆排序以及应用

堆是一种数据结构,是一棵完全二叉树,一般用于维护数据。
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最短路等),建议大家掌握。

欢迎指正错误^_^

点赞