字典樹

cn是大帅哥886發表於2024-08-22

講義


字典樹,就像字典,在建完樹後,不斷查字典,建樹的過程也會查字典。

一般,我們假定0為字典樹的根節點,然後分為建樹和查詢兩個操作。

由於可能不斷插入新的元素,所以把建樹成為插入也不為過。

比如我們要插入一個“abc”到樹上。

那麼先看看根節點的孩子,“a”不存在,那就建一個!

那麼再看看“a”的孩子,"b"不存在,那麼再建。

c同理

這個時候我們給”c“這個節點打個標記,證明這是一個單詞的結尾了(根據題目而異,此處為了方便舉例)

我們再建一個“abe”

那麼先看看根節點的孩子,“a”存在,那就別建了,往下走即可。

那麼先看看“a“的孩子,“b”存在,那就別建了,往下走。

那麼先看看”b“的孩子,“e”不存在,那就建!

建完e,打標記。

那麼我們怎麼知道往下建的節點就是a,b,c,e?有26個英文字母嘛,a對應1,b對應2,c對應3,e對應5即可。

用陣列去存,那麼就是 tr[26]

由於不斷往下去建,我們還要一維存“層數”,這樣才知道每一層的字串是啥。就比如圖中的第4層有2個。也可以轉換為字串的最大長度。

也就是 tr[N][M],其中N是層數,M是26,代表每個字母(因題而異,M大小不固定,有可能有的題有數字,那麼M開到36,實際要預留多一點空間)

我們先看看插入如何實現

我們想要每一個節點對應一個idx,也就是定義一個節點的編號。

只有在該節點沒有編號的時候,才回去賦值一個編號,換句話說也就是該節點不存在,就給他一個編號

void insert(string s)
{
	int p=0;
	for (int i=0; i<s.size(); i++)
	{
		int tem=s[i]-'a'+1;   //把字元選轉換為數字
		if (!tr[p][tem]) tr[p][tem]=++idx;   //沒有這個節點,給他一個編號
		p=tr[p][tem];   //沿著當前的節點往下走
	}
	
	cnt[p]++; //給結尾字母打標記
}

  

再看看查詢,跟插入差不多。

還是那棵樹,我們查詢是否有“ab”這個單詞。

那麼先看看根節點的孩子,“a”存在,往下走。

那麼先看看“a"的孩子,“b”存在

此時發現遍歷完“ab“了,但是由於”b“節點這裡沒有標記,雖然是”abc“的字首,但是卻沒有出現過”ab”

我們查詢是否有“abda”這個單詞。

那麼先看看根節點的孩子,“a”存在,往下走。

那麼先看看“a"的孩子,“b”存在,往下走。

那麼先看看“b"的孩子,“d”不存在,返回0。

只要中途有某個字母沒出現過,那麼就可以證明單詞肯定沒出現過,後面的也不用查了。

我們查詢是否有“abc”這個單詞。

那麼先看看根節點的孩子,“a”存在,往下走。

那麼先看看“a"的孩子,“b”存在,往下走。

那麼先看看“b"的孩子,“c”存在。

此時發現遍歷完“abc“了,且”c“節點有標記,所以肯定出現過這個單詞

int query(string s)
{
	int p=0;
	for (int i=0; i<s.size(); i++)
	{
		int tem=s[i]-'a'+1;   //字元轉換成數字
		if (!tr[p][tem]) return 0;   //沒有這個單詞的某個字母,直接返回0
		p=tr[p][tem];   //往下走
	} 
	
	return cnt[p];   //結尾初有標記才算出現過這個單詞
}

  

剛剛這裡講的就是一道模板題。接下來再溫故一遍程式碼。

板子


第2題 字串統計 檢視測評資料資訊

維護一個字串集合,支援兩種操作:

I x 向集合中插入一個字串 x;

Q x 詢問一個字串在集合中出現了多少次。

共有 N 個操作,所有輸入的字串總長度不超過 1e5,字串僅包含小寫英文字母。

輸入格式

第一行包含整數 N,表示運算元。

接下來 N 行,每行包含一個操作指令,指令為 I x 或 Q x 中的一種。

1≤N≤2e4

輸出格式

對於每個詢問指令 Q x,都要輸出一個整數作為結果,表示 x 在集合中出現的次數。

每個結果佔一行。

輸入/輸出例子1

輸入:

5

I abc

Q abc

Q ab

I ab

Q ab

輸出:

1

0

1

樣例解釋

#include <bits/stdc++.h>
using namespace std;
const int N=2e4+5, M=30;

int n, tr[N][M], idx=0, cnt[N];
string op, s;
void insert(string s)
{
	int p=0;
	for (int i=0; i<s.size(); i++)
	{
		int tem=s[i]-'a'+1;
		if (!tr[p][tem]) tr[p][tem]=++idx;
		p=tr[p][tem];
	}
	
	cnt[p]++;
}
int query(string s)
{
	int p=0;
	for (int i=0; i<s.size(); i++)
	{
		int tem=s[i]-'a'+1;
		if (!tr[p][tem]) return 0;
		p=tr[p][tem];
	} 
	
	return cnt[p];
}
int main()
{
	scanf("%d", &n);
	while (n--)
	{
		cin>>op>>s;
		if (op=="I") insert(s);
		else printf("%d\n", query(s));
	}
	return 0;
}

  

優缺點


優點:

1.快速查詢字串S存不存在
2.快速查詢字串的字首


缺點:

空間佔用有時候較大

相關文章