二叉堆

二叉堆定义

二叉堆和完全二叉树很像,在某种意义上来说是一样的,但是处理不一样。

二叉堆的节点大小没有按大小的要求,二叉堆是按优先级来定义的,列如根节点优先级最高,下面的其次,

再下面的又低一点,这就是大根堆,而根节点优先级最小,下面的大一些,则是小根堆,也可以用数字代表

优先级来应用到具体题目里面。因为堆是一棵完全二叉树,所以对于一个节点数为n的堆,它的高度不会超

过log2n所以对于插入,删除操作复杂度为O(log2n)

例如:

在这里插入图片描述 这个就是小根堆,上面所写的数字是用于方便大家理解,所写的数字代表优先级。怎么样,这和完全二叉树

是不是一样。

二叉堆的插入

先直接插入到堆底就是插入到5的左节点,例如插入一个0,此时的堆就是:

					1
				2		3
			   4        5      6          7
			8     9   0

然后由于子节点比父节点要小,所以0和5需要交换位置,此时就变成了:

     					1
       				2 		 3
       			    4       0          6    7
       		          8   9	  5

然后子节点比父节点还是要小,所以0和2也需要交换位置:

				1
			0		3
		    4       2	    6        7
		  8   9   5

然后0和1交换:

				0
			1	      3
		    4        2    6        7
		 8    9   5

直到子节点到根节点,或者子节点比父节点要大。

这就是插入:这里我用数组创建二叉树,因为是完全二叉树,所以直接用下标就可以了

void sert_tree(int num,int n)//num是要插入的数,n是长度 
{
 int b[10010];//记住指针
 int i=0,k=0,m=n+1;
 if(n==0)
 {
  a[1]=num;
  return;
 }
 n++;
 a[n]=num;//堆底插入数
 while(m>0)
 {
  b[k++]=m;
  m/=2;
 }
 int t;
 for(i=0;i<k-1&&a[b[i]]<=a[b[i+1]];i++)//往上交换 
 {
  t=a[b[i]];
  a[b[i]]=a[b[i+1]];
  a[b[i+1]]=t;
 }
}

二叉堆删除

拿上述插入来说:

				0
			1		3
		    4       2        6      7
		 8     9  5

删除根节点和删除其他的节点都是一样的,因为每一个都相当于一个小二叉堆。就删除0。
0先找子节点中小的子节点1,交换:

				1
			0		3
		    4       2       6       7
		 8     9   5

如此反复得到:

				1
			2		3
		    4      5	    6	    7
		   8  9  0

此时将0丢出去即可:

				1
			2		3
		    4      5 	      6     7
		  8   9   

这就是删除:这里我用数组创建二叉树,因为是完全二叉树,所以直接用下标就可以了

void del_tree(int n)//弹掉堆顶 
{
 swap(a[1],a[n]);//交换堆底和根部 
 n--;//弹掉堆底
 int i=1;
 while(i*2<=n)
 {
  int j=i*2;//左堆 
  if(j+1<=n&&a[j+1]<a[j])j=j+1;//判断需要交换哪个 
  if(a[j]<=a[i])swap(a[j],a[i]);
  else break;//如果满足子节点比父节点大,即结束 
  i=j;
 }
}

下面说一下例题:

题目:在一个果园里,达达已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。

达达决定把所有的果子合成一堆。

每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。

可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。

达达在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以达达在合并果子时要尽可能地节省体力。

假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使
达达耗费的体力最少,并输出这个最小的体力耗费值。

例如有3种果子,数目依次为1,2,9。

可以先将1、2堆合并,新堆数目为3,耗费体力为3。

接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。

所以达达总共耗费体力=3+12=15。

可以证明15为最小的体力耗费值。

输入格式

输入包括两行,第一行是一个整数n,表示果子的种类数。

第二行包含n个整数,用空格分隔,第i个整数aiai是第i种果子的数目。

输出格式

输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。

输入数据保证这个值小于231231。

数据范围

1≤n≤100001≤n≤10000,

1≤ai≤20000

输入:

3

1 2 9

输出:

15

输出原因:1+2=3,3+9=12,再将俩次体力加起来即15就是最小体力值。

根据题意我们不难得知题意就是将最小的俩个加起来然后将加起来这个数再存入里面继续比较,继续

加上最小的数。如果用数组做此题,时间复杂度就显得比较高,因为每一次删除和插入的时间复杂度

都是O(n),而用二叉堆就可以每次加上数值最低的俩个,插入和删除的时间复杂度就得以提高了。这

里我用数组创建二叉树,因为是完全二叉树,所以直接用下标就可以了:

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define ll long long
using namespace std;
int a[20010]={0};//用数组模拟二叉堆(完全二叉树)
void del_tree(int n)//弹掉堆顶 
{
 swap(a[1],a[n]);//交换堆底和根部 
 n--;//弹掉堆底
 int i=1;
 while(i*2<=n)
 {
  int j=i*2;//左堆 
  if(j+1<=n&&a[j+1]<a[j])j=j+1;//判断需要交换哪个 
  if(a[j]<=a[i])swap(a[j],a[i]);
  else break;//如果满足子节点比父节点大,即结束 
  i=j;
 }
}
void sert_tree(int num,int n)//num是要插入的数,n是长度 
{
 int b[10010];//记住指针
 int i=0,k=0,m=n+1;
 if(n==0)
 {
  a[1]=num;
  return;
 }
 n++;
 a[n]=num;//堆底插入数
 while(m>0)
 {
  b[k++]=m;
  m/=2;
 }
 int t;
 for(i=0;i<k-1&&a[b[i]]<=a[b[i+1]];i++)//往上交换 
 {
  t=a[b[i]];
  a[b[i]]=a[b[i+1]];
  a[b[i+1]]=t;
 }
}
int main()
{
 int i,j,k,m,n,a1;
 cin >> n;
 for(i=0;i<n;i++)
 {
  cin >> a1;
  sert_tree(a1,i);
 }
 ll ans=0;
 while(n>1)//只有1个节点即循环结束
 {
  if(n>=3)k=a[1]+(a[2]<a[3]?a[2]:a[3]);
  else k=a[1]+a[2];
  ans+=k;
  del_tree(n);
  n--;
  del_tree(n);//删除根之后第二小的就变成了根,所以继续删除就行了;
  n--;
  sert_tree(k,n);//把k插入进去就行了 
  n++;
 }
 cout << ans;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值