堆一般分为大根堆和小根队,堆形象说是一棵完全二叉树;用数组实现堆时我一般将根节点从1开始,父节点编号为i,则左儿子节点编号为2i,右儿子编号为2i+1;堆的两个基本函数:插入元素到堆中(在此过程中建立好了初始堆),从堆中取出堆顶元素删除;实现堆的代码以具体题目写出;
第九次作业:
1、
(每次取出堆中data最大的节点然后输出name之后删除,堆空时输出Sleep)题目链接:
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define MAX_SIZE 100005
///*以下是以题目所要建堆类型的函数代码*/
typedef struct anode* Node;
typedef struct anode
{
char name[50];
int data;
}Anode;
int lenth=0; //先定义堆的大小为0;
Anode heap[MAX_SIZE];
void Insert_Heap(Anode p) //将一个节点插入到堆中,不论此时堆是否为空;
{
// /*此时将已存在的堆视为已经是一个标准堆*/
// /*此时树父节点时i(1开始),左儿子节点是2*i,右儿子是2*i+1;*/
int i=lenth; //插入数据之后的堆的大小,初始i为堆的最大编号,此时该位置未置入值;
while(i!=1&&p.data>heap[i/2].data)//假设p先插入到i位置,从i开始向上比较是否比父节点大,如果大,则交换值,并继续向上移动比较;
{
heap[i]=heap[i/2];
i/=2;
}
heap[i]=p; //1、插入堆的第一个元素;2、循环终止之后到达一个比父节点键值小的节点,将p插入储存到该节点;
}
void Delete_Heap() //无需传参数,因为每次都是将堆的最大顶即边界删除;
{
// /*将最大顶heap[1]原键值删除,将堆的最大编号键值插入到最大顶中,然后对堆进行重新调整;即交换头尾键值,然后对lenth-1大小的堆调整;*/
heap[1]=heap[lenth];
lenth--;
Anode tmp=heap[1];
int num=heap[1].data;
int fa=1,son=2; //最开始1是根(父)节点 ,每次先将son设为左儿子;
while(fa<=lenth&&son<=lenth)
{
if(son<lenth&&heap[son].data<heap[son+1].data)son++; //判断左儿子和右儿子大小,因为需要将交换后的根节点以下的最大值找出置入堆顶,所以判断更大值;
if(num>heap[son].data)break; //num(交换值)比该节点值大,则um位置找出,终止循环;
// /*还未找到num位置,则继续向下寻找,此时通过更替fa,son的值向下递进;*/
heap[fa]=heap[son];
fa=son;
son*=2;
}
heap[fa]=tmp; //即寻找到交换节点的位置之后,将其存储置入;
}
int main()
{
int m,i,j;
scanf("%d",&m);
for(i=1;i<=m;i++)
{
char oper[10];
scanf("%s",&oper);
if(oper[0]=='G')
{
if(lenth==0)printf("Sleep!
");
else
{
printf("%s
",heap[1].name);
Delete_Heap();
}
}
else if(oper[0]=='P')
{
lenth++; //需要插入一个节点,先使堆大小增加,类似开辟好一个节点存储将输入的节点 ;
scanf("%s %d",&heap[lenth].name,&heap[lenth].data);
Insert_Heap(heap[lenth]);
}
}
return 0;
}
2、(合并多堆石头,求总共花费体力值最少)题目链接:
#include<cstdio>
#include<cstdlib>
#include<algorithm>
int sum=0;
int lenth=0;
int stons[100005];
void Insert(int ston)
{
int i=lenth;
while(i!=1&&ston<stons[i/2])
{
stons[i]=stons[i/2];
i/=2;
}
stons[i]=ston;
}
void Delete()
{
int fa=1,son=2;
stons[1]=stons[lenth--];
int tmp=stons[1];
while(fa<=lenth&&son<=lenth)
{
if(son<lenth&&stons[son]>stons[son+1])son++;
if(tmp<stons[son])break;
stons[fa]=stons[son];
fa=son;
son*=2;
}
stons[fa]=tmp;
}
int main()
{
int n,i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
lenth++;
scanf("%d",&stons[lenth]);
Insert(stons[lenth]);
}
while(lenth>0)
{
int tmp1=stons[1]; Delete();
int tmp2=stons[1]; Delete();
sum+=tmp1+tmp2;
// printf("%d %d %d
",tmp1,tmp2,sum);
if(lenth==0)break;
lenth++;stons[lenth]=tmp1+tmp2;
Insert(stons[lenth]);
}
printf("%d
",sum);
return 0;
}
堆的实际应用:
1、优先队列(每次查询都是堆顶元素O(1),但是进队和出队每次都要重新调整堆O(nlogn)):首先可以直接通过建立堆实现优先队列,其次可以直接使用STL中的单调队列函数,需要包含文件
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<iostream>
#include<string>
#include<vector>
using namespace std;
/*自定义比较结构,数据类型为整型*/
struct cmp1
{
bool operator ()(int &a,int &b)
{
return a>b; //最小值优先;
}
};
struct cmp2
{
bool operator ()(int &a,int &b)
{
return a<b; //最大值优先;
}
};
/*自定义数据结构*/
struct number
{
int data;
string name;
bool operator < (const number &a)const
{
return data>a.data; //最大值优先;
}
};
int a[]={12,35,7,9,25,16,75,2,14,99};
number num[]={16,"jim",11,"tom",48,"kate",40,"sam",5,"hat"};
int main()
{
priority_queue<int>que; //采用默认优先级后遭优先队列;
priority_queue<int,vector<int>,cmp1>que1; //采用cmp1的大小关系优先;
priority_queue<int,vector<int>,cmp2>que2; //采用cmp2的大小关系优先;
priority_queue<number>que3; //采用自定义数据类型number中的重载函数大小优先;
int i;
for(i=0;a[i];i++) //将a数组中的值进入队列que,que1,que2;
{
que.push(a[i]);
que1.push(a[i]);
que2.push(a[i]);
}
for(i=0;num[i].data;i++)
{
que3.push(num[i]);
}
printf("默认优先关系输出:
");
while(!que.empty())
{
printf("%d ",que.top());
que.pop();
}
printf("
");
printf("采用cmp1大小关系优先级输出:
");
while(!que1.empty())
{
printf("%d ",que1.top());
que1.pop();
}
printf("
");
printf("采用cmp2大小关系优先级输出:
");
while(!que2.empty())
{
printf("%d ",que2.top());
que2.pop();
}
printf("
");
printf("采用自定义数据类型重载函数优先级输出:
");
while(!que3.empty())
{
cout<<que3.top().name<<" "<<que3.top().data<<endl;
que3.pop();
}
cout<<endl<<endl<<endl;
return 0;
}
2、堆排序:堆排序在之前的排序博客中已经写出,博客链接
这里写到C语言中的结构体的重载函数写法和C++中的类似;后续详细复习补上;