走进数据结构之排序(六)---堆排序

一、堆排序算法分析

堆排序分为两个阶段:

1、将一个数据序列建成最小/大堆,则根结点值最小/大

2、进行选择排序,每趟将最小值(根结点值)交换到后面,再将其余值调整成堆,依此重复,直到子序列长度为1,排序完成。使用最小/大堆,得到排序的结果是降/升序的。

二、代码实现

package top.einino.selectsort;

public class HeapSort {

//交换keys[i]与keys[j]元素,i、j范围由调用者控制
private static void swap(int[] keys, int i, int j){
int temp = keys[j];
keys[j] = keys[i];
keys[i] = temp;
}

//堆排序,当minheap为true时,创建最小堆,降序排序,否则创建最大堆,升序排序
public static void heapSort(int[] keys){
heapSort(keys, true);
}

private static void heapSort(int[] keys, boolean minheap) {
//创建最小/大堆,根结点最小/大
  for(int i=keys.length/2-1; i>=0; i--){
    sift(keys, i, keys.length-1, minheap);
   }
   //每趟将最小/大值交换到后而,再调整成最小/大堆
   for(int i=keys.length-1; i>0; i--){
    //交换key[0]与keys[i]
    swap(keys, 0, i);
    sift(keys, 0, i-1, minheap);
   }
}

//将keys数组中以parent为根的子树调整成最小/大堆,子序列范围为parent~end
//私有方法,只被 堆排序方法调用,确保parent,end在范围内
private static void sift(int[] keys, int parent, int end, boolean minheap) {
System.out.print("sift "+ parent + ".." + end + " ");
//child是parent的左孩子
 int child = 2 * parent + 1;
int value = keys[parent];
//沿较小/大值孩子结点向下筛选
   while(child <= end){
    if(childkeys[child+1]:keys[child]
//记住孩子值较小/大值
   child++;
    }
//若父母结点值较小/较大者
   if(minheap?value>keys[child]:value
     //将较小/大孩子结点值上移
     keys[parent] = keys[child];
     //parent、child两者都向下一层
     parent = child;
     child = 2 * parent + 1;
    }else{
     break;
    }
   }
   //当前子树的原根值调整的位置
   keys[parent] = value;
   print(keys);
  }
//输出排序数组
private static void print(int[] keys) {
for(int key : keys){
System.out.print(key+" ");
}
System.out.println();
}
public static void main(String[] args) {
int[] keys = {81, 49, 19, 38, 76, 13, 19};
//测试降序(最小堆)
//heapSort(keys);
//测试升序(最大堆)
int[] keys2 = {81, 49, 19, 38, 76, 13, 19};
heapSort(keys2, false);
}
}

三、形象的小例子

体育老师就是尽职!又来排队了!

初始队列:A学生181,B学生149,C学生119,D学生138,E学生197,F学生176,G学生113、H学生119

老师开始创建最小堆(降序)

自己在草稿纸上依顺序将学生画成完成二叉树,然后按0~n进行从上往下、从左往右进行给学生标个号码牌,然后老师根据n/2-1=3进行对D学生的两个孩子结点进行比较,但从图可以看到D学生的孩子结点只有H学生,所以就将H学生与D学生进行比较,结果D>H,所以就将H学生移到D学生的位置,并将D学生放到H学生的位置,再计算D学生在原来H学生的位置的两个孩子结点,很明显没有孩子结点,即不再对D学生进行操作了

现在从上往下、从左往右的排序是:A学生181,B学生149,C学生119,H学生119、E学生197,F学生176,G学生113、D学生138

再进行对C学生(从D学生往左依次操作),对C学生的两个孩子结点(F、G)进行比较,结果F>G,所以进行C学生与F、G中的较小值进行比较,也就是C与G的比较,结果C>G,所以将G排到C的位置,很明显G原来的位置没有孩子结点了,所以将C直接排到G原来的位置

