基础数据结构及其应用
一,栈
1,普通栈
2,单调栈
3,对顶栈
一,队列
1,普通队列
2,单调队列
三,堆
1、二叉堆
对象: 以数为元素的集合(数组)
特性:O(1)的取出数组最大值(大根堆)或者最小值(小跟堆)
维护: 1,向上修改 (用于加入新元素) 2,向下修改(用于删除最值点(根节点))
手段: 逐层交换
操作1 插入:
二叉堆是完全二叉树,所以,树的根节点是1,对于任意非根节点,父亲=x/2,儿子=x * 2 或 x * 2 + 1
void inss(int x) 小跟堆插入
{
nums++; 堆内数字个数统计更改
int i=nums;
s[i]=x;
while (i/2&&s[i/2]>s[i])swap(s[i],s[i/2]),i/=2; 向上操作
}
void insb(int x) 大跟堆插入
{
numb++;
int i=numb;
b[i]=x;
while (i/2&&b[i/2]<b[i])swap(b[i],b[i/2]),i/=2;
}
操作2 取堆顶并删除:
由于根在儿子确定后非常唯一,所以需要讨论的其实只有左右儿子谁大或者(谁小),并记录那个孩子的序号,父亲可能就是和他换了,所以每次循环op操作的是初始op的儿子,这姑且算是一个小改变 (编者自编,正确性未知~~)
范围说: op只能到倒数第二层,留一层给他的儿子在循环中操作
int popb() 大根堆取顶删除操作
{
int ans=b[1];
swap(b[1],b[numb]); b[numb]=0; numb--;
int op=1;
while (op*2<=numb) 由于可能删除后某些 二叉树只有左儿子,所以先判左儿子
{
op*=2; //向下到左儿子
if(op+1<=numb&&b[op+1]>b[op])op++; 右儿子没有超范围,且由于维护了大根,我们只要儿子中最大的
if(b[op]>b[op/2])swap(b[op],b[op/2]); 若最大的儿子比父亲大,换!否则就止步于此了
else break;
}
return ans; 反正 我只要堆顶(dogeeee!)
}
int pops() 小根堆取顶删除操作 (注释同上,懒得搬了)
{
int ans=s[1];
swap(s[1],s[nums]); s[nums]=0; nums--;
int op=1;
while (op*2<=nums)
{
op=op*2;
if(op+1<=nums&&s[op+1]<s[op])op++;
if(s[op]<s[op/2])swap(s[op],s[op/2]);
else break;
}
return ans;
}
2、对顶堆
联系: 一个大根堆和一个小跟堆的集合
特性: 取出数组中第k大的数,并且动态改k也是可以的
思维:小根堆维护第k个以及比k大的所有,大根堆维护比第k个小的所有(俩堆由于k联系,如同头顶相对一样)
操作1 维护:
以维护小跟堆为主,当小跟堆的数据数高于k,取对顶并插入大根堆,恒令小根堆有k个元素
cin>>x;
inss(x);
if(nums>k)insb(pops());
操作2 删取第k大的数:
小根堆弹出,大根堆抽数补充
cout<<pops()<<endl;
inss(popb());
操作3 改k并维护:
k若是大于小根堆数目,就抽小根入大根,反之就抽大根入小跟(总之保证k==size of 小根堆)
cin>>k;
if(k>nums)inss(popb());
if(k<nums)insb(pops());
若是要第K小,就就是维护大根堆为主