排序算法:堆排序

Posted 不要做程序员的小松鼠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法:堆排序相关的知识,希望对你有一定的参考价值。

堆排序

1. 堆:
    1. 一种完全二叉树。
    2. 每个结点的值都大于或等于其左右子结点的值,大顶堆。
    3. 小顶堆同理。
2. 是简单选择排序的一种改进:把每次比较的结果用堆来保存起来。
3. 堆排序(大顶堆):
    1. 将待排序列构造成一个大顶堆。
    2. 将堆顶和待排序列最后一个元素交换,也就是保存起来。
    3. 将剩余的序列(去除最后一个元素)重新构造成一个堆。
    4. 重复23 。

4. 待排序列构造初始大顶堆:
    1. 设序列长度length,已经构造好最初的完全二叉树,无序。
    2. 从最下层最右边的非叶子结点开始向左向上。
    3. 二叉树的性质:根节点从序号1开始,设某个结点的序号为k,则其左子树的序号是2k,右子树的序号是2k+1。最下层最右边的非叶子结点就是length/2取整。
    4. 从第【length/2取整】个结点开始,向根节点(序号为1)开始 逐个调整每个子树的三个结点的顺序,让其成为大顶堆。
    5. 交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整
    6. 最后调整到根节点时候,整个树就是一个大顶堆。

复杂度

1. 构建堆:O(n)
2. 重建堆:O(nlogn),调整的是根节点的左子树或者右子树,而调整的次数也就是该二叉树目前的深度,也就是【log2n】+1,每次都要调整,一共调整n-1次,所以复杂度就是O(nlogn)。
3. 总复杂度:O(nlogn)
4. 对原始记录的排序状态不敏感,反正都要构造初始堆。
5. 不稳定。
6. 构建堆需要多次比较,待排序列个数较少的情况不适合。

图示

代码实现

//构建大顶堆的调整算法
void sift(int r[],int k,int m)
{
    int i = k;
    int j = 2*k;
    while (j<=m)
    {
        if (j<m && r[j]<r[j+1])
        {
            //j指向较大的孩子结点
            j = j + 1;
        }
        if (r[i]<=r[j])
        {
            int temp = r[i];
            r[i] = r[j];
            r[j] = temp;
            //根节点的调整会影响孩子结点,所以一直调整到m
            i = j;
        j = 2*i;
        }
        else
        {
            break;
        }

    }
}
//堆排序
void HeapSort(int r[ ], int n)
{
    int i;
    for (i=n/2; i>=1; i--)       //初始建堆,从最后一个非终端结点至根结点
    {
    sift(r, i, n) ;     
    }
    for (i=1; i<n; i++)        //重复执行移走堆顶及重建堆的操作
    {
        int temp = r[1];
        r[1] = r[n-i+1];
        r[n-i+1] = temp;
        sift(r, 1, n-i);
    }
}

用例:

int a[] = {0,36,30,18,40,32,45,22,50};
    int i ;
    for (i = 1;i<9;i++)
    {
        cout<<a[i]<<" ";
    }
    cout<<endl;

    HeapSort(a,8);
    for (i = 1;i<9;i++)
    {
        cout<<a[i]<<" ";
    }

以上是关于排序算法:堆排序的主要内容,如果未能解决你的问题,请参考以下文章

算法-详解堆排序算法

堆排序算法的实现

[ 数据结构 -- 手撕排序算法第七篇 ] 堆排序(中)堆排序的优化算法

重温基础算法内部排序之堆排序法

重温基础算法内部排序之堆排序法

[ 数据结构 -- 手撕排序算法第七篇 ] 堆及其堆排序