咱们常常会碰到下面这种状况,并不须要将全部数据排序,只须要取出数据中最大(或最小)的几个元素,如排行榜。java
那么这种状况下就可使用优先队列,优先队列是一个抽象数据类型,最重要的操做就是删除最大元素和插入元素,插入元素的时候就顺便将该元素排序(实际上是堆有序,后面介绍)了。编程
二叉堆实际上是优先队列的一种实现,下面主要讲的是用数组实现二叉堆。数组
先上一个实例:ide
若有一个数组A{9,7,8,3,0,6,5,1,2}ui
用二叉树来表示数组更直观:spa
从这张图咱们能够总结一些规律:3d
上面这三点应该很是好理解code
下面就引出一个问题,怎样让一个数组变成堆有序呢? blog
首先,须要介绍两个操做: 排序
当插入一个结点,或改变一个结点的值时,上浮指的是交换它和它的父节点以达到堆有序
在上面的堆有序的图中,若是咱们把0换成10,那么上浮的操做具体为:
(1)10比它的父节点7大,因此交换
(2)交换后,10比它的父节点9还要打,交换
以后获得的二叉树以下图:
代码以下(须要注意,下标是从1开始,A[0]保留不用,如下全部代码相同):
//index based on 1 public void swim(Integer[] a,Integer key) { while(key > 1 && a[key/2] < a[key]) { change(a,key/2,key); key /= 2; } }
2. 由上至下的堆有序化(下沉)
由上浮能够很容易得出下沉的概念:
当插入一个结点,或改变一个结点的值时,下沉指的是交换它和它的较大子节点以达到堆有序。
在原来的二叉树中,若是将根节点9换成4,操做以下:
(1)4与它的最大子节点8交换位置
(2)4与它的最大子节点6交换位置
交换后的二叉树以下图:
代码以下:
//index based on 1 public void sink(Integer[] a,Integer key) { Integer max = key*2; while(key*2 < a.length - 1) { if(a[key*2] < a[key*2 + 1]) { max = key*2 + 1; } else { max = key*2; } if(a[key] > a[max]) break; change(a,key,max); key = max; } }
那么将一个数组构形成有序堆,相应的也有两种方法:使用上浮以及使用下沉:
初始数组以下:
Integer[] a = {null,2,1,5,9,0,6,8,7,3};
上浮构造有序堆:
从数组左边到右边依次使用上浮,由于根节点A[1]没有父节点,因此从A[2]开始:
public void buildBinaryHeapWithSwim(Integer[] a) { for(int k=2;k<a.length;k++) { swim(a,k); } }
结果以下:
a: [null ,9 ,7 ,8 ,5 ,0 ,2 ,6 ,1 ,3] 读者有兴趣能够本身画一下二叉树,看是否有序
下沉构造有序堆:
代码: public void buildBinaryHeapWithSink(Integer[] a) { //index based on 1 for(int k=a.length/2;k>=1;k--) { sink(a,k); } }
为何使用下沉只须要遍历数组左半边呢?
由于对于一个数组,每个元素都已是一个子堆的根节点了,sink()对于这些自对也适用。若是一个结点的两个子节点都已是有序堆了,那么在该结点上调用sink(),可让整个数组变成有序堆,这个过程会递归的创建起有序堆的秩序。咱们只须要扫描数组中一半的元素,跳过叶子节点。
a: [null ,9 ,7 ,8 ,3 ,0 ,6 ,5 ,1 ,2]
能够看到使用下沉和上浮构造出来的有序堆并不相同,那么用哪个更好呢?
答案是使用下沉构造有序堆更好,构造一个有N个元素的有序堆,只需少于2N次比较以及少于N次交换。
证实过程就略过了。
前面说了那么多,终于要说到堆排序了,其实前面的优先队列和二叉堆都是为了堆排序作准备。
如今咱们知道若是将一个数组构形成有序堆的话,那么数组中最大的元素就是有序堆的根节点。
那么很容易想到一个排序的思路:
第一种:将数组构形成有序堆,将根节点拿出来,即将A[1]拿出(由于A[0]不用,固然也可使用,读者能够本身编程实现),对剩下的数组再构造有序堆……
不过第一种思路只能降序排列,而且须要构造一个数组用来存放取出的最大元素,以及最大的弊端是取出最大元素后,数组剩下的其它全部元素须要左移。
那么第二种办法就能够避免以上的问题:
第二种:先看图:
先来解释下这幅图:
…….
这样循环下去,即获得按升序排序的数组
代码:
public void heapSort(Integer[] a) { for(int k=a.length/2;k>=1;k--) { sink(a,k); } Integer n = a.length - 1; while(n > 0) { change(a,1,n--); //去除最后一个元素,即前一个有序堆的最大元素 sink(a,1,n); } }
注意在while循环中,sink()方法多了一个参数,这个参数的目的是去掉上一个有序堆的最大元素。
所有代码以下:
public class HeapSort extends SortBase { /* (non-Javadoc) * @see Sort.SortBase#sort(java.lang.Integer[]) */ @Override public Integer[] sort(Integer[] a) { // TODO Auto-generated method stub print("init",a); heapSort(a); print("result",a); return null; } public void buildBinaryHeapWithSink(Integer[] a) { //index based on 1 for(int k=a.length/2;k>=1;k--) { sink(a,k); } } public void buildBinaryHeapWithSwim(Integer[] a) { for(int k=2;k<a.length;k++) { swim(a,k); } } public void heapSort(Integer[] a) { for(int k=a.length/2;k>=1;k--) { sink(a,k); } Integer n = a.length - 1; while(n > 0) { change(a,1,n--); //去除最后一个元素,即前一个有序堆的最大元素 sink(a,1,n); } } //index based on 1 public void swim(Integer[] a,Integer key) { while(key > 1 && a[key/2] < a[key]) { change(a,key/2,key); key /= 2; } } //index based on 1 public void sink(Integer[] a,Integer key) { Integer max = key*2; while(key*2 < a.length - 1) { if(a[key*2] < a[key*2 + 1]) { max = key*2 + 1; } else { max = key*2; } if(a[key] > a[max]) break; change(a,key,max); key = max; } } public void sink(Integer[] a,Integer key,Integer n) { Integer max = key*2; while(key*2 < n) { if(a[key*2] < a[key*2 + 1]) { max = key*2 + 1; } else { max = key*2; } if(a[key] > a[max]) break; change(a,key,max); key = max; } } public static void main(String[] args) { Integer[] a = {null,2,1,5,9,0,6,8,7,3}; //(new HeapSort()).sort(a); (new HeapSort()).buildBinaryHeapWithSink(a); print("a",a); } }
堆排序的平均时间复杂度为NlogN