1. 程式人生 > 實用技巧 >【C/C++】C和C++11之enum列舉的使用細節

【C/C++】C和C++11之enum列舉的使用細節

目錄

Updating...

前置芝士

  1. 二叉搜尋樹(Binary Search Tree,BST)

普通平衡樹

模板:

普通平衡樹的基本操作可以參考上方模板

1. 替罪羊樹

眾所周知 每種平衡樹都有一種操作來維護平衡。

替罪羊樹是暴力維護平衡的(

替罪羊樹要存 左右兒子,節點值,子樹實際大小(前面的一般平衡樹都要用),子樹不實際大小(?)還有刪除標記(後面解釋)。

struct Node
{
	int l,r,val;
	int size,fact; // 實際大小:fact
	bool exist; // exist : 是否存在
}tzy[N];
int cnt,root; // cnt : 節點數量    root : 根

// 建立新節點
inline void newnode(int &now,int val){now=++cnt; tzy[now].val=val; tzy[now].size=tzy[now].fact=1; tzy[now].exist=true;}

1.1. 插入

只要按 BST 插入方法插入即可,但是為了平衡還得在後面加一句判平衡。

void ins(int &now,int val)
{
	if (!now){newnode(now,val); check(root,now); return ;} // check : 判平衡
	++tzy[now].size; ++tzy[now].fact;
	if (val<tzy[now].val) ins(tzy[now].l,val);
	else ins(tzy[now].r,val);
}

1.2. 刪除

還記得前面節點裡有維護「刪除標記」嗎?

對,刪除就是把節點打上標記。

而「子樹不實際大小」就是樹中子樹的大小,
「子樹實際大小」就是樹中子樹去掉打了標記的大小。

同樣,為了平衡還得在後面加一句判平衡。

void del(int now,int val)
{
	if (tzy[now].exist&&tzy[now].val==val){tzy[now].exist=false; tzy[now].fact--; check(root,now); return ;}
	tzy[now].fact--;
	if (val<tzy[now].val) del(tzy[now].l,val);
	else del(tzy[now].r,val);
}

!.! 判斷樹是否平衡並調整樹

插入和刪除都提到了「判平衡」,但是「判平衡」怎麼寫呢?

我們從根向要操作的節點找,如果找到了需要重構的節點,就暴力重構它的子樹。

判斷是否平衡的條件是:

當前節點的左子樹或右子樹的大小大於當前節點的大小 \(\times \alpha\),其中 \(\alpha\) 是平衡因子,或
當前節點為根的子樹內被刪除的節點數量 \(>\) 樹大小(非實際)的 \(30\%\) .

\(\alpha\) 必須取 \([0.5,1]\),一般取 \(\alpha\in[0.7,0.8]\),最平常的是取 \(\alpha=0.75\) .

inline bool imbalence(int now){return (max(tzy[tzy[now].l].size,tzy[tzy[now].r].size)>tzy[now].size*alpha)||
                                      (tzy[now].size-tzy[now].fact>tzy[now].size*0.3);                      }

所以總體的 check 大概是這樣的:

void check(int &now,int end)
{
	if (now==end) return;
	if (imbalence(now)){rebuild(now); update(root,now); return ;}
	if (tzy[end].val<tzy[now].val) check(tzy[now].l,end);
	else check(tzy[now].r,end);
}

這裡 rebuild 是重構,update 是更新資料(size)。

update 非常的好寫:

void update(int now,int end)
{
	if (!now) return;
	if (tzy[end].val<tzy[now].val) update(tzy[now].l,end);
	else update(tzy[now].r,end);
	tzy[now].size=tzy[tzy[now].l].size+tzy[tzy[now].r].size+1;
}

前面說過了,替罪羊樹的維護平衡(重構)是暴力,替罪羊樹的重構就是拉開(中序遍歷)再拎起來。

來個動圖演示一下:

vector<int> v;
void ldr(int now) // 拉開
{
	if (!now) return;
	ldr(tzy[now].l);
	if (tzy[now].exist) v.push_back(now);
	ldr(tzy[now].r);
}
void lift(int l,int r,int &now) // 拎起來
{
	if (l==r){now=v[l]; tzy[now].l=tzy[now].r=0; tzy[now].size=tzy[now].fact=1; return ;}
	int m=(l+r)>>1;
	while ((l<m)&&(tzy[v[m]].val==tzy[v[m-1]].val)) --m;
	now=v[m];
	if (l<m) lift(l,m-1,tzy[now].l);
	else tzy[now].l=0;
	lift(m+1,r,tzy[now].r);
	tzy[now].size=tzy[tzy[now].l].size+tzy[tzy[now].r].size+1; // (
	tzy[now].fact=tzy[tzy[now].l].fact+tzy[tzy[now].r].fact+1;
}
void rebuild(int &now)
{
	v.clear(); ldr(now);
	if (v.empty()){now=0; return ;}
	lift(0,v.size()-1,now);
}

1.3. & 1.4. 查詢值的排名 / 查詢排名的值

這些就按照普通 BST 寫就行啦

int getrank(int val)
{
	int now=root,rank=1;
	while (now)
	{
		if (val<=tzy[now].val) now=tzy[now].l;
		else {rank+=tzy[now].exist+tzy[tzy[now].l].fact; now=tzy[now].r;}
	} return rank;
}
int getval(int rank)
{
	int now=root;
	while (now)
	{
		if (tzy[now].exist&&tzy[tzy[now].l].fact+tzy[now].exist==rank) break;
		else if (tzy[tzy[now].l].fact>=rank) now=tzy[now].l;
		else {rank-=tzy[tzy[now].l].fact+tzy[now].exist; now=tzy[now].r;} // 這個 rank-=... 有主席樹內味了
	} return tzy[now].val;
}

1.5. & 1.6. 求前趨 / 求後繼

前趨和後繼有個小技巧:
前趨:getval(getrank(x)-1);
後繼:getval(getrank(x+1));

inline int pre(int x){return getval(getrank(x)-1);}
inline int nxt(int x){return getval(getrank(x+1));}

實際時空比較

普通平衡樹:

平衡樹 程式碼長度 時間 空間
替罪羊樹 3.15KB 352ms 1.89MB

Refence