六.堆排序
堆排序其实是选择排序的进化版本,回忆一下选择排序
把待排序列分成没有排序和已排序两部分,每一趟从未排序的序列中选择一个最大(最小)的元素插入到已排序的末尾处。
选择排序最耗费时间的地方就是从未排序的序列中选择一个合适的数,选择排序使用的是遍历整个未排序序列的方法,找到一个最大值(最小值),其实还可以用更加简单的方法。
树形选择排序
还是看序列 int a[] ={49 ,38 ,65 ,97 ,76 ,13 ,27 ,49}可以根据此序列构建一棵树,树根节点为序列中的最小值,取出这个最小值13,
取出最小值,并将最小值的位置设为无穷大
然后重新构建一棵树
又得到一个最小值27,取出27,设27的位置为无穷大
重新构建一棵树
又得到了一个最小值
。。。。。。。。。。
重复以上过程,可以得到一个取出的最小值的序列,13 27 38 …
这个序列就是已经排好的序列
—————————————————————————————
堆排序
堆排序也是这样的一个过程,而选择是通过堆这个数据结构实现的。
堆有两种,最大堆 最小堆
最大堆的堆顶元素是整个堆的最大值
最小堆的堆顶元素是整个堆的最小值
在这里以最小堆为例在开始排序之前,我们要建堆一个初始序列的完全二叉树表示方式
建堆过程如图所示:
从序列的最后一个元素开始上滤,如果当前节点元素比父节点要小,那么父节点下移,当前元素上移(父子交换位置)。直到建成堆为止。
堆建成之后,就正式开始排序了!!!和树形选择排序一样,每次选择根节点的元素(最小的元素),取出来,放在整个待排序序列的后面(树形选择排序是直接把那个元素放在另外的序列中)。
因为少了一个元素,就要重新考量一下,这还是一个最小堆吗??
1.先将待排序序列最后那个元素放在根节点上,此时根节点为当前节点
2.当前节点和它的两个孩子节点比较大小
3.若比左右孩子节点其中一个(或者两个)大,那这个元素就应该和两个孩子节点中较小的节点交换。
4.若比左右孩子节点都小,那么就不用移动。
重复 2 3 4过程,直到比较结束,我们就有建成了一个最小堆了。
既然有了这个最小堆,我们就继续把其中的最小值取出来,重复前面的操作。
以下是代码实现
ElementType a[] = {-1000,49,38,65,97,76,13,27,49};
void HeapSort(ElementType a[],int N)
{
//建堆
int i,j,temp;
for(i=N-1;i>0;--i){
temp = a[i];
j = i;
for(;a[j/2]>temp;j/=2){//如果当期节点元素小于父节点元素
a[j] = a[j/2];//父节点下移
}
a[j] = temp;//合适的位置插入元素
}
int Parent,Child,size;
int insert;
for(i=1;i<N;++i){//第0个节点为哨兵,设为负无穷
insert = a[1];//取得根节点,也就是当前的最小元素
size = N-i;//当前还有n-i个未排序元素,已排序元素放在数组第N-i+1---N-1的位置
temp = a[size];//取得未排序序列的最后一个元素
//下滤 ,从根节点开始
for(Parent=1;Parent*2<=size;Parent=Child){
Child = Parent*2;//左孩子节点为父节点的两倍
if(Child!=N-i&&a[Child]>a[Child+1])//如果左孩子比右孩子大
Child++;//Child指向右孩子
if(temp<=a[Child]) break;//如果待插入元素比左右孩子节点都要小,跳出循环
else a[Parent] = a[Child];//如果比左右孩子都打,那么就要下移
}
a[Parent]= temp;//插入元素
a[size] = insert;
}
}
分析:
堆排序对于数量较少的序列,并不是非常有效。它适用于对N较大的序列。
堆排序的时间复杂度为O(n*log(n)),即使在最坏的情况下,这是相对于快速排序来说最大的优点,即对初始序列不敏感。