单向单词树

本文介绍了单向单词树的概念,包括其树结构、时间复杂度和空间复杂度。详细阐述了单向单词树的节点数据结构、插入、查找、获取所有单词、获取最大前缀单词的操作,并提供了完整的代码实现链接。单向单词树是一种高效的单词查找数据结构,适用于单词查找和管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、本章主要内容:
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值