Trie树(c++实现)

原理

先看个例子,存储字符串abc、ab、abm、abcde、pm可以利用以下方式存储

      上边就是Trie树的基本原理:利用字串的公共前缀来节省存储空间,最大限度的减少无谓的字串比较。

应用

      Trie树又称单词查找树,典型的应用是用于统计,排序和保存大量的字符串(不仅用于字符串),所以经常被搜索引擎系统用于文本词频的统计。

设计

      trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。

结点可以设计成这样:

//trie节点定义
template <int Size>
class trieNode {
public:
	trieNode() : terminableSize(0), nodeSize(0) {
		for (int i = 0; i < Size; ++i)
			children[i] = NULL;
	}
	~trieNode() {
		for (int i = 0; i < Size; ++i) {
			if (children[i] != NULL) {
				delete children[i];
				children[i] = NULL;
			}
		}
	}

public:
	int terminableSize; //存储以此节点为结尾的字符串的个数
	int nodeSize;  //记录此节点孩子的个数
	trieNode *children[Size]; //该数组记录指向孩子的指针
};

图示

设计成这样:

//trie树定义
template <int Size>
class trie {
public:
	trie() : root(new trieNode<Size>) {}

	int Index(char ch) { //取某个字符在children数组中的位置
		return static_cast<int>(ch % Size);
	}

	void insert(const string& str); //插入字符串str

	bool find(const string& str);  //查找字符串str

	bool downNodeAlone(trieNode<Size> *ptr); //判断当前节点往下是否是单一的字符串

	bool erase(const string& str);  //删除字符串str

	int sizeAll(const trieNode<Size> *pNode); //统计不重复字符串个数

	int sizeNoneRedundant(const trieNode<Size> *pNode); //统计重复字符串个数

public:
	trieNode<Size> *root;
};

index字串索引利用(char % 26) 得到,这样'a' % 26 = 19, 'b' % 26 = 20

实现

插入

以插入abc、ab为例

]

 

删除

删除结点,首先查找此字串是否在树中,如果在树中,再查找此结点以下的部分是不是都是只有一个孩子,并且每个结点只有叶子结点是结束结点,如果不是继续往下重复上边过程。

统计字串个数

分两种情况

  1. 计算重复的字串的个数:是结束结点,此时加的是terminabel的个数
  2. 计算不重复的字串的个数:是结束结点,此时加的是1(当terminabel>0)的个数

参考代码

#include <iostream>
#include <string>
using namespace std;

//trie节点定义
template <int Size>
class trieNode {
public:
	trieNode() : terminableSize(0), nodeSize(0) {
		for (int i = 0; i < Size; ++i)
			children[i] = NULL;
	}
	~trieNode() {
		for (int i = 0; i < Size; ++i) {
			if (children[i] != NULL) {
				delete children[i];
				children[i] = NULL;
			}
		}
	}

public:
	int terminableSize; //存储以此节点为结尾的字符串的个数
	int nodeSize;  //记录此节点孩子的个数
	trieNode *children[Size]; //该数组记录指向孩子的指针
};


//trie树定义
template <int Size>
class trie {
public:
	trie() : root(new trieNode<Size>) {}

	int Index(char ch) { //取某个字符在children数组中的位置
		return static_cast<int>(ch % Size);
	}

	void insert(const string& str); //插入字符串str

	bool find(const string& str);  //查找字符串str

	bool downNodeAlone(trieNode<Size> *ptr); //判断当前节点往下是否是单一的字符串

	bool erase(const string& str);  //删除字符串str

	int sizeAll(const trieNode<Size> *pNode); //统计不重复字符串个数

	int sizeNoneRedundant(const trieNode<Size> *pNode); //统计重复字符串个数

public:
	trieNode<Size> *root;
};

template <int Size>
void trie<Size>::insert(const string& str) {
	trieNode<Size> *cur = root;
	for (size_t i = 0; i < str.size(); ++i) {
		if (!cur->children[Index(str[i])]) {
			cur->children[Index(str[i])] = new trieNode<Size>;
			++cur->nodeSize;
		}
		cur = cur->children[Index(str[i])];
	}
	++cur->terminableSize;
}

template <int Size>
bool trie<Size>::find(const string& str) {
	trieNode<Size> *cur = root;
	for (size_t i = 0; i < str.size(); ++i) {
		if (!cur->children[Index(str[i])]) {
			return false;
		}
		cur = cur->children[Index(str[i])];
	}
	if (cur->terminableSize > 0)
		return true;
	return false;
}

//判断当前节点往下是否是单一的字符串
template<int Size>
bool trie<Size>::downNodeAlone(trieNode<Size> *ptr) {
	trieNode<Size> *cur = ptr;
	int terminableSum = 0;
	while (cur->nodeSize > 0) {
		terminableSum += cur->terminableSize;
		if (terminableSum > 1)
			return false;

		if (cur->nodeSize > 1)
			return false;
		else { //cur->nodeSize = 1
			for (int i = 0; i < Size; ++i) {
				if (cur->children[i]) {
					cur = cur->children[i];
					break;
				}
			}
		}
	}
	if (terminableSum == 1)
		return true;
	return false;
}


//删除字符串str
template<int Size>
bool trie<Size>::erase(const string& str) {
	if (find(str)) {
		trieNode<Size> *cur = root;
		for (int i = 0; i < str.size(); ++i) {
			if (downNodeAlone(cur)) {
				while (i < str.size()) {
					trieNode<Size> *tmp = cur;
					cur = cur->children[Index(str[i])];
					delete tmp;
					++i;
				}
				return true;
			}
			cur = cur->children[Index(str[i])];
		}
		if (cur->terminableSize > 0)
			--cur->terminableSize;
		return true;
	}
	return false;
}


//统计该trie树中包含字符串个数(包括重复字符串)
template <int Size>
int trie<Size>::sizeAll(const trieNode<Size> *root) {
	if (root == NULL)
		return 0;
	int rev = root->terminableSize;
	for (int i = 0; i < Size; ++i) {
		rev += sizeAll(root->children[i]);
	}
	return rev;
}

//统计该trie树中包含字符串个数(不包括重复字符串)
template <int Size>
int trie<Size>::sizeNoneRedundant(const trieNode<Size> *root) {
	if (root == NULL)
		return 0;
	int rev = 0;
	if (root->terminableSize > 0)
		rev = 1;
	if (root->nodeSize > 0) {
		for (int i = 0; i < Size; ++i) {
			rev += sizeNoneRedundant(root->children[i]);
		}
	}
	return rev;
}

int main()
{
	trie<26> t;
	t.insert("hello");
	t.insert("hello");
	t.insert("h");
	t.insert("h");
	t.insert("he");
	t.insert("hel");
	cout << "SizeALL:" << t.sizeAll(t.root) << endl;
	cout << "sizeNoneRedundant:" << t.sizeNoneRedundant(t.root) << endl;

	t.erase("h");

	cout << "\nSizeALL:" << t.sizeAll(t.root) << endl;
	cout << "sizeNoneRedundant:" << t.sizeNoneRedundant(t.root) << endl;

	system("pause");
	return 0;
}

 结果 

 技术实现细节

1. 对树的删除,并不是树销毁结点,而是通过结点自身的析构函数实现

2. 模版类、模版函数、非类型模版可以参考:http://www.cnblogs.com/kaituorensheng/p/3601495.html

3. 字母的存储并不是存储的字母,而是存储的位置,如果该位置的指针为空,则说明此处没有字母;反之有字母。

4. terminableNum存储以此结点为结束结点的个数,这样可以避免删除时,不知道是否有多个相同字符串的情况。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章