堆——数据结构

首先,要说明一下本篇文章说的堆和JVM内存结构里边的堆是两个东西。它是优先级队列的底层数据结构。

  • new出来的空间(new一个对象)-------->这里的堆指的是一块具有特殊作用的内存空间(JVM内存区域中)
  • 优先级队列的底层数据结构 ------->这里的堆是一种数据结构

那么什么是堆这种数据结构呢?

堆的基本概念

这个堆是建立在完全二叉树的存储方式上,我们知道完全二叉树适合用顺序的存储方式来存储,因为它是没有满的满二叉树,所以通过数组来顺序存储效率是比较高的。

堆的概念:
堆就是一棵完全二叉树,采用顺序的方式(数组)来高效存储。
在这里插入图片描述

堆的分类: 堆又分为大根堆(根节点的值比左右孩子都大)和小根堆。如上图所示,就是一个小根堆。

堆的性质:
将元素存储到数组中后,可以根据二叉树的性质对树进行还原。假设i为节点在数组中的下标,则有:

  • 如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
  • 如果2 * i + 1小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
  • 如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子

堆的常用方法

1、构建一个堆

我们这里以构建一个小堆为例,小堆的特点就是这棵二叉树每个根节点的值都小于左右孩子的值。
我们知道,堆其实就是一个按顺序存储的二叉树,只要给定了数组,我们就可以按照数组的下标将这棵树构建出来,比如给定的数组为:[27,15,19,18,28,34,65,49,25,37 ],那么我们就可以构建出如下图的二叉树:
在这里插入图片描述
我们会发现,按照数组下标创建出来的堆,并不满足大根堆或者小根堆,对于根节点的左右子树都是满足根节点小于左右节点的值,只有根节点大于左右子树,那么此时只需要将根节点向下调整好即可。

向下调整

我们这里以调整为小根堆为例,当根节点的左右子树都满足小堆的性值时,即 使用一次向下调整就可以满足堆的性值。那我们来看看如何实现这个向下调整函数:

思路:如果根节点比左右孩子都大:找到较小的孩子将其与根节点进行交换。依次向下循环,直到叶子节点

代码:

public void shiftDown(int[] array,int parent)//parent是下标
{
	int child=parent*2+1;//孩子的下标

	while(child<array.length)//此时只能保证有左孩子
	{
	//找到左右孩子较小的(右孩子存在的前提下)
		if(child+1<array.length && array[child+1]<array[child])
		{
			child=child+1;
		}
		//开始向下调整

		//检测双亲是否比较小的孩子大
        if(array[parent]>array[child])
        {
        	//交换
			int tmp=array[parent];
			array[parent]=array[child];
			array[child]=tmp;
			//向下更新孩子和双亲的下标
			parent=child;
			child=parent*2+1;
		}else{
			return;
		}
	}
}

注意: 在调整以parent为根的二叉树时,必须要满足parent的左子树和右子树已经是堆了才可以向下调整。

时间复杂度分析: 最坏的情况即图示的情况,从根一路比较到叶子,比较的次数为完全二叉树的高度,即时间复杂度为O(log2(n))

堆的创建

堆的创建其实也就是将完全二叉树调整为堆(以小堆为例),我们先分两种情况:

  • 如果根节点的左右子树都已经时小堆了,那么 只需要一次向下调整.就可以成为小堆
  • 如果根节点的左右子树都不满足堆的性质,那麽我们就需要从左右子树的 最下面的二叉树开始调整,依次向上,直到根节点。所以关键就是需要找到倒数第一个非叶子节点(也就是调整的第一个节点)
    • 倒数第一个非叶子节点就是最后一个节点的双亲,最后一个节点的下标是:arr.length-1;所以倒数第一个非叶子节点的下标就是:( (arr.length-1) -1)/2===>(arr.length-2)/2

代码:

public void createHeap(int[] array) {
	//从倒数第一个非叶子节点开始调整
	int adjust=(arr.length-2)/2;
	for(i=adjust;i>=0;i--)
	{
		shiftDown(array,adjust);
	}
}

时间复杂度分析: 最坏情况:O(n*log(2n));最优(只需要一次向下调整):O(log(2n))

2、插入元素

每次插入的时候是向数组中插入元素,所以每次都是插在数组尾部。所以需要向上一层一层调整将整个完全二叉树.

步骤:

  1. 先检测是否需要扩容
  2. 先将元素放入到底层空间中:尾插
  3. 将最后新插入的节点向上调整,直到满足堆的性质
public boolean offer(int x)
{
	//1.先检测是否需要扩容,我们这里以简单的2倍进行扩容
	if(size==array.length)
	{
		array,length=2*array.length;
	}
	//2.将元素尾插到数组中
	size+=1;
	array[size]=x;	
	//3.向上调整
	shiftUp(size-1);
	return true;
}
//向上调整
private void shiftUp(int child)
{
	int parent=(child-1)/2;
	while(child!=0)
	{
		if(array[parent]>array[child])
		{
			//交换
			int tmp=array[parent];
			array[parent]=array[child];
			array[child]=tmp;
			//更新下标
			child=parent;
			parent=(child-1)/2;
		}
	}
}

3、删除元素

注意:堆的删除一定删除的是堆顶元素。 所以步骤如下:

  1. 判断堆是否为空
    1. 空:return
    2. 不为空:将最后一个元素和堆顶元素进行交换,并删除堆中最后一个元素(也就是将之前的堆顶元素删除)
  2. 调整0号下标这棵树(一次向下调整即可)

代码:

public void pop()
{
	//判断堆是否为空
	if(array.length==0)
	{
		return;
	}
	int tmp=array[0];
	array[0]=array[size-1];
	array[size-1]=tmp;
	//删除
	array.length-=1;
	//一次向下调整
	shiftDown(array,0);
}

堆的应用

堆这种数据结构应用主要有两个方面:

  1. 用堆作为底层结构封装优先级队列:将在后续的优先级队列的包括中具体写出
  2. 堆排序:具体见排序的博客:链接: 八大排序(1).
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值