1. 程式人生 > >資料結構實現 8.1:字典樹(C++版)

資料結構實現 8.1:字典樹(C++版)

資料結構實現 8.1:字典樹(C++版)

1. 概念及基本框架

字典樹 顧名思義,就是用來存放單詞的樹,也稱 Trie樹 ,其本質上是一種多叉樹,我們可以通過圖示說明。

1

這裡有一個灰色的根結點,不儲存資訊,然後下面的每個結點儲存一個字母,利用結點顏色區分從根結點到當前結點構成的字串是否是一個單詞。這樣,就可以構成一部字典。很顯然,從字典中查詢一個單詞,與字典 的容量並無關係,而只與單詞長度有關。這樣只要建立好了字典,那麼可以大大減小查詢操作的時間複雜度。
當然,我們也可以把字母儲存在路徑上,只用結點顏色表示當前結點構成的字串是否是一個單詞。

2

我們先來定義一個字典樹的結點,程式碼如下:

class TrieNode{
public:
	TrieNode(bool isWord = false){
		this->isWord = isWord;
		BSTMap<char, TrieNode*> *p = new BSTMap<char, TrieNode*>; 
		next = p;
	}
	TrieNode operator= (TrieNode& node){
		this->isWord = node.isWord;
		this->next = node.
next; return *this; } public: bool isWord; BSTMap<char, TrieNode*> *next; };

這裡使用了我們之前實現的 map 作為結點,其中 key 作為字母,value 用來儲存子樹。(很顯然,每個結點都可以看成一棵多叉樹的根)isWord 表示是否是一個單詞,初始化預設為 false 。這裡對 = 運算子進行了過載,方便我們後面的操作。
有了結點,字典樹的大體框架如下:

class Trie{
public:
	Trie(){
		m_size = 0;
	}
	...
private:
	TrieNode root;
int m_size; };

root 表示字典樹的根,不儲存資訊。
m_size 表示字典樹的大小。
同樣,為了保護資料,這些變數都放在 private 區。
接下來我們就對字典樹的增加、查詢以及一些其他基本操作用程式碼去實現。

2. 基本操作程式實現

2.1 增加操作

class Trie{
public:
	...
	void add(std::string word){
		TrieNode* cur = &root;
		for (int i = 0; i < word.size(); ++i){
			char c = word[i];
			if (!cur->next->contains(c)){
				TrieNode* node = new TrieNode;
				cur->next->add(c, node);
			}
			cur = cur->next->get(c);
		}
		if (!cur->isWord){
			cur->isWord = true;
			m_size++;
		}
	}
	...
};

這裡新增操作有兩個:
第一個是新增字母結點,只有結點不存在才會執行此操作;
第二個是新增新單詞,而只有字典中沒有當前單詞才會記錄增加了新單詞。

2.2 查詢操作

這裡我們提供兩個查詢函式,containsisPrefix ,函式實現程式碼如下。

class Trie{
public:
	...
	bool contains(std::string word){
		TrieNode* cur = &root;
		for (int i = 0; i < word.size(); ++i){
			char c = word[i];
			if (!cur->next->contains(c)){
				return false;
			}
			cur = cur->next->get(c);
		}
		return cur->isWord;
	}
	bool isPrefix(string prefix){
		TrieNode *cur = &root;
		for (int i = 0; i < prefix.size(); ++i){
			char c = prefix[i];
			if (!cur->next->contains(c)){
				return false;
			}
			cur = cur->next->get(c);
		}
		return true;
	}
	...
};

contains 用於查詢字典中是否包含該單詞。
isPrefix 用於查詢字典中是否包含該字首串。

2.3 其他操作

字典樹還有一些其他的操作,包括 字典樹大小 等的查詢操作。

class Trie{
public:
	...
	int size()const{
		return m_size;
	}
	bool isEmpty()const{
		return m_size == 0;
	}
	...
};

3. 演算法複雜度分析

因為字典樹的增加、查詢操作只與單詞長度有關,所以時間複雜度均是 O(1) 級別的。

3.1 增加操作

函式 最壞複雜度 平均複雜度
add O(1) O(1)

3.2 查詢操作

函式 最壞複雜度 平均複雜度
contains O(1) O(1)
isPrefix O(1) O(1)

總體情況:

操作 時間複雜度
O(1)
O(1)

4. 完整程式碼

程式完整程式碼(這裡使用了標頭檔案的形式來實現類)如下:
這裡不再給出 BSTMap 實現程式碼,如有需要可檢視 5.1

字典樹 類程式碼:

#ifndef __TRIE_H__
#define __TRIE_H__

#include <string>
#include "BSTMap.h"

class TrieNode{
public:
	TrieNode(bool isWord = false){
		this->isWord = isWord;
		BSTMap<char, TrieNode*> *p = new BSTMap<char, TrieNode*>; 
		next = p;
	}
	TrieNode operator= (TrieNode& node){
		this->isWord = node.isWord;
		this->next = node.next;
		return *this;
	}
public:
	bool isWord;
	BSTMap<char, TrieNode*> *next;
};

class Trie{
public:
	Trie(){
		m_size = 0;
	}
	int size()const{
		return m_size;
	}
	bool isEmpty()const{
		return m_size == 0;
	}
	void add(std::string word){
		TrieNode* cur = &root;
		for (int i = 0; i < word.size(); ++i){
			char c = word[i];
			if (!cur->next->contains(c)){
				TrieNode* node = new TrieNode;
				cur->next->add(c, node);
			}
			cur = cur->next->get(c);
		}
		if (!cur->isWord){
			cur->isWord = true;
			m_size++;
		}
	}
	bool contains(std::string word){
		TrieNode* cur = &root;
		for (int i = 0; i < word.size(); ++i){
			char c = word[i];
			if (!cur->next->contains(c)){
				return false;
			}
			cur = cur->next->get(c);
		}
		return cur->isWord;
	}
	bool isPrefix(string prefix){
		TrieNode *cur = &root;
		for (int i = 0; i < prefix.size(); ++i){
			char c = prefix[i];
			if (!cur->next->contains(c)){
				return false;
			}
			cur = cur->next->get(c);
		}
		return true;
	}
private:
	TrieNode root;
	int m_size;
};
#endif