33. 排序算法(6):堆排序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dugudaibo/article/details/79485606

  之前所讲的算法,很多都是 O(n2) 的,造成这些算法复杂度较高的主要原因是它们一次又一次地重复地进行这比较操作,并没有利用之前的比较得到的结果。希尔排序对直接插入排序进行改进,使得这种方法的复杂度从 O(n2) 下降到 O(nlogn) 。在这里我们将接着介绍一种利用之前排序结果的方法,堆排序。

1. 基本原理

1.1 完全二叉树

  若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树,如下图就是一个二叉树

他并不一定保证所有的父节点都同时拥有左孩子和右孩子,如上图的这种情况也是满足定义的,但是如下图的这种情况却是不满足定义的

1.2 堆与二叉树

  堆是具有以下性质的完全二叉树,每个结点的值都大于或等于其左右孩子节点的值,称之为大定堆;相反地可以得到小顶堆,如下图所示为大定堆和小顶堆
这里写图片描述

根结点一定是堆中所有结点最大或者最小者,如果按照层序遍历的方式给结点从1开始编号,则结点之间满足如下关系:

下标i与2i和2i+1是双亲和子女关系。那么把大顶堆和小顶堆用层序遍历存入数组,则一定满足上面的表达式。

1.3 堆排序算法

  堆排序(Heap Sort)就是利用堆进行排序的算法,它的基本思想是:

  (1) 将待排序的序列构造成一个大顶堆(或小顶堆)。

  (2) 此时,整个序列的最大值就是堆顶的根结点。将它移走(就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值)。

  (3) 然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的此大值。

  (4) 如此反复执行,便能得到一个有序序列了。

2. 代码实现

  

#include <stdio.h>

int count = 0;

void swap(int k[], int i, int j)       //将数组 k[] 中下标为 i 和 j 的元素进行互换
{
    int temp;

    temp = k[i];
    k[i] = k[j];
    k[j] = temp;
}

void HeapAdjust(int k[], int s, int n)    //调整父节点与孩子结点之间的位置
{
    int i, temp;

    temp = k[s];

    for( i=2*s; i <= n; i*=2 )
    {
        count++;
        if( i < n && k[i] < k[i+1] )
        {
            i++;
        }

        if( temp >= k[i] )
        {
            break;
        }

        k[s] = k[i];
        s = i;
    }

    k[s] = temp;
}

void HeapSort(int k[], int n)
{
    int i;

    // 对 1 - 4 这四个结点及它们对应的孩子节点将的相对顺序进行排列
    // 整体是在建立一个初始的大顶堆
    for( i=n/2; i > 0; i-- )   // i=n/2 是向下取整的,相当于最后的父节点
    {
        HeapAdjust(k, i, n);    //k 是数组,i 循环变量,n 是数组中元素的个数
    }

    // 利用堆进行排序
    for( i=n; i > 1; i-- )
    {
        swap(k, 1, i);
        HeapAdjust(k, 1, i-1);
    }
}

int main()
{
    int i, a[10] = {-1, 5, 2, 6, 0, 3, 9, 1, 7, 4};

    HeapSort(a, 9);

    printf("总共执行 %d 次比较!", count);
    printf("排序后的结果是:");
    for( i=1; i < 10; i++ )
    {
        printf("%d", a[i]);
    }
    printf("\n\n");

    return 0;
}

在上述的代码中,只有子函数 HeapAdjust(int k[], int s, int n)较为难理解,他的过程实际上就是调节一个双亲结点和孩子节点之间顺序的作用,如果某一个孩子节点和双亲结点发生了互换,还要讨论互换后对下面的结点的影响。

由原始数组中的数字直接构成的二叉树如下所示

比如说当 i= 1的时候,执行的结果如下图所示,将最末端的数进行了重新排序,使其满足最大顶的要求

比如说当 i= 2的时候,执行的结果如下图所示

比如说当 i= 3的时候,执行的结果如下图所示
这里写图片描述

这个时候不仅仅将以 2 号结点为根节点的小树的顺序进行了互换,还将以 4 号结点为根节点的小树的顺序进行了互换,这个是十分重要的。

猜你喜欢

转载自blog.csdn.net/dugudaibo/article/details/79485606