堆排序代码详解(Java实现)

本文深入讲解堆排序算法,包括其核心思想与实现细节,并通过具体示例帮助读者理解堆排序的过程。文章还提供了堆排序的Java实现代码,并对其性能进行了评估。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

堆排序是一种高级排序算法,时间复杂度为O(nlogn),空间复杂度仅为O(1),对于海量数据使用堆排序是相当高效的。

相比其他排序算法,堆排序确实比较难,不仅体现在思想上,更体现在代码逻辑上。在《数据结构与算法分析(Java语言描述)》所给的堆排序伪代码中,有一个很巧妙的地方,值得我们反复推敲。当然在看代码前,大家先自行搞懂堆排序思想。

堆排序第一个步骤是构造二叉堆(按二叉树理解也没问题)。二叉堆是由数组实现的,假设我们要对一个数组进行升序排列,就得构造大根堆;否则,就得构造小根堆。我们当然是进行升序排列,所以要构造大根堆。

//arr : 数组
//n : 数组前n个数的长度。为什么不是数组长度?因为后面的数是已经排好序的,
//	  所以不必再进入二叉堆。这里不理解不要紧,后面堆排序中大家就能明白了。
//i : 从左往右,从下往上第一个非叶结点的数组下标。即 i = n / 2 - 1

//另外一个重要的点,下面这个函数只是让以某个非叶结点为根的子堆局部构成大根堆,
//而整个数组构成大根堆是循环遍历的结果。
private static void adjustHeap(int[] arr, int n, int i){
	int child; //子节点
	int temp;  //存储结点i的值
	//下面这个for循环,temp初始化为arr[i]后便永恒不变了(巧妙1);
	//每轮循环的判断条件是看当前结点是否有左子节点;
	//子节点成为当前结点(巧妙2),继续向下构造大根堆(因此构造大根堆是一个自顶向下的过程)
	for (temp = arr[i]; i * 2 + 1 < n; i = child){
		child = i * 2 + 1;
		if (child + 1 < n && arr[child] < arr[child + 1])
			++child; //存在右子节点且节点值比左子节点大(自然会交换最大的)
		if (temp < arr[child]) //当前节点值小于子节点值,要交换
			arr[i] = arr[child];//(巧妙3,看下面解释)
		else break;
	}
	arr[i] = temp; //temp最终位置,是最后一次赋值来的,而不是一次次交换的结果
}

上面说了,当前节点值小于子节点值时,逻辑上是肯定要交换的,不然就不是大根堆。但是大家也会疑惑,后面代码压根就不是交换,只是把子节点值赋给了当前结点。

我前面说了,堆排序适合于排序海量数据,在最差情况下,如果每两个数之间都要进行一次O(3)的交换,肯定会带来很大的开销。看到这里,大家又疑惑了,这不是跟我前面说的自相矛盾吗?

大家结合一下代码中的巧妙1,巧妙2,巧妙3。有没有这样一种体会:代码里没有实现交换,但是逻辑上的确交换了。每次构造中,temp是不变的,若有继续向下构造大根堆的机会,依然是用temp来比较,却是早就交换了哈哈!

构造二叉堆是堆排序的核心与重难点,大家一定要反复理解透彻。相比之下,接下来的堆排序不过是循环使用adjustHeap罢了。

public static void heapsort(int[] arr){
	for (int i = arr.length / 2 - 1; i >= 0; i--) //整体形成大根堆
		adjustHeap(arr, arr.length, i);
	for (int i = arr.length - 1; i >= 0; i--){
		//每次交换大根堆的第一个数和最后一个数
		int temp = arr[0];
		arr[0] = arr[i];
		arr[i] = temp;
		//交换后的最后一个数已经放在排序后的最终位置
		//对剩余数重新构造大根堆
		//(只需对根节点构造大根堆,因为根节点以下的子堆早已是局部大根堆了)
		adjustHeap(arr, i, 0);
	}
}

最后我测试以下的堆排序的性能:

public static void main(String[] args) {
        int[] arr = new int[9999999];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int)(Math.random()*arr.length);
        }
        long start = System.currentTimeMillis();
        heapsort(arr);
        long end = System.currentTimeMillis();
        System.out.println("堆排序耗时:" + (end - start) + "ms");
}

在这里插入图片描述

我前面比较过希尔排序的耗时,结合这次的实践证明,堆排序比希尔增量和Hibbard增量下的希尔排序快了1/3,略慢于Sedgewick增量下的希尔排序
《希尔排序在三种著名增量下的时间性能比较》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值