1. 程式人生 > >資料結構 : 字首樹 Trie

資料結構 : 字首樹 Trie

字首樹 Trie

1 什麼是Trie樹

  • Trie樹,又叫字典樹 字首樹 單詞查詢樹 鍵樹
  • 是一種樹形結構,是一種雜湊樹的變種
  • 是一種多叉樹.

img

1.1 基本性質:

  1. 根節點不包含字元. 除了根節點之外,每個子節點包含一個字元.
  2. 從根節點到某一節點,路徑通過經過的字元連線.為該節點對應字串
  3. 每個節點的所有子節點包換的字元互不相同

通常,會在節點結構中設定一個標誌,用來標記該結點處是否構成一個單詞(關鍵字).

2 Tire樹的優缺點

Tire樹的核心思想是通過 空間來換時間.

利用字串的公共字首來減少無謂的字串比較

2.1 優點

  1. 插入和查詢效率高! 都是 O(m). m為字串長度.

  2. Tire樹中不同的關鍵字不會產生衝突.

    Trie樹只有在允許一個關鍵字關聯多個值的情況下才有類似hash碰撞發生.

  3. Trie樹不用求 hash 值.對短字串有更快的速度.

    通常求hash值也是需要遍歷字串的.

  4. Trie樹可以對關鍵字按字典序排序

2.2 缺點

  1. 當 hash 函式很好時. Trie樹的查詢效率會低於雜湊搜尋

  2. 空間消耗比較大

    優化:

    壓縮字典樹 Compressed Trie

    Ternay Search Trie 三分搜尋樹

3 Trie樹的應用

3.1 字串檢索

檢索/查詢功能是Trie樹最原始的功能

思路 是從根節點開始一個一個字元進行比較

  • 如果沿路比較,發現不同的字元,則表示該字串在集合中不存在
  • 如果所有的字元全部比較完並且全部相同,還需要判斷一下最後一個節點的標誌位.標記該節點是否代表一個關鍵字.
class Node {
    public:
    bool isWord; // 標記是否是一個關鍵字
    map<char, Node*> next; // 存放各個子節點
    Node(bool isWord) {
        this->isWord = isWord;
    }
    Node() {
        Node(false);
    }
};

3.2 詞頻統計

Trie樹常被搜尋引擎系統用於文字詞頻統計

思路 就是增加一個 count 來計數. 對每一個關鍵字執行插入操作時,若已經存在,則計數+1.若不存在則,插入後 count 置為1.

3.3 字串排序

Trie樹可以對大量字串按字典序進行排序

思路 就是遍歷一遍所有的關鍵字,將他們全部插入 Trie 樹.

樹的每個結點的所有兒子,很顯然按照字母表排序.然後先序遍歷輸出 Trie 樹中所有關鍵字即可.

3.4 字首匹配

例如: 找出一個字串集合中所有以ab開頭的字串 . 需要用所有的字串構造一個 trie 樹. 然後然後輸出 以 a->b-> 開頭的路徑的搜有關鍵字即可.

trie樹字首匹配常用於搜尋提示 :

如當輸入一個網址. 可以自動搜尋出可能的選擇.

當沒有完全匹配的搜尋結果,可以返回字首最相似的可能.

3.5 作為其他資料結構和演算法的輔助結構

字尾樹 : (字串模式識別)

4 C++實現簡單的 Trie

class Trie {
private:
	class Node {
	public:
		bool isWord;

		map<char, Node*> next;
		Node(bool isWord) {
			this->isWord = isWord;
		}
		Node() {
			Node(false);
		}
	};
	
	Node* root; // 根節點
	int size;

public:
	Trie() {
		root = new Node();
		size = 0;
	}
	
	// 獲得Trie儲存的單詞數量
	int getSize() {
		return size;
	}

	// trie中新增一個新的word
	void add(string word) {
		Node* cur = root;
		for (int i = 0; i < word.size(); i++) {
			char c = word[i]; // 當前的字元
			if(cur->next.count(c)==0){ // 如果找不到呢,就要自己加一個
				cur->next.insert(make_pair(c, new Node(false)));
			}
			// 移動節點到下一個節點
			cur = cur->next[c];
		}
		// 若當前這個單詞滿意出現過,則把它置為真
		if (!cur->isWord) {
			cur->isWord = true;
			size++;
		}
	}

