日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

字符串处理——字典树

發布時間:2025/3/17 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 字符串处理——字典树 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【概述】

字典樹,又稱為單詞查找樹,Tire 樹,是一種樹形結構,它是哈希樹的變種。

字典樹與字典很相似,當要查一個單詞是不是在字典樹中,首先看單詞的第一個字母是不是在字典的第一層,如果不在,說明字典樹里沒有該單詞,如果在就在該字母的孩子節點里找是不是有單詞的第二個字母,沒有說明沒有該單詞,有的話用同樣的方法繼續查找,以此類推。

基本性質:

  • 字典樹用邊表示字符
  • 有相同前綴的單詞共用前綴結點
  • 根節點不包含字符
  • 每個單詞結束的時候都用一個特殊字符表示,圖中用的紅色 $ 符
  • 從根節點到一個紅點所經過的所有邊的字母就是一個字符串

應用:其常用于統計、排序和保存大量的字符串(不僅限于字符串),所以經常被搜索引擎系統用于文本詞頻統計。

優點:其利用字符串的公共前綴來節約存儲空間,最大限度地減少無謂的字符串比較,查詢效率比哈希表高。?

【構建過程】

1.插入

1)思路

對于字典樹,從左到右掃描一個單詞,若字母在相應根節點下沒有出現,就插入這個字母,若出現過,則沿著樹走下去,看單詞的下一個字母。

那么此時產生一個問題,對于從左到右掃描的這個單詞,若字母在相應根節點下沒有出現過,如何去選擇位置插入?計算機不會自動選擇位置,因此需要給他指定一個位置,這樣就需要對每個字母進行編號。

設數組 tire[i][j]=k,表示編號為 i 的結點的第 j 個孩子是編號為 k 的結點,此時有兩種編號:

  • i、k:表示結點位置的編號,相對整棵樹而言,此處相同字母編號不同
  • j:表示幾點 i 的第 j 個孩子,相對結點 i 而言,此處編碼應按照 ASCLL 碼來編,用到哪個編哪個,相同字母編號相同

以單詞 cat,cash,app,apple,aply,ok 為例,按照輸入順序對其進行編號?

第一種編號結果:

第二種編號結果:

經過這樣的編號后,這樣數組?trie[i][j]=k,表示編號為 i 的節點的第 j 個孩子是編號為 k 的節點,那么第一種編號即為 i、k,第二種編號即為 j,從而可以實現插入

2)實現

int tot=0;//編號 int trie[N][26];//字典樹 int val[N];//字符串結尾標記,val[i]=x表示第i個節點的權值為x void insert(char *s){//插入單詞sint len=strlen(s);//單詞s的長度int root=0;//字典樹上當前匹配到的結點for(int i=0;i<len;i++){int id=s[i]-'a';//子節點編號if(trie[root][id]==0)//如果之前沒有從root到id的前綴 trie[root][id]=++tot;//插入root=trie[root][id];//順著字典樹往下走}val[root]=n; }

2.查找

1)思路

查找有很多種,可以查找某一個前綴,也可以查找整個單詞。一般來說,對于一個單詞,從左到右依次掃描每個字母,順著字典樹往下找,能找到這個字母,往下走,否則結束查找,即沒有這個單詞;單詞掃完了,表示有這個單詞。

如果是查詢某個單詞是否在字典樹中的話,可用布爾變量 vis[i] 表示節點 i 是否是單詞結束的標志,那么返回的是 vis[root],所以在插入操作中插入完每個單詞時,要對單詞最后一個字母的 vis[i] 置為 true

如果是查詢前綴出現的次數的話,那就在開一個數組 sum[i],表示位置 i 被訪問過的次數,那么最后返回的是 sum[root],所以插入操作中每訪問一個節點,都要讓他的 sum++。在這里,前綴的次數是標記在前綴的最后一個字母所在位置的后一個位置上。

例如:前綴 abc 出現的次數標記在 c 所在位置的后一個位置上

2)實現

bool find(char *s){//查詢單詞是否在樹中int len=strlen(s);//單詞長度int root=0;//從根結點開始找for(int i=0;s[i];i++){int x=s[i]-'a';if(trie[root][x]==0)//以root為頭結點的x字母不存在return false;root=trie[root][x];//為查詢下個字母做準備,往下走 }return val[root];//找到 }

3.刪除

1)思路

對于一個單詞,如果要在樹中將其刪除,有三種情況:

  • 沒找到這個單詞
  • 找到葉節點的時,葉節點的 cnt 標志清零,代表不是葉節點
  • 當前節點沒有其他孩子節點的時,可直接刪除這個節點

2)實現

