大顶堆删除最大值_C/C++后台开发学习笔记(九)-二叉堆

84dacdae5a6f168ee8935b9735e66996.png

首先恭喜您,能够阅读到这篇文章《二叉堆》,如果对部分理解不太明白,建议先将文章收藏起来,然后对不清楚的知识点进行查阅,然后在进行阅读,相应你会有更深的认知。如果您喜欢这篇文章,就点个赞或者【关注我】吧!

一、二叉堆简介

(1) 简介

二叉堆本质上是一种完全二叉树,它分为两个类型。

  1. 最大堆:任何一个父节点的值,都大于或等于它左右孩子结点的值。
  2. 最小堆:任何一个父节点的值,都小于或等于它左右孩子结点的值。

5876381b239056fa46316c8d41b3cafe.png

二叉堆的根结点叫做堆顶,最大堆的堆顶是这个堆中的最大值,最小堆的堆顶是这个堆中的最小值

(2)二叉堆代码结构

二叉堆的存储方式不是链式结构,而是用顺序存储的,因为这个二叉堆就是一个完全二叉树,用数组存储也是合理利用空间。

这里以最小堆为例介绍,优先队列是最大堆,这样两个都可以介绍了。

我们看的时候还是要画成二叉树的风格,这样有利于我们理解,

b9b6991e230ef6e0a55fb5f22a8b95b6.png


这个就是最小堆,是不是很想二叉树,不过跟二叉树性质不一样,最小堆的父节点是最小值,排序二叉树的父节点是中间值。

代码中的存储结构:

773b874f14081d09a952efca83e48684.png


就是这样的顺序结构,这里没有指向左右孩子的指针,那怎么找到左右孩子呢?
这个我们就要利用数组的下标了,假设父节点的下标为parent,左孩子的下标是2 * parent + 1 ,右孩子的下标为 2 * parent + 2。
**假设孩子结点的下标为child,那么父节点的下标为 (child - 1)/ 2. **
这个很重要,在代码中就是移动的关键。

代码:

typedef Elemtype int;

typedef struct binHeap
{
	int size;			//data数组的长度
	int len;			//有对少有限数据
	Elemtype *data;		//data数据
}_binHeap;

(3)二叉堆的创建

二叉堆的创建比较简单,跟创建一个栈差不多,都是申请数据,填充长度信息

/**
    * @brief  创建binHeap对象
    * @param  
    * @retval 
    */ 
    struct binHeap *binHeap_creat(int size)
    {
		struct binHeap *heap = (struct binHeap *)malloc(sizeof(struct binHeap));
		assert(heap);

		Elemtype *data = (Elemtype *)malloc(sizeof(Elemtype) *size);
		assert(data);

		heap->len = 0;
		heap->data = data;
		heap->size = size;
	
        return heap;
    }

/**
    * @brief  销毁binHeap对象
    * @param  
    * @retval 
    */ 
    int binHeap_destroy(struct binHeap *heap)
    {
		assert(heap);

		if(heap->data)
			free(heap->data);

		if(heap)
			free(heap);
		
		return 0;
    }

二、二叉堆插入

(1) 插入介绍

又来到我们插入环节,这个二叉堆的插入也是比较简单的,二叉堆的插入就两个步骤:
第一,插入到最后一个元素,二叉堆前面的元素都已经符合了最大堆或最小堆的性质,所以新添加的要加入到最后一个元素里。
第二,上浮,新加的一个元素不可能就一定会满足最大堆或最小堆的性质,所以需要调整,这个调整就是上浮。

(2)插入

二叉堆的插入比较简单,从上面都已经看到二叉堆的存储结构是数据,所以只要找到数据最后一个元素插入即可。

代码:

/**
    * @brief  二叉堆插入
    * @param  
    * @retval 
    */ 
	int binHeap_insert(struct binHeap *heap, Elemtype data)
	{
		//判断数据元素是否已经超出
		if(heap->size <= heap->len + 1)   //可以实现扩容
			return -1;

		heap->data[heap->len] = data;

		minHeap_upAdjust(heap);
	
		return 0;
	}

是不是看着很简单,minHeap_upAdjust这个函数下节讲,这才是插入的重点。

(3) 上浮

插入比较简单的话,那就是调整比较复杂了,像红黑树那样,调整起来多难受,不过这个二叉堆调整起来也简单,只要不断的把最小结点往上提即可。