	// 查詢單詞word是否在Trie中
	bool contains(string word) {
		Node* cur = root;
		for (int i = 0; i < word.size(); i++) {
			char c = word[i];
			if (cur->next.count(c) == 0) {
				return false;
			}
			cur = cur->next[c];
		}
		return cur->isWord;
	}

	// 在Trie中查詢是否有單詞以orefix為字首
	bool isPrefix(string prefix) {
		Node* cur = root;
		for (int i = 0; i < prefix.size(); i++) {
			char c = prefix[i];
			if (cur->next.count(c) == 0) {
				return false;
			}
			cur = cur->next[c];
		}
		return true;
	}

};

5 Trie刪除操作

待續

6 LeetCode題解

208. Implement Trie (Prefix Tree)

參考上面程式碼

211. Add and Search Word - Data structure design

class WordDictionary {
public:
	class Node {
	public:
		bool isWord;
		map<char, Node*> next;

		Node(bool isWord) {
			this->isWord = isWord;
		}

		Node() {
			Node(false);
		}
	};

	Node* root;

	/** Initialize your data structure here. */
	WordDictionary() {
		root = new Node(false);
	}

	/** Adds a word into the data structure. */
	void addWord(string word) {
		Node* cur = root;
		for (int i = 0; i < word.size(); i++) {
			char c = word[i]; // 當前的字元
			if (cur->next.count(c) == 0) { // 如果找不到呢,就要自己加一個
				cur->next.insert(make_pair(c, new Node(false)));
			}
			// 移動節點到下一個節點
			cur = cur->next[c];
		}
		// 若當前這個單詞滿意出現過,則把它置為真
		if (!cur->isWord) {
			cur->isWord = true;
		}
	}

	bool search(Node* node, string word, int start) {
		// 遞迴結束條件
		if (start == word.size()) {
			return node->isWord;
		}

		Node* cur = node;
		char c = word[start];
		if (c == '.') {
			for (auto it = cur->next.begin(); it != cur->next.end(); it++) {
				if (search(it->second,word,start+1))
				{
					return true;
				}
			}
			return false;
		}
		else { // 不是'.' 就要判斷存不存在 c
			if (cur->next.count(c) == 0) {
				return false;
			}
			cur = cur->next[c];
			return search(cur, word, start + 1);
		}
	}

	/** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
	bool search(string word) {
		Node* cur = root;
		return search(cur, word, 0);
	}
};

677

class MapSum {
public:

	class Node {
	public:
		Node(int val) {
			this->val = val;
		}
		Node() {
			Node(0);
		}
		map<char, Node*> next;
		int val;
	};

	Node* root;

	/** Initialize your data structure here. */
	MapSum() {
		root = new Node();
	}
	void insert(string key, int val) {
		Node* cur = root;
		for (auto c : key) {
			if (cur->next.count(c) == 0) {
				cur->next.insert(make_pair(c, new Node(0)));
			}
			cur = cur->next[c];
		}
		cur->val = val;
	}


	int sum(Node* node) {
		int res = 0;
		Node* cur = node;
		if (cur->val != 0) {
			res += cur->val;
		}
		for (auto it = cur->next.begin(); it != cur->next.end(); it++) {
			res += sum(it->second);
		}
		return res;
	}

	int sum(string prefix) {
		Node* cur = root;
		for (auto c : prefix) {
			if (cur->next.count(c) == 0) {
				return 0;
			}
			cur = cur->next[c];
		}
		// 找到了以prefix開頭的節點,接下來要遍歷所有的的節點
		return sum(cur);
	}
};

/**
* Your MapSum object will be instantiated and called as such:
* MapSum obj = new MapSum();
* obj.insert(key,val);
* int param_2 = obj.sum(prefix);
*/

472

212

421

參考文獻

Trie樹介紹

Trie數(字首樹/字典樹)簡介及Leetcode上關於字首樹的題