堆排序java实现

一.前提

1.为简单起见,讨论从小到大的整数排序
2.只讨论基于比较的排序(< = >有定义)
3.只讨论内部排序
4.稳定性:任意两个相等的数据,排序前后相对位置不发生变化


二.堆排序定义

堆排序(Heapsort) 是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆排序是选择排序的优化:选择排序



三.分类

1.算法1

(1)实现

根据传进来的数组创建一个最小堆,最小堆的第一个元素一定是最小的,所以我们每次删除根节点(数组第一个元素),把根节点放入我们新建的数组中,最后得到的新数组就是一个排好序的。
注意:每次删除操作不仅仅是删除根节点,删除之后要调整数组,使数组重新满足最小堆的要求
java提供了一个数据类型PriorityQueue-优先级队列,使用动态数组实现了最小堆,我们可以直接用。 (当然我们也可以自己实现最小堆)

//数据结构-最小堆
static int[] Heap_Sort(int[] array){
		int len = array.length;
		
		//创建最小堆,PriorityQueue内部维护的数组默认长度是11
		//为防止给的数组长度大于11,进堆时要扩展内部数组的长度
		//所以初始化时,确定内部数组的长度等于我们传入的数组的长度
		Queue<Integer> minHeap = new PriorityQueue<>(len);
		for(int e : array){
			minHeap.add(e);
		}
		
		//把堆顶(最小元素)投出,存在新数组中
		for(int i = 0; i < len; i++){
			array[i] = minHeap.poll();
		}
		return array;
	}




(2)时间复杂度

//nlog(n)
for(int e : array){
			minHeap.add(e);
		}
//nlog(n)
for(int i = 0; i < len; i++){
			array[i] = minHeap.poll();
		}
  • 两个都是nlongn,选一个来讲
  • foreach循环:T1(n) = O(n)
  • add()函数: (具体可以去看堆的插入)
         T2(n) = O(logn)
  • 总的时间复杂度:T(n) = O(nlogn)



(3)空间复杂度

Queue<Integer> minHeap = new PriorityQueue<>(len);

额外创建了一个新的数组

  • 空间复杂度:S(n) = O(n)



(4)说明

1.这个算法使用最大堆也可以
2.重要的是,这个方法额外创建了一个新的数组
3.所以我们优化后的算法2就是只用一个数组



2.算法2

(1)实现

想要只用一个数组,就不能使用PriorityQueue了,我们要自己写最大堆的实现。

//因为我没新建测试类,直接在本类测试了所以方法字段前面都加了static
	//当前堆的大小(不是数组的大小)
    static int size = 0;
	
	//堆排序
	static void Heap_Sort(int[] array){
		//数组为null或者数组元素小于等于1,不用排序
		if(null == array ||1 >= array.length) return;
		
		size = array.length;
		//建堆(就是把数组元素调整为符合最大堆的顺序)
		buildMaxHeap(array);
		
		//循环
		//把堆的根节点和堆的最后一个元素互换,堆的大小减一
		//此时根节点的左右子树是满足最大堆的定义的,只有根节点需要重新调整
		//根节点一路向下比较,直到满足最大堆的定义
		//当堆的大小为1时,不用比较了
		while(size > 0){
			swap(array, 0, size-1);
			size--;
			adjustHeap(array, 0);
		}
	}
	
	//建堆
	static void buildMaxHeap(int[] array){
		//从倒数第一个有孩子的节点开始调整
		for(int i = size/2-1; i >= 0; i--){
			adjustHeap(array, i);
		}
	}
	
	//调整堆
	//默认左右子树已经是堆了
	//现在要调整根节点
	static void adjustHeap(int[] array, int root) {
	//循环,当根节点有左孩子的时候循环
	//孩子必须在堆里,即孩子的下标不能超过size-1
	//而左孩子下标为2*root+1,再计算下就可以得到循环条件
		while((2*root + 2) <= size){
		//根节点和左孩子比
			int maxIndex = (array[root] < array[2*root+1]) ? 2*root +1 :root;
			//如果有右节点,上面比的那个最大值再和右孩子比
			if(2*root+1 < size-1 )
				maxIndex = (array[2*root+2] > array[maxIndex]) ? (2*root + 2) : (maxIndex);
			
			//如果根节点不是最大值,就还要循环
			if(root != maxIndex){
				swap(array, root, maxIndex);
				root = maxIndex;
			}
			else break;
		}
	}

	//交换数组元素
	static void swap(int[] array, int i1, int i2) {
		int temp = array[i1];
		array[i1] = array[i2];
		array[i2] = temp;
	}




(2)时间复杂度

while(size > 0){
			swap(array, 0, size-1);
			size--;
			adjustHeap(array, 0);
		}
  • while循环:T1(n) = O(n)
  • adjustHeap()函数:
         T2(n) = O(logn)
  • 总的时间复杂度:T(n) = O(nlogn)
    在这里插入图片描述



(3)空间复杂度

没有额外创建一个新的数组

  • 空间复杂度:S(n) = O(1)



四.参考

1.b站-【浙江大学】数据结构
2.csdn-超详细十大经典排序算法总结

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值