排序算法学习之堆排序

一、堆与堆排序的产生及定义

     在简单选择排序中,每次从n个元素中比较n-1次选取最小的元素,这很好理解,但是前面比较过的数据在之后还要重新比较,这将花费大量的运算时间。堆排序算法就很好的解决了这个问题,堆排序在每次选择到最小记录的同时会根据比较结果对其他数据进行调整,堆排序的时间复杂度为O(NlogN).

    堆通常是指二叉堆,即堆是一颗完全二叉树,同时其满足一定性质:每个节点的值大于等于其左右孩子的值(大顶堆),或者每个节点的值小于等于其左右孩子的值(小顶堆)。堆在本质上是一个数组,根节点即为a[0],节点a[i]的左右孩子分别为a[2*i+1]和a[2*i+2],父节点为a[(i-1)/2](注意根节点是从0下标开始的,如果不是从0下标开始,则换算公式有些许变化)。

二、堆排序算法(用大顶堆分析,小顶堆同理)

    对一个满足大顶堆性质的数组,其最大值即是根节点,将其与最后一个元素交换,于是最大值就移动到数组尾部,然后对剩下的n-1个数据进行调整使之成为大顶堆,于是又得到第二大元素,如此重复即可完成数组的排序。相比于之间选择排序,堆排序在每次调整时保留了之前得到的某些结果,故而效率更高 。

    实现过程需要解决两个问题:

1)如何将给定的无序数组构造成大顶堆

2)输出堆顶元素后如何调整使之成为新的堆

    给定数组建成一颗无序的完全二叉树后,将其叶子节点单独来看,就是一个大顶堆,在此基础上进行调节,即可将一颗无序数组构造成一颗大顶堆,所以总的来说只需解决第二个问题就OK。

    那么如何调整呢?我们可以用“补空白”的思想来理解,原先的堆顶元素输出后,就形成了一个“空白”,将这个空白节点的左右孩子中较大的数 (max = max{a[i],a[i+1]}) 与待插入数据temp比较,如果max>temp,就将max填入父节点空白处,这样在它的位置又形成了一个空白,如此继续向下循环直到temp>=max或者到达二叉树底端,将temp填入最后留下的空白处,于是完成了一次调整。代码如下:

/*array[start+1] ~ array[end]已经满足堆的定义,调整使得array[start] ~ array[end]满足堆定义*/
void HeapAdjust (int start, int end, int array[])
{
    int i;
    int temp = array[start];    /*产生第一个空白*/
    for (i=2*start+1; i<=end; i=2*i+1)    /*每次循环时空白节点为array[(i-1)/2]*/
    {
        if (i<end && array[i] < array[i+1])    /*在左右孩子中寻找较大值*/
            i++;
        if (array[i] > temp)
            array[(i-1)/2] = array[i];
        else
            break;
    }
    array[(i-1)/2] = temp;    /*插入原来的temp到空白处*/
}

   关键问题解决之后哦,接下来的就容易多了:①先将无序数组构造成大顶堆;②将根节点交换到数组末端;③调整形成新的堆。代码如下:

void HeapSort (int n, int array[])
{
    int i;
    for (i=(n-2)/2; i>=0; i--)        /*构造大顶堆*/
        HeapAdjust(i, n-1, array);

    for (i=n-1; i>0; i--)
    {
        int t = array[i];    /*将根节点交换到数组末端*/
        array[i] = array[0];
        array[0] = t;
        
        HeapAdjust(0, i-1, array);    /*重新调整堆*/
    }
}
原文地址:https://www.cnblogs.com/lifan/p/3718476.html