1. 程式人生 > >演算法詳解——Trie樹

演算法詳解——Trie樹

Tire樹(字典樹)是用於字串檢索的一種多叉樹結構,其中每一個節點包含了若干個字元,在插入或檢索某一個字串時,就沿著當前節點的指標訪問下一個節點,我們先來講一下Tire樹的基礎操作

 

初始化

一顆空的Tire樹僅包含根節點,且該點的指標為空

 

插入

當我們要插入一個字串a時,我們先令指標p指向根節點,然後掃描a中的每一個字元c,執行以下操作:

1.當p中的c指向一個已存在的節點q,令p = q

2.當p中的c指標為空,則新建一個節點q,使c指向q,令p = q

 

檢索

當我們要檢索一個字串a在Trie樹中是否存在時,我們先令指標p指向根節點,然後掃描a中的每一個字元c,執行以下操作:

1.當p中的c指向一個已存在的節點q,令p = q

2.當p中的c指標為空,則說明S不存在於Trie樹中,結束檢索

 

下面給出具體操作程式碼:

 

inline void Insert(char* a) //插入一個字串a
{
  int len = strlen(a),p = 0;
  for(int k = 0;k < len;k ++)
  {
    int ch = a[k] - '0';
    if(!trie[p][ch]) trie[p][ch] = tot ++; //不存在元素c,新建一個節點q
    p = trie[p][ch]; //
令p = q } return ; } inline bool Search(char* a) //對字串a進行檢索 { int len = strlen(a),p = 0; for(int k = 0;k < len;k ++) { int ch = a[k] - 'a'; if(trie[p][ch]) p = trie[p][ch]; //存在元素c,令p = q else return false; //不存在元素c,結束檢索 } return true; }

 

 

 

下面給出兩道例題

 


T1 HDU1251

 

Problem Description

 

Ignatius最近遇到一個難題,老師交給他很多單詞(只有小寫字母組成,不會有重複的單詞出現),現在老師要他統計出以某個字串為字首的單詞數量(單詞本身也是自己的字首).   Input

 

輸入資料的第一部分是一張單詞表,每行一個單詞,單詞的長度不超過10,它們代表的是老師交給Ignatius統計的單詞,一個空行代表單詞表的結束.第二部分是一連串的提問,每行一個提問,每個提問都是一個字串.

 

  Output

 

對於每個提問,給出以該字串為字首的單詞的數量.

 

題解

顯然是一道Trie樹的板子題,在插入的時候,統計每個節點被訪問的次數,最後查詢就行了

程式碼如下:

 

#include <cstdio>
#include <cstring>
#include <iostream>

const int maxn = 1e6;
int trie[maxn][26];
int cnt[maxn]; 
int tot = 1;
char a[100];

inline void Insert(char* a)
{
  int len = strlen(a),p = 0;
  for(int k = 0;k < len;k ++)
  {
    int ch = a[k] - 'a';
    if(!trie[p][ch]) trie[p][ch] = tot ++;
    p = trie[p][ch];
    cnt[p] ++;
  }
}

inline int Search(char* a)
{
  int len = strlen(a),p = 0;
  for(int k = 0;k < len;k ++)
  {
      int ch = a[k] - 'a';
      if(trie[p][ch]) p = trie[p][ch];
      else return 0;
  }
  return cnt[p];
} 

int main(int argc, char const *argv[]) 
{
  while(gets(a))
  {
    if(!strlen(a)) break;
    Insert(a);
  }
  while(std::cin>>a)
  {
    printf("%d\n",Search(a));
  }
  return 0;
}

 

 


T2 洛谷P2580

 

 

題目背景

XS中學化學競賽組教練是一個酷愛爐石的人。

他會一邊搓爐石一邊點名以至於有一天他連續點到了某個同學兩次,然後正好被路過的校長髮現瞭然後就是一頓尤拉尤拉尤拉(詳情請見已結束比賽CON900)

題目描述

這之後校長任命你為特派探員,每天記錄他的點名。校長會提供化學競賽學生的人數和名單,而你需要告訴校長他有沒有點錯名。(為什麼不直接不讓他玩爐石。)

輸入輸出格式

輸入格式:

第一行一個整數 n,表示班上人數。接下來 n 行,每行一個字串表示其名字(互不相同,且只含小寫字母,長度不超過 50)。第 n+2 行一個整數 m,表示教練報的名字。接下來 m 行,每行一個字串表示教練報的名字(只含小寫字母,且長度不超過 50)。

輸出格式:

對於每個教練報的名字,輸出一行。如果該名字正確且是第一次出現,輸出“OK”,如果該名字錯誤,輸出“WRONG”,如果該名字正確但不是第一次出現,輸出“REPEAT”。(均不加引號)

題解

也是一道非常簡單的Trie板子題,在檢索的時候注意加一個訪問標記,當檢索的元素已被檢索過,輸出“REPEAT”

程式碼如下:

#include <cstdio>
#include <cstring>
#include <iostream>

const int maxn = 1e6 + 5;
int n,m;
int trie[maxn][26];
int tot = 1;
int vis[maxn];
bool flag;

inline void Insert(char* a)
{
  int len = strlen(a),p = 0;
  for(int k = 0;k < len;k ++)
  {
    int ch = a[k] - 'a';
    if(!trie[p][ch]) trie[p][ch] = tot ++;
    p = trie[p][ch];  
  }
  return ;
}

inline bool Search(char* a)
{
  int len = strlen(a),p = 0;
  for(int k = 0;k < len;k ++)
  {
    if(trie[p][a[k] - 'a']) p = trie[p][a[k] - 'a'];
    else return false;
  }
  if(vis[p]) flag = true;
  vis[p] = 1;
  return true;
}

int main(int argc, char const *argv[]) 
{
  scanf("%d",&n);
  for(int i = 1;i <= n;i ++)
  {
    char a[100];
    std::cin>>a;
    Insert(a);
  }
  scanf("%d",&m);
  for(int i = 1;i <= m;i ++)
  {
    char a[100];
    flag = false;
    std::cin>>a;
    if(Search(a))
    {
      if(!flag) printf("OK\n");
      else printf("REPEAT\n");
    }
    else printf("WRONG\n");
  }
  return 0;
}