堆排序
 

堆的定义
    小根堆:ai<=a2i、ai<=a(2i+1)
    大根堆:ai>=a2i、ai>=a(2i+1)
    
    从堆的定义可以看出,堆实质是满足如下性质的完全二叉树;二叉树中任一非叶子结点均小于(大于)它的孩子结点
    
堆排序    
    若在输出堆顶的最小值(最大值)后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值(次大值)......如此反复,便能得到一个有序序列,这个过程称之为堆排序。
    
实现堆排序需解决两个问题:
    1.如何由一个无序序列建成一个堆?
    2.如何在输出堆顶元素后,调整剩余元素为一个新的堆?
    
如何在输出堆顶元素后,调整剩余元素为一个新的堆?
小根堆:
    1.输出堆顶元素之后,以堆中最后一个元素替代之;
    2.然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;
    3.重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”
    
筛选过程的算法描述为:
 

int rc,j;
void HeapAdujust(int R[], int s, int m) {
/*
* 已知R[s..m]中记录的关键字除R[s]之外均满足堆的定义,本函数
* 调整R[s]的关键字,使R[s]成为一个大根堆
*/    
    rc = R[s];
    for (j = 2 * s; j <= m; j *= 2) {//沿key较大的孩子结点向下筛选
        if (j < m && R[j] < R[j + 1])//j为key较大的记录的下表
            ++j;
        if (rc >= R[j])
            break;
        R[s] = R[j];
        s = j;//rc应插入在位置s上
    }
    R[s] = rc;//插入
}//HeapAdjust

筛选过程的算法解读:
    1.s是根节点下标,m是数组最大下标,j被赋值为s的左孩子下标
    2.第一个if对两个子节点进行比较,选最大的子节点,在第二个if中与根结点的值比较
    3.如果根结点的值更大则直接退出,保持不变
    4.否则将大的子节点的值赋值给根结点的位置,然后将子节点的下标赋值给s
    5.进行循环继续比较,循环结束后将之前存的根结点的值赋值在叶子结点上
    
堆的调整:
    对一个无序序列反复“筛选”就可以得到一个堆;即:从一个个无序序列建堆的过程就是一个反复“筛选”的过程。
    
堆的建立:

显然:
    单结点的二叉树是堆;
    在完全二叉树中所有以叶子结点    (序号i>n/2)为根的子树是堆。
即:    
    对应由n个元素组成的无序序列,“筛选”只需从第n/2个元素开始。
    
堆的建立: 
    由于堆实质上是一个线性表,那么我们可以顺序存储一个堆
    下面以一个实例介绍建一个小根堆的过程。
    
将初始无序的R[1]到R[n]建成一个小根堆,可用一下语句实现:

for(i=n/2;i>=1;i--)
        HeapAdjust(R,i,n);

    
堆排序
由以上分析知:
    若对一个无序序列建堆,然后输出根;重复该过程就可以由一个无序序列输出有序序列。
    实质上,堆排序就是利用完全二叉树中父结点与孩子结点之间的内在关系来排序的。
    
堆排序的算法如下:

void HeapSort(int R[]) {//对R[1]到R[n]进行堆排序
    int i,n;
    for (i = n / 2; i >= 1; i--)
        HeapAdujust(R, i, n);//建初始堆
    for (i = n; i > 1; i--) {//进行n-1趟排序
        swap(R[1], R[i]);//根与最后一个元素交换
        HeapAdujust(R, 1, i - 1);//对R[1]到R[i-1]重新建堆
    }
}//HeapSort

算法性能分析

初始堆化所需时间不超过O(n)
排序阶段(不含初始堆化)
    一次重新堆化所需时间不超过O(logn)
    n-1次循环所需时间不超过O(nlogn)
        Tw(n)=O(n)+O(nlogn)=O(nlogn)
    
    堆排序的时间主要耗费在建初始堆和调整建新堆时进行的反复筛选上。堆排序在最坏情况下,其时间复杂度也为O(nlog2n),这是堆排序的最大优点。无论待在排序列中的记录是正序还是逆序排列,都不会使堆排序处于“最好”或“最坏”的状态。
    另外,堆排序仅需一个记录大小供交换用的辅助存储空间。
    然而堆排序是一种不稳定的排序方法,它不适用于待排序记录个数n较少的情况,但对于n较大的文件还是很有效的。