堆排序(C++实现)

堆排序

堆排序运行时间(n lgn)。它是一种原地(in place)排序算法:在任何时候,数组中只有常数个元素存储在数组外。堆的数据结构不至少在堆排序中有用,还可以构成一个有效的优先队列。

数据结构

二叉堆数据结构是一种数组对象,它可以被看做是一棵完全二叉树。树中的每个节点与数组中存放该节点值的那个元素对应。除了最后一层,树的每一层都是填满。
假设有数组A。
树的根为A[0]现在给定某个节点的下标i,其父节点parent(i),左孩子left(i),右孩子right(i)的下标可以通过简单的计算得到:

parent(i) = (i-1)/2

left(i) = i*2+1

right(i) = i*2+2

二叉堆有两种:大根堆和小根堆。在这两种堆中,节点内的数值都要满足堆的特性,其细节则根据堆的种类决定。
大根堆指的是除了根节点以外的每个节点,有:

A[parent[i]]>=A[i]

小根堆指的是除了根节点以外的每个节点,有:

A[parent[i]]<=A[i]

此处以大根堆为例:
fixDown过程,其运行时间为O(lg n),是保持大根堆性质的关键。
buildHeap过程,以线性时间运行,可以在无序的输入数据基础上构造处最大堆。
heapSort过程,运行时间为O(n lg n),对一个数组进行原地排序。
push、pop过程,运行时间为O(lg n),可以让堆结构作为优先队列使用。

实现

fixDown被调用时,我们假定以left(i)和right(i)为根的两棵二叉树都是大根堆,但这时A[i]可能小于其子女,这就违反了大根堆的性质。
fixDown让A[i]在大根堆中“下降”,使得以i为根的子树成为大根堆。

时间复杂度:O(lg n)

void fixDown(int a[],int root,int len)//存放堆的数组,当前正在调整的子树的根,数组允许访问部分的长度
{
    int tmp=a[root];
    int child=root*2+1;
    while(child<len)
    {
        if(child+1<len&&a[child]<a[child+1])
            child++;
        if(tmp>=a[child])
            break;
        a[root]=a[child];
        root=child;
        child=child*2+1;
    }
    a[root]=tmp;
}

buildHeap被调用时,A[0…n-1]将自下而上得变成一个最大堆,子数组A[((n-1)/2+1)…n-1]中的元素都是树中的叶子,因此叶子都可以被认为是只含有一个元素的堆,故建树过程中对每一个其他节点都进行一个fixDown的调用。

时间复杂度:O(n)

buildHeap(int a[],int len)
{
    for(int i=len/2;i>=0;--i)//buildHeap
        fixDown(a,i,len);
}

pop被调用时,我们返回了根结点,此时整棵树变成了两棵,这时我们需要将最后一个数据放置到根节点处,再进行一次fixDown
时间复杂度:O(lg n)

int pop(int a[],int len)
{
    swap(a[0],a[len-1]);
    fixDown(a,0,len-1);
    return a[len-1];
}

开始时,堆排序算法先用buildHeap将输入数组A造成了一个大根堆,在调用heapSort时,可以通过将A[0]同A[n-1]交换来达到最终的目的。现在只需要将数组的长度减去1,就可以很容易得将剩下的的A[0…n-2]重构成大根堆。原来跟的子女还是大根堆,但新的根元素可能违背了大根堆的性质。这时只需要调用fixDown 就能保持这一性质了。堆排序算法不断重复这个过程,堆的大小由n变成了1。

算法复杂度:O(n lg n)

void heapSort(int a[],int len)
{
    for(int i=len;i>1;++i)
        pop(a,i);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值