void del(char *str,int word){//word為要刪除的單詞的標號,一般為seach("單詞");int len=strlen(str);int root=0;if(word==0)//沒找到單詞return;for(int i=0;i<len;i++){int x=str[i]-'a';if(tire[root][x]==0)//沒有子節點return;sum[root]-=cnt;//減去前綴root=tire[root][x];}sum[root]=0;//前綴清零for(int i=0;i<26;i++)//刪除節點tire[root][i]=0; }

【模版】

1.查詢單詞/前綴是否出現

int tot; int trie[N][26];//trie[rt][x]=tot,root是上個節點編號,x是字母,tot是下個節點編號 //bool vis[N];//查詢整個單詞用 void insert(char *s,int root){for(int i=0;s[i];i++){int x=s[i]-'a';if(trie[root][x]==0)//現在插入的字母在之前同一節點處未出現過 trie[root][x]=++tot;//字母插入一個新的位置,否則不做處理 root=trie[root][x];//為下個字母的插入做準備 }//vis[root]=true;//標志該單詞末位字母的尾結點,在查詢整個單詞時用 } bool find(char *s,int root){for(int i=0;s[i];i++){int x=s[i]-'a';if(trie[root][x]==0)return false;//以root為頭結點的x字母不存在,返回0 root=trie[root][x];//為查詢下個字母做準備 }return true;//return vis[root];//查詢整個單詞時 }int main(){int n,m;char s[22];tot=1;cin>>n;//插入單詞個數for(int i=1;i<=n;i++){cin>>s;insert(s,1);}cin>>m;//查詢單詞個數for(int i=1;i<=n;i++){cin>>s;if(find(s,1))printf("YES\n");else printf("NO\n");}return 0; }

2.查詢前綴出現次數

int trie[400001][26],tot; int sum[400001]; void insert(char *s){int root=0;int len=strlen(s);for(int i=0;i<len;i++){int id=s[i]-'a';if(!trie[root][id]) trie[root][id]=++tot;sum[trie[root][id]]++;//前綴保存 root=trie[root][id];} } int search(char *s){int root=0;int len=strlen(s);for(int i=0;i<len;i++){//root經過循環后變成前綴最后一個字母所在位置int id=s[i]-'a';if(!trie[root][id]) return 0;root=trie[root][id];}return sum[root]; } int main(){int n,m;char s[11];tot=1;cin>>n;//插入單詞個數for(int i=1;i<=n;i++){cin>>s;insert(s);}cin>>m;//查詢次數for(int i=1;i<=m;i++){cin>>s;printf("%d\n",search(s));} }

3.結構體實現的增刪查字典樹

struct Node{int sum;//前綴int next[26];//子節點void init(){sum=0;memset(next,-1,sizeof next);} }tire[N]; int tot; void insert(char *str){int len=strlen(str);int root=0;for(int i=0;i<len;i++){int x=str[i]-'a';if(tire[root].next[x]==-1)tire[root].next[x]=tot++;root=tire[root].next[x];tire[root].sum++;} } int search(char *str){int len=strlen(str);int root=0;for(int i=0;i<len;i++){int x=str[i]-'a';if(tire[root].next[x]==-1)return 0;root=tire[root].next[x];}return tire[root].sum; } void del(char *str,int word){int len=strlen(str);int root=0;if(word<0)return;for(int i=0;i<len;i++){int x=str[i]-'a';if(tire[root].next[x]==-1)return;tire[root].sum-=word;root=tire[root].next[x];}tire[root].sum=0;for(int i=0;i<26;i++)tire[root].next[i]=-1; }int main(){tot=1;for(int i=0;i<N;i++)tire[i].init();int t;scanf("%d",&t);while(t--){char str[10],word[35];scanf("%s%s",str,word);if(str[0]=='i')//插入insert(word);else if(str[0]=='d')//刪除del(word,search(word));else{//查詢if(search(word)>0)printf("Yes\n");elseprintf("No\n");}}return 0; }

4.輸出唯一前綴

int tot; int trie[N][26];//trie[rt][x]=tot,root是上個節點編號,x是字母,tot是下個節點編號 int val[N]; void insert(char *s,int root){int len=strlen(s);for(int i=0;i<len;i++){int x=s[i]-'a';if(trie[root][x]==0){//現在插入的字母在之前同一節點處未出現過trie[root][x]=tot;//字母插入一個新的位置,否則不做處理val[tot]=0;//記錄以當前結點為根的子樹下單詞個數tot++;}root=trie[root][x];//為下個字母的插入做準備val[root]++;} } void find(char *s,int root){int len=strlen(s);for(int i=0;i<len;i++){int x=s[i]-'a';root=trie[root][x];//為查詢下個字母做準備printf("%c",s[i]);//輸出當前字母if(val[root]==1)//為1時直接返回return;} } char word[N][50]; int main(){int cnt=0;tot=1;while(scanf("%s",word[cnt])!=EOF)insert(word[cnt++],0);for(int i=0;i<cnt;i++){//枚舉所有單詞printf("%s ",word[i]);find(word[i],0);printf("\n");}return 0; }

【例題】

  • 統計難題(HDU-1251)(查詢前綴次數):點擊這里
  • Hat’s Words(HDU-1247)(字典樹+單詞劃分):點擊這里
  • Problem C(HDU-5687)(帶刪除的字典樹):點擊這里
  • Phone List(HDU-1671)(有限空間+指針的靈活應用):點擊這里
  • Shortest Prefixes(POJ-2001)(輸出唯一前綴):點擊這里

總結

以上是生活随笔為你收集整理的字符串处理——字典树的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。