现在从上往下、从左往右的排序是:A学生181,B学生149,G学生113,H学生119、E学生197,F学生176,C学生119D学生138

再进行对B学生操作,对B学生的两个孩子结点(H、E)进行比较,结果HH,所以将H排到B的位置,再进行H原来位置的两个孩子结点的比较,很明显只有D,所以直接进行B与D的比较,结果B>D,所以将D排到H原来的位置上,而且D原来的位置没有孩子结点了,所以就把B排到D原来的位置

现在的排序是A学生181,H学生119,G学生113,D学生138、E学生197,F学生176,C学生119 、B学生149

再进行对A学生操作,进行A学生的两个孩子结点(H、G)的比较,结果H>G,所以就进行A与G的比较,结果A>G,所以将G学生排到A学生的位置,再进行G原来位置的两个孩子结点(F、C)的比较,结果F>C,所以进行A与C的比较,结果C

此时老师创建的最小堆就大功告成了,保证了每一棵的子树的孩子结点都比父结点大,此时

排序是G学生113,H学生119,C学生119 、D学生138、E学生197,F学生176,A学生181,B学生149

接着老师要从树的根结点把一个个最矮的同学排到最后面了!

首先是将G学生拉出队伍,并将其排到最后,并把创建好的最小堆的最后一位学生B学生排到根结点,再对B学生进行上述对“A的相似操作”,进行H与C的比较,结果H=C,所以进行H与B操作,结果B>H,所以把H学生排到B的位置,进行H原来位置的两个孩子结点的比较,结果D

现在老师将G拉出队伍,把A放到根结点,再进行同样创建最小堆的操作,对A进行操作,最后的排序是C、D、F、B、E、A,还有已经排好序的H119、G113

现在老师将C拉出队伍,把A放到根结点,再进行同样创建最小堆的操作,对A进行操作,最后的排序是D、B、F、A、E,还有已经排好序的C119、H119、G113

现在老师将D拉出队伍,把E放到根结点,再进行同样创建最小堆的操作,对E进行操作,最后的排序是B、A、F、E,还有已经排好序的D138、C119、H119、G113

现在老师将B拉出队伍,把E放到根结点,再进行同样创建最小堆的操作,对E进行操作,最后的排序是F、A、E,还有已经排好序的B149、D138、C119、H119、G113

现在老师将F拉出队伍,把E放到根结点,再进行同样创建最小堆的操作,对E进行操作,最后的排序是A、E,还有已经排好序的F176、B149、D138、C119、H119、G113

现在老师将A拉出队伍,把E放到根结点,再进行同样创建最小堆的操作,对E进行操作,最后的排序是E,还有已经排好序的A181、F176、B149、D138、C119、H119、G113

最后把E学生排到最前面即可:E197、A181、F176、B149、D138、C119、H119、G113

结束了排序!

四、堆排序的时间复杂度

将一个数据序列调整为堆的时间复杂度为O(log2(n)),因此堆排序的时间复杂度为O(n*log2(n))

五、堆排序的空间复杂度

堆排序的空间复杂度为O(1)

六、稳定性

堆排序算法是不稳定的,因为堆排序遵从的是从左到右的原则,如果完全二叉树的相同元素的排序是一个在左,一个在右,这样可能就错过比较的机会,导致相同关键字排序发出颠倒。本博文例子没有突出这一点,但大家可以自己出一道题试试,比如A81、B19、C49、D39、E49、F19,降序排序的最后结果是A81、C49、E49、D39、F19、B19,可以看到F与B的位置发生变化,所以堆排序是不稳定的!

七、小结

本博文从堆排序算法分析,升序和降序代码实现,老师排队的例子讲演,时间复杂度,空间复杂度以及稳定性介绍了堆排序的方方面面。

如果有疑问或者对该博文有何看法或建议或有问题的,欢迎评论,恳请指正!

你可能感兴趣的:(数据结构)