堆排序

堆排序

堆排序不同于其他排序算法,它的时间复杂度为O(n lgn),与并归排序相同。但是,不同于并归排序的是,空间复杂度为O(1)。堆排序具有空间原址性。

一种完全二叉树.它又被分为大根(顶)堆和小根(顶)堆.实际以数组存储但是,你可以将一个堆看成一个完全二叉树

大顶堆

若想从小到大排序就需要构建大顶堆,否则构建小顶堆。

大顶堆
顺序储存
大根堆顺序存储

由图可以清楚的看出大顶堆的性质——一颗根节点大于其余节点的完全二叉树。而小顶堆的性质与大顶堆正好相反,根节点比其他节点都要小。


维护堆的性质

要想建立一个堆首先需要知道如何维护堆的性质。编写一个函数,它接受要一个数组a和一个下标。


void max_heap_adj(int a[], int s, int m)
{
    int l = s * 2;      // 得到当前节点的左孩子的下标
    int r = l + 1;       // 右孩子的下标
    int lagest = s;
    if(l <= m && a[l] > a[s]) {
        lagest = l;
    }
    if(r <= m && a[r] > a[lagest]) {
        lagest = r;
    }
    //   比较当前节点和左孩子,右孩子之间的最大值.得到后将他们交换
    if(lagest != s) {
        std::swap(a[lagest], a[s]);
        heapadjust(a, lagest, m);
        // 由于交换后,以lagest为节点的子树可能会违反最大堆的性质,因此需要递归调用.
    }
}

在程序的每一步中,从a[s], a[l], a[r]中选出最大的,并将下标存在lagest中。如果a[s]是最大的,不需要过多操作,过程结束。否则,交换a[s], a[lagest]的值。从而使s节点及其孩子节点都满足最大堆的性质。不过,在交换后,原来根节点的子树可能会违反最大堆性质,因此需要递归调用。


建堆

我们可以利用过程max_heap_adj来把一个大小为n的数组转换为最大堆。由于子数组a[n/2] + 1 …. a[n]中的元素都是树的叶子节点,因此从a[n/2]开始自下而上的转化。

void create_heap(int a[], int m)
{
    int i;
    for(i = m / 2; i >= 0; i--) {
        heapadjust(a, i, m);
    }
}

利用这个过程就可以得到大顶堆了。


堆排序
void heapsort(int a[], int n)
{
    int i;
    create_heap(a, n - 1);
    for(i = n - 1; i >= 1; i--) {
        std::swap(a[0], a[i]);
        heapadjust(a, 0, i - 1);
    }
}

初始的时候,算法利用create_heap将输入的数组建成最大堆。因为数组中最大的元素总在根节点(也就是数组的第一个元素a[0]),通过把它与a[0]交换,我们可以让该元素放到正确的位置上。这时,我们再从堆中去掉该节点(这步操作通过减少数组的元素来实现
剩余的节点中,原来根的孩子节点,仍是大根堆,而新的根节点可能会违背大根堆的性质。为了维护其性质,需要不断地调用max_heap_adj,从而在a[0 … n-1]上构造一个新的大根堆。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值