算法导论 第六章 堆排序

进入排序专题了。

这次我们要讲的是堆排序,而说到堆排序,肯定就免不了要了解一下堆这个数据结构((我们这里说的都是二叉堆)。

什么是堆呢?

堆就是一个完全二叉树,而完全二叉树。。。就是除了最后一层可以不满外(也可以满),其他层都是满的,并且最后一层的叶子节点都位于最左端的二叉树。

 

上图就是一个标准的完全二叉树,红色的是其叶子节点,可以看到最后一层的节点都位于左端,并且除了最后一层都是满的。

我们所要介绍的堆排序就是基于这样一个数据结构。

而堆根据父节点和子节点的关系,又分为大根堆和小根堆,大根堆就是指任意父节点大于两个子节点的堆,如下:

小根堆可以堆就是任意父节点小于子节点的堆,如下:

为了方便的实现堆排序,我们可以先写一个优先队列priority_queue。

其原型如下:

#include <cstdio>
#define INF 999999999
using namespace std;

class priority_queue
{

        private:
        //      队列用数组存1~n,0可作为交换区间
                int *a;
        public:
        //      构造函数
                priority_queue();
        //      析构函数
                ~priority_queue();
        //      队列长度
                int length;
        //      比较函数(确定升序或降序)
                bool cmp(const int& a,const int& b);
        //      建堆
                void buildHeap();
        //      重构子树
                void heapify(int k);
        //      更新某个节点的值并调整其父节点
                bool update(int k,int v);
        //      入队
                void push(int v);
        //      出队            
                bool pop();
        //      取队头
                int top();
        //      取左节点下标
                int left(int k);
        //      取右节点下标
                int right(int k);
        //      取父节点下标
                int parent(int k);
        //      交换下标为i和j的两个节点的值
                void exchange(const int& i,const int& j);
};

然后我们首先来看构造函数

priority_queue::priority_queue()
{
        a = new int[100];

        length=0;
}

priority_queue::~priority_queue()
{
        delete []a;
}

这个数组的大小可以根据需要来调整。

然后看5个附带的基本操作:

bool priority_queue::cmp(const int &a,const int &b){return a > b;}

int priority_queue::left(int k){return k<<1;}

int priority_queue::right(int k){return (k<<1)|1;}

int priority_queue::parent(int k){return k>>1;}

void priority_queue::exchange(const int &i,const int &j){a[0]=a[i];a[i]=a[j];a[j]=a[0];}

再就是重构和建堆还有更新操作:

void priority_queue::heapify(int k)
{
        int t;
        int l=left(k);
        int r=right(k);
        while(l<=length)
        {
                if(cmp(a[l],a[k]))t=l;
                else t=k;
                if(r<=length&&cmp(a[r],a[t]))t=r;
                if(t!=k)
                {
                        exchange(k,t);
                        k=t;
                        l=left(k);
                        r=right(k);
                }
                else break;
        }
}

void priority_queue::buildHeap()
{
        for(int i=length/2;i>=1;i--)heapify(i);
}

bool priority_queue::update(int k,int v)
{
        if(cmp(v,a[k])==false)return false;
        a[k]=v;
        while(k>1&&cmp(a[k],a[parent(k)]))
        {
                exchange(k,parent(k));
                k = parent(k);
        }
        return true;
}

最后是入队,出队和取队头:

void priority_queue::push(int v)
{
        length++;
        a[length]=(cmp(1,2)?INF:-INF);
        update(length,v);
}

bool priority_queue::pop()
{
        if(length<1)return false;
        exchange(1,length);
        length--;
        heapify(1);
        return true;
}

int priority_queue::top(){return a[1];}

就是这样一个基本的优先队列就完成了。

其中建堆操作其实也可以通过不断的push实现。

然后我们实现堆排序就比较简单了。

代码如下:

int main()
{

        priority_queue a;
        int s[] = {15,13,9,5,12,8,7,4,0,6,2,1};
        for(int i=0;i<12;i++)a.push(s[i]);
      int l = 12;do
        {
                l--;
                s[l]=a.top();
        }
        while(a.pop());
        for(int i=0;i<12;i++)printf("%d ",s[i]);
        printf("\n");
        return 0;

}

其实就是将数组push进优先队列中,然后再pop回去,就得到了一个排序过后的数组。

第一步中每次push都是O(lgn),所以最坏的复杂度是O(nlgn),第二部中同样。

话说buildHeap的复杂度貌似是O(n)来着,不过也没事了,反正最终结果整个堆排序的复杂度是O(nlgn)。

posted @ 2017-08-22 19:32  mtl6906  阅读(194)  评论(0编辑  收藏  举报