堆排序原理详解和代码实现

什么是堆?

堆是利用完全二叉树实现的一种数据结构,分为大根堆和小根堆:
大根堆:每个节点的值都大于等于其左右孩子的值。
小根堆:每个节点的值都小于等于其左右孩子的值。

堆在数组中的存储

  • 根节点存储在数组a[0]的位置,后面的数值按照广度优先顺序依次填入数组中。
  • 已知父节点的索引为i,其左孩子坐标为2*i+1,右孩子的坐标为2*i+2

堆排序

排序步骤

  1. 首先将待排序的数组构造成一个大根堆,此时数组中最大的值应该是堆顶端的值,也就是数组下标为a[0]的值。
  2. 将堆顶的元素与最末尾的数字交换。此时最大值的位置转移到了数组的末尾处。
  3. 将剩余的 n − 1 n-1 n1个元素重新构建大根堆,重复步骤2,将堆顶元素与这 n − 1 n-1 n1个元素的最后一个交换。
  4. 重复上述步骤,就能得到一个升序的数组。
  5. 如果想要得到降序的数组,就构建小根堆。

构建大根堆

构建的大根堆需要自下而上进行构建

  1. 首先定义一个parent游标从后向前移动,找到最后一个有孩子的节点。

  2. 定义child游标,指向parent的左孩子。

  3. 判断当前parent是否存在右孩子。(根据完全二叉树的性质,有孩子就一定有左孩子,右孩子有没有不一定)

  4. 如果有右孩子,比较左右孩子的大小,child指向左右孩子中大的那个。
    1

  5. parentchild节点进行比较,如果父节点大,parent继续向上移动。

  6. 如果父节点小,父子节点进行交换,child游标继续指向child的左右孩子中的最大值进行对比,直至child指向null或者parent的值大于child
    2

  7. 重复上述步骤直至parent游标指向堆顶元素。
    3
    4
    5
    6

堆顶和堆底元素交换

堆顶和堆底元素交换之后,去掉最后一个元素重新构建一个大根堆。

7
8
9
10

代码实现

//检查一个节点是否符合大顶堆
public static void adjust(int[] arr, int parent, int length) {
	int child = 2 * parent + 1;
	while (child < length){
		int rchild = child + 1;
		if (rchild < length && arr[child] < arr[rchild]){ //如果右孩子的索引小于数组的有效长度,并且右孩子大,child已经指向左右孩子中的最大值
			child ++;
		}
		
		if(arr[parent] < arr[child]) {//如果父节点小于孩子节点
			int temp = arr[parent];
			arr[parent] = arr[child];
			arr[child] = temp;
			
			//递归处理孩子的孩子节点
			parent = child;
			child = 2 * child + 1;
		} else {
			break;
		}
	}
}

public static void main(String[] args) {
	int[] arr = {5,7,4,2,0,3,1,6};
	//1.构建大顶堆
	for (int p = arr.length - 1; p >= 0; p --){
		adjust(arr,p,arr.length);
	}
	System.out.println(Arrays.toString(arr));
	
	for(int i = arr.length - 1; i >= 0; i --){
		//2.堆顶堆底进行交换
		int temp = arr[i];
        arr[i] = arr[0];
        arr[0] = temp;
        System.out.println(Arrays.toString(arr));
        //3.剩余元素继续构建大顶堆
        adjust(arr,0,i);
	}
}

运行结果

复杂度分析

  • 构建大顶堆的过程中,假设每个元素都需要调整,根据二叉树的性质,最顶端的节点最坏的情况下也不过调整 l o g n logn logn 次,因此一共 n n n 个元素,复杂度不会超过 O ( n l o g n ) O(nlogn) O(nlogn)
  • 在将堆顶堆底元素进行交换的过程中,假设每个元素都会移动 l o g n logn logn 次,共 n n n 个元素,复杂度也不会超过 O ( n l o g n ) O(nlogn) O(nlogn)
  • 这两次操作是平行的而不是嵌套的,因此最终排序的复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值