原題連結:https://www.luogu.com.cn/problem/P3378
題意解讀:實現二叉堆。
解題思路:
二叉堆本質上一棵完全二叉樹,根節點稱為堆頂,根據特性不同分為有兩種:
大根堆:所有父節點的值大於子節點,根節點最大
小根堆:所有父節點的值小於子節點,根節點最小
主要作用:動態維護序列,並快速找到最大/最小值,或者查詢topN的值
主要操作:插入(O(logN))、查詢最大/最小值(O(logN))、刪除最大/最小值(O(logN))
儲存方式:用陣列來模擬完全二叉樹int s[N],s[i]的左子結點為s[2 * i],右子節點為s[2 * i + 1],父節點為s[i / 2]
下面介紹三種主要操作:
設初始堆中有4個元素:2、3、4、5,且為大根堆
用陣列儲存為s[1] = 5, s[2] = 3, s[3] = 4, s[4] = 2
插入元素:
設插入元素6,先將6放置在陣列末尾,即s[5] = 6,
然後進行向上比較,s[5]=6與s[2]=3比較,不符合大根堆性質,交換元素,此時s[2]=6,s[5]=3,
繼續向上比較,s[2]=6與s[1]=5比較,依然不符合大根堆性質,交換元素,此時s[2]=5,s[1]=6
查詢堆頂:
直接返回s[1]
刪除堆頂:
先將堆頂和末尾元素進行交換,swap(s[1], s[5])
再從堆頂進行向下比較,先看s[1]=3和子節點s[2]=5,s[3]=4,s[2]更大,因此要交換swap(s[1], s[2])
再看s[2]=3和子節點s[4]=2,符合大根堆性質,不用交換
注意不用考慮s[5]=6的元素了,因為交換到末尾意味著將其刪除,陣列s的長度-1即可。
100分程式碼:
注意此題中,要用小根堆,判斷的方式有所區別而已。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005;
int n, s[N], cnt;
void up(int p)
{
if(p / 2 < 1) return;
if(s[p] < s[p / 2])
{
swap(s[p], s[p / 2]);
up(p / 2);
}
}
void down(int p)
{
int left = p * 2;
int right = p * 2 + 1;
if(left > cnt) return;
int minx = left;
if(right <= cnt && s[right] < s[minx]) minx = right;
if(s[minx] < s[p])
{
swap(s[minx], s[p]);
down(minx);
}
}
int main()
{
cin >> n;
int op, x;
while(n--)
{
cin >> op;
if(op == 1)
{
cin >> x;
s[++cnt] = x;
up(cnt);
}
else if(op == 2) cout << s[1] << endl;
else if(op == 3)
{
swap(s[1], s[cnt]);
cnt--;
if(cnt) down(1);
}
}
return 0;
}