Java实现堆排序

(二叉)堆是一个完全二叉树,分为大根堆和小根堆。大根堆是指树中除根结点外的所有结点的值都小于等于其父结点的堆;小根堆是指树中除根节点外的所有结点的值都大于等于其父结点的堆。从定义可知,大根堆的根结点是堆中的最大结点,小根堆的根结点是堆中的最小结点。本文基于大根堆实现对数组元素排序。大根堆的图形描述如下:

基于大根堆实现对数组元素排序有三个步骤:

(1)实现下降结点的子程序;

(2)实现建堆子程序;

(3)实现堆排序。

接下来具体描述。


(1)实现下降结点的子程序

结点i的左子树和右子树都是大根堆,但是以结点i为根结点的子树可能不是大根堆,也就是说,结点i可能小于其子结点,这时需要在树中下降i结点的值,使以结点i为根结点的子树成为大根堆。实现该功能的伪代码如下:

Java代码如下:

	/**
	 * 数组从下标1开始存储堆中元素;
	 * 
	 * @param a 保存堆得数组
	 * @param i 堆中需要下降的元素
	 * @param heapLength 数组中最右边堆元素的位置
	 */
	public <T extends Comparable<T>> void maxHeapify(T[] a, int i, int heapLength) {
		int l = left(i);
		int r = right(i);
		int largest = 0;
		/**
		 * 下面两个if条件句用来找到三个元素中的最大元素的位置largest; 
		 * l <= heapLength说明l在数组内,i非叶子结点;
		 */
		if (l <= heapLength && a[i].compareTo(a[l]) < 0) {
			largest = l;
		} else {
			largest = i;
		}
                // r <= heapLength说明r在数组内
		if (r <= heapLength && a[largest].compareTo(a[r]) < 0) {
			largest = r;
		}
		// 如果i处元素不是最大的,就把i处的元素与最大处元素交换,交换会使元素下降
		if (i != largest) {
			T temp = a[i];
			a[i] = a[largest];
			a[largest] = temp;
			// 递归下沉
			maxHeapify(a, largest, heapLength);
		}
	}

(2)实现建堆子程序

建堆就是使堆满足大根堆的特性,也就是使堆中所有结点都满足大根堆的特性。假设有保存堆的数组A[1...n],我们很容易知道,结点n的双亲结点(n/2,整数除法)之后的节点都是叶子结点,这些结点可以理解为是只包含一个元素的大根堆,它们满足大根堆性质,我们只需要对从n/2结点开始到1结点的所有结点调用maxHeapify方法使之满足大根堆性质,从而使整个二叉树成为一个大根堆。之所以要从n/2结点开始,是因为使用maxHeapify需要保证结点的子树是大根堆,而在数组中孩子结点一定在双亲结点的右边,所以首先可以保证的就是n/2结点的孩子结点是大根堆。

建堆的伪代码如下:

Java代码如下:

	public <T extends Comparable<T>> void buildMaxHeap(T[] a, int heapLength) {
		int lengthParent = parent(heapLength);
		// 最初,parent(length)之后的所有元素都是叶子结点
		for(int i = lengthParent; i >= 1; i--){
			maxHeapify(a, i, heapLength);
		}
	}

(3)实现堆排序

我们知道,大根堆的根结点是最大的节点,所以,只需要重复这个步骤就能对数组中的元素排序:从数组中取出第一个元素,然后把最后一个元素放在根节点的位置,然后堆得长度减1,然后对第一个元素调用maxHeapify方法使之满足大根堆性质。这样我们就能得到一系列从大到小排列的元素,但在实际中,我们是交换第一个元素和最后一个元素,最后数组就变为一个数组元素从小到大排列的数组。

堆排序伪代码如下:

上面的循环中没有到达1,是因为2结束时,堆中只有一个元素了,它是有序的。

Java代码如下:

	public <T extends Comparable<T>> void sort(T[] a) {
		// TODO Auto-generated method stub
		// 最初,堆的长度就是整个数组的长度
		int heapLength = a.length - 1;
		buildMaxHeap(a, heapLength);
		for(int i = heapLength; i >= 2; i--){
			T temp = a[i];
			a[i] = a[1];
			a[1] = temp;
			heapLength--;
			maxHeapify(a, 1, heapLength);
		}
	}

堆排序类的完整代码如下:

public class HeapSort{

	public <T extends Comparable<T>> void sort(T[] a) {
		// TODO Auto-generated method stub
		// 最初,堆的长度就是整个数组的长度
		int heapLength = a.length - 1;
		buildMaxHeap(a, heapLength);
		for(int i = heapLength; i >= 2; i--){
			T temp = a[i];
			a[i] = a[1];
			a[1] = temp;
			heapLength--;
			maxHeapify(a, 1, heapLength);
		}
	}

	public int left(int i) {
		return 2 * i;
	}

	public int right(int i) {
		return 2 * i + 1;
	}

	public int parent(int i) {
		// i为根结点
		if (i == 1) {
			return 0;
		}
		return i / 2;
	}

	/**
	 * 数组从下标1开始存储堆中元素;
	 * @param a 保存堆得数组
	 * @param i 堆中需要下降的元素
	 * @param heapLength 数组中最右边堆元素的位置
	 */
	public <T extends Comparable<T>> void maxHeapify(T[] a, int i, int heapLength) {
		int l = left(i);
		int r = right(i);
		int largest = 0;
		/**
		 * 下面两个if条件句用来找到三个元素中的最大元素的位置largest; 
		 * l <= heapLength说明l在数组内,i非叶子结点;
		 */
		if (l <= heapLength && a[i].compareTo(a[l]) < 0) {
			largest = l;
		} else {
			largest = i;
		}
        // r <= heapLength说明r在数组内
		if (r <= heapLength && a[largest].compareTo(a[r]) < 0) {
			largest = r;
		}
		// 如果i处元素不是最大的,就把i处的元素与最大处元素交换,交换会使元素下降
		if (i != largest) {
			T temp = a[i];
			a[i] = a[largest];
			a[largest] = temp;
			// 递归下沉
			maxHeapify(a, largest, heapLength);
		}
	}

	public <T extends Comparable<T>> void buildMaxHeap(T[] a, int heapLength) {
		int lengthParent = parent(heapLength);
		// 最初,parent(length)之后的所有元素都是叶子结点
		for(int i = lengthParent; i >= 1; i--){
			maxHeapify(a, i, heapLength);
		}
	}
	
	public static void main(String[] args) {
		Integer[] heap = new Integer[]{0, 3, 1, 5, 12, 7};
		new HeapSort().sort(heap);
		for(Integer integer : heap){
			System.out.println(integer);
		}
	}

}
参考 算法导论 第六章 堆排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值