一、本章主要内容:
1、单向单词树的概念
2、单向单词结构图解
3、单向单词树代码实现
(1)、单向单词树的节点数据结构
(2)、单向单词树的插入
(3)、单向单词树的查找
(4)、单向单词树获取所有单词
(5)、单向单词树获取某前缀单词
(6)、单向单词树获取最大前缀单词
(7)、单向单词树的删除
4、完整代码链接
二、单向单词树的概念
1、介绍:用于存放单词的一个树形结构,可以快速的查找,删除单词
2、树结构:树的节点由两部分组成,该树不是一个二叉树
3、value—一个存放意思,可以为空(代表不是单词),
4、key(next)存放的是下一个节点的地址的数组,数组大小为26(字母表大小),数组下标对应单词的字母(0–a…)
5、时间复杂度:O(log®N); R=26, N表示单词个数
6、空间复杂度:O(8R+56)*N~O(8R+56)NW W:单词的长度
7、优点:单词查找树查找时,直接诼个字符进行匹配,查找效率很高
8、缺点:当R很大时,空间利用率比较低,适用于较短键和较小的字符表
三、单词树结构图解:
四、单词树代码实现
1、单词树节点结构:
struct WTNode {
string _value; //注释
WTNode *_next[26]; //下标表示字符(0-25),存放的是下一个节点的地址
WTNode(string value = "")
:_value()
{
int i = 0;
for (; i < 26; i++)
_next[i] = NULL;
}
};
2、单词树插入操作
1、注:先判断根是否为NULL,为空创建一个节点
2、判断是否达到字符串的结尾(bit统计第几个字符),达到结尾更新value,返回退出
3、判断节点中该字符对应的next位置是否指向空节点,为空则让该位置指向一个有效节点(指向新创建节点)
4、递归该节点中的next位置,同时指向下一个字符
int getChar(char ch) { //计算字符对应的下标
return ch - 'a';
}
void put(string key, string value) {
//先创建一个头节点
if (_root == NULL)
_root = new Node;
_put(_root, key, value, 0);
}
void _put(Node* root, string key, string value, int bit) {
if (bit == key.size()) {
root->_value = value;
return;
}
int k = getChar(key[bit]);
if (root->_next[k] == NULL) {
root->_next[k] = new Node;
}
_put(root->_next[k], key, value, bit + 1); //递归处理下一个字符
}
3、查找单词对应的value
1、判断节点是否为NULL,为空返回"“表示为找到
2、判断是否为最后一个字符,是返回该节点的value,之后在判断value是否为”",为""表示未找到
3、获取字符对应的下标及下标中的下一个节点地址,递归/循环查找下一个字符。
string get(string key) {
if (_root == NULL) {
return "";
}
return _get(_root, key, 0);
}
string _get(Node* root, string key, int bit) {
if (root == NULL) {
return "";
}
if (bit == (int)key.size()) {
return root->_value;
}
int k = getChar(key[bit]);
if (root->_next[k] == NULL) {
return "";
}
return _get(root->_next[k], key, bit + 1);
}
4、获取所有的单词
1、遍历每个节点的next[0-25],同时计算出next下标对应的字符,
2、将计算的字符与上层计算结果拼接在一起,
3、当节点的value不为空时,将当前拼接的字符串保存起来。
4、最终打印出保存的字符串即可
void getAll() { //获取所有字符串
if (_root == NULL) return;
vector<string> str;
string ptr = "";
_getAll(_root, ptr, str);
Print(str);
}
void _getAll(Node* root, string ptr, vector<string>& str) {
if (root == NULL) return;
if (root->_value.size() > 0) { //表示为一个有效单词
str.push_back(ptr);
}
int i = 0;
for (i = 0; i < 26; i++) { //循环递归next中的每一个下标
_getAll(root->_next[i], ptr + (char)(i+'a'), str); //将该字符拼接起来,进行递归该字符对应的节点
}
}
4、获取匹配的字符串(.表示通配任意字符)
1、遇到NULL节点时退出
2、当长度达到给定字符串长度时并且value不为空,将遍历过程记录的字符串保存
3、计算给定字符的下标
4、循环节点中的每个next,递归给定字符对应的next下标或字符为’.'时进行递归所有位置(0-25)。
注:递归下一位置时,拼接上下一个位置对应的字符
void getMatch(string key) { //获取匹配的字符串(.表示通配任意字符)
if (_root == NULL) {
return;
}
vector<string> str;
_getMatch(_root, key, str, "");
Print(str);
}
void _getMatch(Node* root, string key, vector<string>& str, string ptr) {
if (root == NULL) {
return;
}
int bit = ptr.size();
if (key.size() == bit && root->_value.size() > 0) {
str.push_back(ptr);
}
int k = getChar(key[bit]);
int i = 0;
for (; i < 26; i++) {
if (i == k || key[bit] == '.') { //'.'表示任意一个字符
_getMatch(root->_next[i], key, str, ptr+(char)(i+'a'));
}
}
}
5、获取给定字符串的最大前缀单词
1、遍历key,同时将value不为空的长度记录下来/更新(len)
2、如果遇到空节点时表示已遍历到最大位置,返回记录的长度(len)
3、如果节点的已遍历到最后一个字符,则表示遍历结束,返回记录的长度(len)。
4、递归每一个位,直到遍历结束(key遍历完,遇到空节点)
5、截取key的前len个字符,即为最大前缀单词。
string longestPrefixOf(string key) { //获取最大前缀匹配的单词
if (_root == NULL) return "";
int len = 0;
len = _longestPrefixOf(_root, key, len, 0);
return key.substr(0, len); //截取字符串,从起始位置开始
}
int _longestPrefixOf(Node* root, string str, int &len,int bit) {
if (root == NULL) return len;
if (root->_value.size() > 0) { //有注释更新长度
len = bit;
}
if (str.size() == bit) { //达到最大长度,返回该长度
return len;
}
//递归下一个字符
int k = getChar(str[bit]);
return _longestPrefixOf(root->_next[k], str, len, bit + 1);
}
6、删除单词
1、先找到删除节点,然后将该位置value置空,再判断该节点中的next是否为空,不为空返回该节点,若为空将该节点删除。然后返回NULL
2、上层循环/递归接收下层的返回值,用于更新节点中的next。
3、同时若发现value值不为空则说明该节点是有效的,直接返回该节点,不用再判断节点中的next是否为NULL
具体步骤:
1、采用递归删除,从0bit位开始查找
2、达到长度后,将该节点的value置为""。
3、递归下一个位并且该位置接收返回的地址
4、如果节点的value存在,则说该节点有效,可直接返回该节点而不用再判断next
5、遍历节点的next,有不为空的地址,返回该节点地址,所有地址为空的,删除该节点,并返回NULL。
void deleteKey(string key) {
if (_root == NULL) return;
_root = deleteKey(_root, key, 0);
}
Node* deleteKey(Node* root, string str, int bit) {
if (root == NULL) return NULL;
if (bit == str.size()) {
root->_value = "";
}
else { //未找到该节点位置
int k = getChar(str[bit]);
//递归处理下一个字符,并且重新接收下一个字符的地址
root->_next[k] = deleteKey(root->_next[k], str, bit + 1);
}
if (root->_value.size() > 0) { //存在value,表示节点有效,不能删除,可直接返回节点地址
return root;
}
int i = 0;
//遍历删除next为空的节点
for (; i < 26; i++) {
if (root->_next[i]) {
return root;
}
}
delete root;
return NULL;
}
五、代码链接
https://github.com/weienjun/-algorithm/tree/master/word_tree