堆排序

算法思想

  首先在数组上建立一个堆。然后弹出堆顶,将弹出的元素放到堆区的后面。不断重复以上过程直到堆中没有元素。

步骤[1][2][3][4][5][6][7][8]
建堆241216111014159
弹出161215111014924
弹出151214111091624
弹出141291110151624
弹出121191014151624
弹出111091214151624
弹出109111214151624
弹出910111214151624
输出910111214151624

  堆中元素的上渗和下渗操作算法可以根据堆的性质得出,这里不再赘述。接下来介绍一下堆中元素弹出、放入和建立的操作算法。
  堆的弹出算法比较常用的方法是:首先将堆顶元素和堆的最后一个元素交换,然后将堆的大小减一,再将交换后的堆顶元素下渗。这样操作之后,原来的堆顶元素就是当前堆区之后的第一个元素。
  堆的放入算法比较简单,将需要放入的元素放到堆区后的第一个位置,然后将堆的大小加一,再将放入元素进行上渗操作。
  建堆方法并不是运行多次堆的放入算法。首先对只有两个节点或三个节点的子树的父节点进行下渗,然后再对该子树的父节点进行下渗,如此循环下去直到将堆的根节点下渗完毕。根据以上思想,在 n n n个元素的数组中,首先对 n 2 \frac{n}{2} 2n处的节点进行下渗,然后是 n 2 − 1 , n 2 − 2 , n 2 − 3 … 1 \frac{n}{2}-1,\frac{n}{2}-2,\frac{n}{2}-3\dots1 2n1,2n2,2n31。此方法相较运行多次放入算法的方式时间复杂度减少了一半。

时间复杂度

  建堆的时间复杂度为 Θ ( n ) \Theta(n) Θ(n),弹出或放入算法的时间复杂度为 Θ ( log ⁡ 2 n ) \Theta(\log_2n) Θ(log2n),所以堆排序的时间复杂度为:
T ( n ) = n + ( n − 1 ) log ⁡ 2 n T(n)=n+(n-1)\log_2n T(n)=n+(n1)log2n
  计算可得: T ( n ) ∈ Θ ( n log ⁡ 2 n ) T(n)\in\Theta(n\log_2n) T(n)Θ(nlog2n)

测试代码

#include <iostream>
using namespace std;
typedef int element;
/** \brief 堆的比较方法
 *
 * \param a element
 * \param b element
 * \return bool
 *
 */
inline bool less_basic(element a,element b){
    return a<b;///使用小于号则建立最大堆
}
/** \brief 元素上渗
 *
 * \param a element* 堆指针
 * \param p size_t 上渗元素下标
 * \return void
 *
 */
void up(element *a,size_t p){
    element t;
    for(t=a[p];p/2&&less_basic(a[p/2],t);p/=2){///与父节点比较
        a[p] = a[p/2];///将父节点下调
    }
    a[p] = t;
}
/** \brief 元素下渗
 *
 * \param a element* 堆指针
 * \param n size_t 堆中元素个数
 * \param p size_t 下渗元素下标
 * \return void
 *
 */
void down(element *a,size_t n,size_t p){
    element t = a[p];
    while(p*2+1<=n&&(less_basic(t,a[p*2])||less_basic(t,a[p*2+1]))){///与子节点比较
        if(less_basic(a[p*2],a[p*2+1])){///将合适的子节点上调为父节点
            a[p] = a[p*2+1];
            p = p*2+1;
        }else{
            a[p] = a[p*2];
            p = p*2;
        }
    }
    if(p*2<=n&&less_basic(t,a[p*2])){///是否存在只有一个子节点的子树
        a[p] = a[p*2];
        a[p*2] = t;
    }else{
        a[p] = t;
    }
}
/** \brief 元素弹出
 *
 * \param a element* 堆指针
 * \param n size_t 堆中元素个数
 * \return element 弹出的元素
 *
 */
element pop(element *a,size_t n){
    element t = a[1];///将前部和后部交换
    a[1] = a[n];
    a[n] = t;
    down(a,n-1,1);///对交换后的节点下渗
    return t;
}
/** \brief 元素放入
 *
 * \param a element* 堆指针
 * \param n size_t 堆中元素个数
 * \param d element 放入的数据
 * \return size_t 放入后堆中元素个数
 *
 */
size_t push(element *a,size_t n,element d){
    a[++n] = d;
    up(a,n);
    return n;
}
/** \brief 建堆
 *
 * \param a element* 堆指针
 * \param n size_t 元素个数
 * \return void
 *
 */
void make(element *a,size_t n){
    for(size_t i=n/2;i;i--){///自底向上,从子树开始调整
        down(a,n,i);
    }
}
/** \brief 堆排序
 *
 * \param a element* 数组指针
 * \param n size_t 数组长度
 * \return void
 *
 */
void heap(element *a,size_t n){
    make(a-1,n);
    for(size_t i=n;i>1;i--){
        pop(a-1,i);
    }
}
int main(){
    element test[] = {
        41,467,334,500,169,724,478,358,962,464,
        705,145,281,827,961,491,995,942,827,436,
        391,604,902,153,292,382,421,716,718,895,
        447,726,771,538,869,912,667,299,35,894,
        703,811,322,333,673,664,141,711,253,868,
        547,644,662,757,37,859,723,741,529,778,
        316,35,190,842,288,106,40,942,264,648,
        446,805,890,729,370,350,6,101,393,548,
        629,623,84,954,756,840,966,376,931,308,
        944,439,626,323,537,538,118,82,929,541,
    };
    heap(test,sizeof(test)/sizeof(element));
    for(size_t i=0;i<sizeof(test)/sizeof(element);i++){
        cout << test[i] << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值