排序算法之堆排序及C++实现

先声明:所有的排序我都会总结记录在博客里,最后会有一个大的cpp文件包含全部排序的代码,每个排序算法放在单独的一个namespace里面,清晰明了,并设计对数器给出测试。我会把它放到网盘供需要之人下载,见这里。 ps:所有的排序以升序为基准。测试编译器为Visual C++

堆排序(heap sort)思想:借助于堆这个数据结构进行排序。

堆排序属于比较排序,不稳定。

声明 :堆的操作,包括插入,拔掉堆顶,上浮,下滤等等过程,自己画图辅助理解。

下面贴出百度百科对堆的定义:
heap
堆满足两个性质:

  1. 结构性质
    是一棵完全二叉树。
  2. 堆序性质
    对于树中每个节点(如果它有父亲),那么它的值总是 <= 父亲节点的值(这叫大根堆),总是 >= 父亲节点的值(叫小根堆)。
    每次对堆进行的插入,删除操作都必须进行调整保持堆的堆序性质。

堆逻辑上是一棵完全二叉树,但实际上是一个数组表示。

堆排序具体的步骤:

  1. 建初始堆(buildHeap)
    将原始数组变成一棵逻辑上具有堆序性质的完全二叉树。
  2. 拔掉堆顶 + 重新调整以保持堆序性质。
    具体做法:用堆顶与最后一个元素交换,然后堆的逻辑大小减一,然后对现在的堆顶执行下滤过程重新建堆以保持堆序性质(因为现在堆序性质仅仅由交换后的堆顶破坏,所以仅仅对堆顶执行下滤即可)。一直到最后只剩下一个元素完成排序。

因为建的是大根堆,所以最后排序结果是升序的。

下面分析一下建初始堆的过程:(两种做法)

  • 第一种
    将堆的逻辑大小一开始就设为数组的大小,然后从第一个非叶子节点一直到根节点执行下滤操作,即可将其构造成初始堆。
    bt
    高为h的满二叉树是高为h的完全二叉树的上界,所以高度为h的满二叉树的节点的高度和是高度为h的完全二叉树的上界,非叶子节点执行下滤过程,所以总的时间复杂度一定不会超过 2h+1 - 1 - (h + 1),即为 O(N) 的时间复杂度。
  • 第二种
    每次都进行insert操作,一共进行 N -1 次,每次堆的逻辑大小加1,每次执行的操作为上浮过程。每一次插入操作花费 O(1)~O(logN)的时间复杂度,所以总的时间为 O(N) ~O(NlogN),时间复杂度太高,所以下面的demo采用的第一种做法。

demo:

template<typename T>
inline void swap(T* arr, int i, int j) {
    T tmp(arr[i]);
    arr[i] = arr[j];
    arr[j] = tmp;
}

/**
*  返回堆中i位置节点左孩子的索引
*/
inline int leftChild(int i) {
    return 2 * i + 1;
}

/**
*  下滤堆中i位置的元素 buildHeap和deleteMax都要使用
*  i :要下滤的元素所在的位置
*  n :堆的逻辑大小
*/
template<typename T>
void percolateDown(T* arr, int i, int n) {          // 辅助记忆,上浮或者下滤的过程有没有类似插入排序的过程?
    T tmp = arr[i];                                 // 如此,相信堆排序手撸的时候忘记的概率会大大降低。
    int child = leftChild(i);
    for (; child < n; i = child, child = leftChild(i)) {
        if (child < n - 1 && arr[child] < arr[child + 1])
            child++;
        if (tmp < arr[child]) {
            arr[i] = arr[child];
        }
        else {
            break;
        }
    }
    arr[i] = tmp;
}

/**
*  standard heapsort
*  大根堆
*/
template<typename T>
void heapsort(T* arr, size_t size) {
    if (size <= 0)
        return;

    // buildHeap  O(N) Linear Time Complexity, approximately 2N.
    for (int i = size / 2 - 1; i >= 0; --i) {
        percolateDown(arr, i, size);
    }

    // deleteMax process
    for (int i = size - 1; i > 0; --i) {
        swap(arr, 0, i);
        percolateDown(arr, 0, i);  //从第一个非叶子节点到根节点,执行下滤过程
    }
}

结果:
res

堆是一个很重要的数据结构,优先队列(priority queue)也是使用堆实现的。我写过一篇关于优先队列的文章,见这里

时间复杂度:
建堆:O(N)
deleteMax+重建堆:O(NlogN)
所以总的时间复杂度:O(NlogN)
空间复杂度:
空间复杂度为O(1)。
O

纸上得来终觉浅,绝知此事要躬行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值