选择排序—堆排序(Heap Sort)

堆的定义

具有n个元素的序列(k1,k2,…,kn),当且仅当满足

时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:

大顶堆序列:(96, 83,27,38,11,09)

小顶堆序列:(12,36,24,85,47,30,53,91)

初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。

因此,实现堆排序需解决两个问题:

  1. 如何将n 个待排序的数建成堆;
  2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。

如何将n 个待排序的数建成堆?

如上图所示的无序序列建堆过程,首先我们需要找到序列的中点,然后将该中点的值与其孩子节点的值进行比较,若 <= 最小孩子节点的值则不改变,反之,则需要交换他们的位置。当中点比较完成过后,就开始比较中点前面的节点,重复该过程至第一个节点即可。具体代码如下所示:

void HeapAjust(int (&b)[length], int pos, int len){
    if(NULL == b || pos < 0 || len < 0)
        return ;
    int temp = b[pos];  //使用temp存储pos位置的数值
    int child = 2*pos + 1;  //求出子节点的position
    while(child < len){
        if(child+1 < len && b[child] < b[child+1])  //用于建立大堆 
            ++child;    //若右子节点的值大于左子节点,则与父节点比较的节点定为右子节点 
        if(b[pos] < b[child]){
            b[pos] = b[child];
            pos = child;
            child = 2*pos + 1;
        }else{
            break;
        } 
        b[pos] = temp;
    }
}

void BuildingHeap(int (&c)[length], int len){
    for(int i=(len-1)/2; i>=0; --i)
        HeapAjust(c, i, len);
}

这样就将n个待排序的数建成了堆。

输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆?
上面建成堆过后,第一个数字是所有数字中最大的一个数字,我们需要将该数值放到数组的最末尾,同时将最末尾的数字放到数组第一个位置,如上图所示。由于整体数组已经在第一步的时候建立起了堆,因此现在只有第一个位置的数字可能不满足堆的要求,而其它位置肯定都是满足的,因此我们只需要对第一个位置调用HeapAjust()函数即可。具体代码如下:

void HeapSort(int (&d)[length], int len){
    BuildingHeap(d, len);
    for(int i=len-1; i>=0; --i){
        int temp = a[i];
        a[i] = a[0];
        a[0] = temp;
        HeapAjust(d, 0, i);
    }
}

这样就可以通过使用堆的方法对数组进行排序了

分析

设树深度为k。从根到叶的筛选,元素比较次数至多2(k-1)次,交换记录至多k 次。所以,在建好堆后,排序过程中的筛选次数不超过下式:

而建堆时的比较次数不超过4n 次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn )。

注意

在上面过程中,我们求数组从小到大的排列时,建堆时使用的是建立大堆,这样做的目的是:我们在第一位的数字永远都是最大的,然后将该数字与末尾位相调换,一直重复该过程,就可以获得从小到大的排序。

那么如果我们想要排序是由大到小该怎么办呢?建立堆的时候我们可以建立小堆,具体代码如下:

#include<iostream>
using namespace std;

int a[] = {4, 5, 9, 2, 1, 3, 8, 7, 6};
const int length = sizeof(a)/sizeof(a[0]);

void printArray(int a[], int length){
    if(NULL == a || length < 0)
        return ;
    for(int i=0; i<length; i++)
        cout << a[i] << " ";
    cout << endl;
}

void HeapAdjust(int (&b)[length],int pos, int len){
    if(NULL == b || pos < 0 || len < 0)
        return ;
    int temp = b[pos];
    int child = 2*pos + 1;
    while(child < len){
        if(child+1 < len && b[child+1] < b[child])
            ++child;
        if(b[pos] > b[child]){
            b[pos] = b[child];
            pos = child;
            child = 2*pos + 1;
        }else{
            break;
        }
        b[pos] = temp;
    }
    printArray(a, length);
}

void BuildingHeap(int (&c)[length], int len){
    for(int i=(len-1)/2; i>=0; --i)
        HeapAdjust(c, i, len);
}

void HeapSort(int (&d)[length], int len){
    cout << endl;
    cout << "HeapSort : " << endl;
    for(int i=len-1; i>=0; --i){
        int temp = a[i];
        a[i] = a[0];
        a[0] = temp;
        HeapAdjust(d, 0, i);
    }
}

int main(){
    printArray(a, length);
    BuildingHeap(a, length);
    printArray(a, length);
    HeapSort(a, length);
    cout << endl;
    printArray(a, length);
    return 0;
}

参考博客

http://blog.csdn.net/hguisu/article/details/7776068
http://blog.csdn.net/clam_clam/article/details/6799763

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值