数据结构——堆结构的总结

关于堆结构从以下几个方面总结关于此数据结构的问题:

  • 生产中的常见问题
  • 堆的定义
  • 堆的基本操作
  • 堆排序
  • 堆在生产中的应用

生产中的常见问题

  1. 优先队列的应用场景很广,它是如何实现的呢?
  2. 如何求解TopK问题
  3. TP99是生产中的一个非常重要的指标,如何快速计算

堆的定义

  1. 堆是一颗完全二叉树,这样实现的堆也被称为二叉堆
  2. 堆中节点的值都大于等于(或小于等于)其子节点的值,堆中如果节点值都大于等于其子节点的值,我们称这个数据结构就是大顶堆,反之如果都小于等于其子节点的值,那么就称为小顶堆

(完全二叉树就是它的叶子节点都是在最后一层,并且这些叶子节点都是靠左排序的

在这里插入图片描述
在上图当中1、2是属于大顶堆,3是属于小顶堆。4不属于大顶堆和小顶堆,因为在叶子节点当中不是靠左排序的。

可以注意的是无论是在大顶堆还是在小顶堆当中,节点的值都是大于左右子节点的值,并且左右子节点的值是没有特殊的规定的。(即未规定左右节点的排列方式)

问题:堆的底层该如何表示呢?
因为完全二叉树可以用数组来表示,又因为堆是一颗完全二叉树,所以堆也可以使用数组来表示。
在这里插入图片描述
如图示:给完全二叉树从上到下从左到右进行编号排序,则对于任意一个节点来说,很容易得知如果它在数组的位置为i,则它的左右子节点就在数组的2i和2i+1,通过这种方式可以定位到树种的每一个节点。


堆的基本操作

堆有两个基本的操作,构建堆(往堆中插入元素)与删除堆顶元素。

  • 往堆中插入元素
    往堆中插入元素,往往是在叶子节点(最底层)先插入节点,再进行调整,直到满足堆的特点。(堆中节点值都大于等于/小于等于其子节点的值),将这个过程称为堆化(heaplify)
public class Heap {
	private int[] arr;		//堆是完全二叉树
	private int capacity;	//堆中能存储的最大元素数量
	private int n;			//当前堆中的元素
	
	public static void swap(int[] arr,int i,int j) {
		int k = arr[i];
		arr[i] = arr[j];
		arr[j] = k;
	}
	
	public Heap(int count) {
		capacity = count;
		arr = new int[capacity+1];
		n = 0;
	}
	
	public void insert(int value) {
		if(n > capacity) {
			return;		//超过了堆的大小就不可以插入元素了
		}
		n++;
		//先将元素插入到队尾中
		arr[n] = value;
		int i = n;
		//这里的判断条件就是如果插入的元素大于它的父节点,那么这个元素就需要上移
		while(i/2 > 0 && arr[i] > arr[i/2]) {
			swap(arr,i,i/2);
			i = i / 2;
		}
	}
	
	
}

这种调整方式是先将元素插入到堆的最后,然后自下而上不断进行调整比较子节点和父节点的值,称为由下而上的堆化

时间复杂度就是树的高度O(logn)

  • 删除堆顶元素
    假设操作的堆是大顶堆,则删除堆顶元素后,要找到原堆中第二大的元素以填补顶堆元素,而第二大的元素无疑就是在根节点的左右节点当中,假设是左节点,则用左节点来填补堆顶元素后,左节点就空了。之后的过程就是进行不断地循环。
    在这里插入图片描述
    但是,在调整后,在最终调整堆当中,出现了数组空洞,对应数组如下:
    在这里插入图片描述
    解决方案:用最后一个元素覆盖堆顶元素,然后再自上而下调整堆,让其满足大顶堆的要求,即可解决数组空洞的问题。

问题:Java默认的数组排序(Array.sort())底层也是用的是快速排序,时间复杂度和快速排序一样快,为什么堆排序在实际生产当中不受欢迎

主要是以下的两个原因:

  1. 快速排序在递归排序过程中,都是拿pivot与相邻元素进行比较,会使用到计算机中一个非常重要的原理:局部性原理。

什么是局部性原理?可以简单理解为CPU读取到某个数据的时候,它认为这个数据附近相邻的数据也有很大概率会被用到,所以干脆将被读取到数据的附近数据也一起加载到cache当中,这样下次还需要进行读取操作的时候,就直接从cache里拿数据就可以了(无需再从内存当中进行读写操作了)。如果数据量大的话,就可以有极大的性能提升了。
堆排序无法利用局部性原理,因为在堆化的过程当中,需要不断比较节点与其左右子节点的大小,左右子节点也需要比较其左右子节点。
在这里插入图片描述
如图示:在节点2自上而下的堆化过程中,其要遍历的数组有4,5,9,10…中的元素,这些元素不是相邻元素,无法利用局部性原理进行提升性能

  1. 我们知道堆排序的一个重要步骤是将堆顶元素进行移除,重新进行堆化,每次堆化都会导致大量的元素比较,这也是堆排序性能较差的一个原因。
  2. 堆排序不是稳定排序,因为堆化开始前需要将首位和末尾元素进行交换,如果两元素值一样,那么就可能会改变他们的相对顺序。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值