算法学习(三)堆排序

要弄清楚堆排序,我们首先要懂得以下两点:

1)逻辑上的结构,怎么样才是一个堆。

2)存储上的结构,一个堆存储起来的结构是怎么样的。

一般来讲,堆排序中的“堆”指的是二叉堆,一种完全二叉树的结构,每个父结点最多只有两个子结点,且满足两点:

1)父结点总是大于(小于)其两个子结点(大于的,我们叫最大堆,小于的,我们叫最小堆)。

2)父结点的左右子树也都满足上面1)的规则,也就是说其左右子树也是一个最大堆或者是最小堆。

树结构,我们可以看下图:


一般来说,算法中提到的堆结构都是用数组来存储的,所以上面的这个二叉树放在数组中就是


从上面我们可以看到,按顺序从上到下,从左到右存储,比如“1”存在a[0],则其左子结点则存在a[2*0 + 1]=a[1]的位置上,其右子结点则存在a[2*0 + 2] = a[2]的位置上,而"2"的左结点是4,是存在a[2*1+1] = a[3]的位置上,其右结点5,则是在跟着的位置上,所以我们可以发现如果父结点的位置是i,则其左右子结点的位置分别2*i + 1 和 2*i + 2,这其实是因为完全二叉树就是2倍2倍往下变大的。

		int lchild = 2 * i + 1;
		int rchild = 2 * i + 2;
不过要注意,这只是将根节点放在a[0]的位置上,如果将根节点放在a[1]上,则左结点应该是a[2*1],而右结点则是a[2*1 + 1] = a[3]了,也就是说左右子结点的公式就变了:

		int lchild = 2 * i;
		int rchild = 2 * i + 1;

接下来,我们来看一下堆排序的原理。

一开始,其实数组是无序的,如下面一堆数据:


它是无序的,如果把它当成一个堆,它的结构就是下面这样,


它即不是最大堆,也不是最小堆。在这里,假设我们把它调节成最大堆(最小堆),

1)那么它的根结点一定是要比下面所有的子结点都大的,这样,我们就可以拿出这堆数据中最大的那个数,比如上图中的57。

2)我们将这个最大数从这个堆顶移走,然后再重新调整成最大堆,于是我们又跑到第1)步了,这样,我们又拿到一个最大数,把这个数拿走,我们将剩下的值继续调整成最大堆。这样,一次又一次,我们就能够顺序地将这堆数据由大到小地给拿出来了,而这其实就是一个排序过程,也就是堆排序。

而第一步,我们就要先把这个无序的数组给调整成一个最大堆的结构,调整过程如下图:


1)从第一个非叶子结点开始,也即是7开始,我们比较它跟其左右子结点的值,将三者中最大的值给放到顶上,所以就把7跟23给互换了位置,由于子结点已经没有子结点了,所以第一个就结束了。

2)那么就轮到其前面一个结点,也即是12了,同样的将其跟57互换位置,也结束了。

3)轮到9了,比较9跟其子结点的值,发现23比9还要大,这时候把23换上调,把9往下放,由于9还有子结点,所以就要继续往下比较,所以就把10给放上来,而9就沉到底下了,然后这一次结束。

4)那么就轮到根结点11了,比较发现,57比它要大,就把它跟57互换位置,而11调下来之后,其还有子结点,就要继续比较,而44比11大,所以44往上放,而11就继续往下放。

当所有非叶子结点都调整完了之后,建堆这个操作也就结束了,这个时候可以现,57,这个数组中最大的值已经给放到堆顶了。

而此时在数组中的结构也相对应地变了,如下:


这样,我们就能够把57跟9互换一下位置,然后拿数组前面8个数值再去继续调整,这是为了让要调整的数组永远是从0开始,改变的只是数组的长度,一直到最后调整的数组长度为1,就说明调整结束了,而这个时候,数组也已经就是有序的了。

利用最大堆,我们就可以把数组从小到大排,反之,利用最小堆,就可以把数组从大到小排。

下面就是根据这个原理而实现的堆排序的代码了:

package com.lms;
/**
 * 
 * @author linmiansheng
 * @date 2014-02-28
 *
 */
public class HeapSort {
 
	/**
	 * build a big root heap
	 * @param a
	 * @param i
	 * @param size
	 */
	public static void adjustHeap(int[] a, int i, int size){		
		int lchild = 2 * i + 1;
		int rchild = 2 * i + 2;
		
		int tmp = i;
		
		// i <= size/ 2 means i has children.
		if(i <= size/2 ){
			
			if(lchild < size && a[lchild] > a[tmp]){
				tmp = lchild;
			}
			
			if(rchild < size && a[rchild] > a[tmp]){
				tmp = rchild;
			}
			
			if(tmp != i){
				//either the left child or the right child, swap the two values.
				Helper.swap(a, tmp, i);
				adjustHeap(a, tmp, size);//continue to check the child and its children.
			}
		}
	}
		
	public static void buildHeap(int[] a,int size){
		for (int i = size /2; i >= 0; i--) {
			adjustHeap(a, i, size);
		}
	}
	
	public static void heapSort(int[] a) {
		int size = a.length;
		buildHeap(a, size);
		for (int i = 1; i < size; i++) {
			Helper.swap(a, 0, size - i);
			adjustHeap(a, 0, size - i);
		}
	}
	
	public static void main(String[] args){
		int[] a = { 11, 9, 12, 7, 4, 57, 44, 23, 10 };
		Helper.printArray(a);
		heapSort(a);
		Helper.printArray(a);
	}
}

同样的,我们也像前面 算法学习(二)快速排序(上)一样,来试试大数据量的时间:

		int[] a = Helper.generateRandomNumbers(20000000, 100000000);
		long start = System.currentTimeMillis();
		heapSort(a);
		long end = System.currentTimeMillis();
		long duration = end - start;
		Helper.print("duration = " + duration + "\n");
而所花的时间,如下:

duration = 24573
很明显,对于大数据量来说,其排序速度不如快速排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值