简单的插入过程:

dcb203492ede1db5b22f37f708244805.png


插入1,然后可以往上浮:

bd9798c177029ea71375b316348a95ba.png


还不满足,继续上浮:

2de249c69ca69f3e588dabe4d19f81f4.png


这样就完成了一个上浮操作,结点多的时候,也是这个道理。

代码:

/**
	* @brief  最小堆调整
	* @param  
	* @retval 
	*/ 
	static int minHeap_upAdjust(struct binHeap *heap)
    {
		//调整的时候,就是不断的和父节点比较,然后判断是否替换父节点

		int child = heap->len - 1; 
		int parent = (child - 1)/2;
		Elemtype temp = heap->data[heap->len-1];

		//开始循环比较
		while(child)
		{
			
			//跟第一个父节点比较
			if(temp < heap->data[parent]){
				//上浮
				heap->data[child] = heap->data[parent];
				child = parent;
				parent = (child - 1)/2;
			}
			
		}

		heap->data[child] = temp;
		return 0;
	}

三、 二叉堆删除

(1) 删除介绍

删除跟插入是反方向的,不过大体逻辑一样,也是先删除在调整,废话就不多说了,直接下节,删除。

(2) 删除

二叉堆的删除跟插入不一样,插入是插入到最后一个元素,但是删除是删除第一个元素,为什么删除第一个呢?因为第一个元素是这个堆中的最大值或者最小值,我们用了二叉堆这个数据结构,就是必然对最大值或最小值感兴趣,所以删除的是第一个元素。

代码:

/**
* @brief  二叉堆删除
* @param  
* @retval 
*/ 
int binHeap_delete(struct binHeap *heap)
	{
		//判断数据元素是否已经超出
		if(heap->size <= 0)   //可以实现扩容
			return -1;

		int head = heap->data[0];

		heap->data[0] = heap->data[heap->len-1];
		heap->len--;

		minHeap_downAdjust(heap);
	
		return head;
	}

(3)下沉

下沉步骤:

faae0f070b52240563a1516649893ba7.png


删除1,然后最后一个元素4替换到1的位置:

bf019edb8c73ba80322de444061b88e0.png


然后开始下沉:

288eb06c49ff048ca65acfe0971fca66.png


然后下沉完成,我画的比较简单,不过原理确实这样。

代码:

/**
	* @brief  最小堆下沉
	* @param  
	* @retval 
	*/ 
	static int minHeap_downAdjust(struct binHeap *heap)
	{
   //调整的时候,就是不断的和孩子结点比较,然后获取到那个孩子结点的值比父节点的小,替换父节点

		int parent = 0;
		int left_child = 2 * parent + 1;
		int right_child = 2 * parent + 2;
		int child = left_child;

		Elemtype temp = heap->data[parent];
		
		while(parent < heap->len)
		{
			//如果存在右孩子,并且右孩子小于左孩子,
         if(right_child < heap->len && heap->data[right_child] < heap->data[left_child]) {
				child = right_child;  //父节点要跟右孩子比较
			}

			//判断父节点如果小于等于最小孩子结点,就不用移动
			if(temp <= heap->data[child])
				break;

			//这个需要下沉
			heap->data[parent] = heap->data[child];
			parent = child;
			left_child = parent * 2 + 1; 
			right_child = parent * 2 + 2; 
			child = left_child; 
		}

		heap->data[parent] = temp;
		return 0;
	}

四、构建二叉堆

构建二叉堆其实就是堆排序,想看详细步骤可以看《二、排序算法下》中的堆排序,这里也是就不描述了。(本来是要写的,但是上面写的代码都是基于优先队列的写的,所以这里就不写了)

五、 优先对列

队列我们前面已经讲过了,就是先进先出,入队操作,元素加到队尾,出队操作,首元素出队。

优先对垒有什么特殊的呢?
优先队列分为两种情况:最大优先队列,无论入队顺序如何,都是当前最大的元素优先出队最小优先队列,无论入队顺序如何,都是当前最小的元素优先出队

看到这两种情况就想到我们的二叉堆,二叉堆会把最小值或最大值调整到第一个元素。
出队的时候,获取到第一个元素即可,然后再调整,就想到上面写的删除操作。
入队操作:这个就更简单,直接插入到队尾,然后调整,这个跟上面的插入操作一样。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值