堆排序算法个人理解


一、基础知识

堆的结构可以分为大根堆小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序。

  • 完全二叉树(Complete Binary Tree): 除了最后一层之外的其他每一层都被完全填充,并且所有结点都保持向左对齐。
    在这里插入图片描述

1.1 大根堆和小根堆

大根堆:每个结点的值都大于其左孩子和右孩子结点的值。
小根堆:每个结点的值都小于其左孩子和右孩子结点的值。

在这里插入图片描述

在这里插入图片描述

2.2 查找数组中某个数的父结点和左右孩子结点

对于 i 点,有:
父亲节点为: ( i − 1 ) / 2 (i-1)/2 (i1)/2;
左儿子为: 2 ∗ i + 1 2*i+1 2i+1;
右儿子为: 2 ∗ i + 2 2*i+2 2i+2;

对于大小根堆映射的数组,他们满足堆的定义性质。所以有:

大根堆 a r r ( i ) > a r r ( 2 ∗ i + 1 ) arr(i)>arr(2*i+1) arr(i)>arr(2i+1) && a r r ( i ) > a r r ( 2 ∗ i + 2 ) arr(i)>arr(2*i+2) arr(i)>arr(2i+2)

小根堆 a r r ( i ) < a r r ( 2 ∗ i + 1 ) arr(i)<arr(2*i+1) arr(i)<arr(2i+1) && a r r ( i ) < a r r ( 2 ∗ i + 2 ) arr(i)<arr(2*i+2) arr(i)<arr(2i+2)


二、堆排序

2.1 基本思想

  1. 将待排序的数组构造成一个大根堆,此时整个数组的最大值就是堆结的顶端
  2. 将顶端的数与末尾的数交换,此时末尾的数为最大值,剩余待排序数组个数为n-1
  3. 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

2.2 模拟过程

我们以数组 [ 7 , 14 , 9 , 21 , 9 , 10 , 12 ] [ 7,14,9,21,9,10,12 ] [7,14,9,21,9,10,12] 为例:

初建堆为

我们会发现根节点的数并不符合,明显 7 小于 14,所以我们要进行交换,将7与14 交换后,7 所在的树又不符合大根堆的性质,所以我们也要将右子树上最大的数交换到右子树的根节点上(左子树同理)

可以发现的是:一次堆建立完之后,我们的最大值就在了堆的根节点上
随后将堆顶最大值和数组最后的元素进行替换,我们就完成了一趟排序了。

在这里插入图片描述
接下来,剩下的数不断进行建堆,交换就可以完成我们的堆排序了

在这里插入图片描述

2.3 动态图演示

在这里插入图片描述
在这里插入图片描述

三、代码

堆排序的时间复杂度O(N*logN), 额外空间复杂度O(1),是一个不稳定性的排序

   //交换数组中两个元素的值
void swap(int[] arr, int i, int j)
{
	int temp = arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}
//堆排序
void heapSort(int[] arr)
{
	//构造大根堆
	heapInsert(arr);
	int size = arr.length;
	while (size > 1)
	{
		//固定最大值
		swap(arr, 0, size - 1);
		size--;
		//构造大根堆
		heapify(arr, 0, size);

	}
}
//构造大根堆(通过新插入的数上升)
void heapInsert(int[] arr)
{
	for (int i = 0; i < arr.length; i++)
	{
		//当前插入的索引
		int currentIndex = i;
		//父结点索引
		int fatherIndex = (currentIndex - 1) / 2;
		//如果当前插入的值大于其父结点的值,则交换值,并且将索引指向父结点
		//然后继续和上面的父结点值比较,直到不大于父结点,则退出循环
		while (arr[currentIndex] > arr[fatherIndex])
		{
			//交换当前结点与父结点的值
			swap(arr, currentIndex, fatherIndex);
			//将当前索引指向父索引
			currentIndex = fatherIndex;
			//重新计算当前索引的父索引
			fatherIndex = (currentIndex - 1) / 2;
		}
	}
}
//将剩余的数构造成大根堆(通过顶端的数下降)
void heapify(int[] arr, int index, int size)
{
	int left = 2 * index + 1;
	int right = 2 * index + 2;
	while (left < size)
	{
		int largestIndex;
		//判断孩子中较大的值的索引(要确保右孩子在size范围之内)
		if (arr[left] < arr[right] && right < size)
		{
			largestIndex = right;
		}
		else
		{
			largestIndex = left;
		}
		//比较父结点的值与孩子中较大的值,并确定最大值的索引
		if (arr[index] > arr[largestIndex])
		{
			largestIndex = index;
		}
		//如果父结点索引是最大值的索引,那已经是大根堆了,则退出循环
		if (index == largestIndex)
		{
			break;
		}
		//父结点不是最大值,与孩子中较大的值交换
		swap(arr, largestIndex, index);
		//将索引指向孩子中较大的值的索引
		index = largestIndex;
		//重新计算交换之后的孩子的索引
		left = 2 * index + 1;
		right = 2 * index + 2;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值