左神算法基础class7—题目1前缀树一系列问题

1.题目:前缀树一系列问题

生成前缀树,包括添加、删除、查找的功能,此外还需要查找某个前缀出现的次数。

2.前缀树

(1)前缀树介绍

前缀树用于统计词频,如下图当我们添加“abc”,“abd”,“bce”,“be”,“bef”时就会得到如下树。其中根节点不存储字符,只起标记位置作用,为了存储26个字母,每个节点有26条隐含的路可分别存储a-z的字符,添加上节点时,路才显示为存储的字符,那么可以看出这种树和一般的树不同,最多每个节点有26个子节点;如果像存储“abc”,“abd”这样,不同字符串的相同位置“ab”相同时,使用同一个节点。

在这里插入图片描述
为了增加前缀树的功能,我们在节点上增加两个变量path和end初始化为0。end很好理解就是在结尾的地方记录一下,这样如果某个节点end不为0,那么这个节点必为终止节点。如下图“bef”上e处end为1,f为1,这样就可得到存储得是“be”,和“bef”;而path变量的作用是,这个节点经过了几次,它的用途是记录有多少个字符串使用这个前缀。比如说“abc”,“abd”,这条链a处path为2,表示整个树中存储“a”这样的前缀共有两个。下图红色和蓝色表示某个节点的end和path值。

在这里插入图片描述

(2)前缀树类的实现

①节点类

对于每个节点,我们需要记录end、path两个变量,此外还需要使用数组来表示节点通向子节点的26条路。初始化时,path、end为0,使用memset函数将26个数组也置为0。

#define num 26

class TrieNode
{
public:
	int path;	//经过的次数
	int end;	//结束的次数
	TrieNode* nexts[num];

	//构造
	TrieNode():path(0),end(0)
	{ //memset(结构体/数组名,用于替换的ASCII码对应字符,前n个字符 );
		memset(nexts,0,sizeof(nexts));
	}

};

②树类

使用节点类定义根节点。

class TrieTree
{
private:
	TrieNode *root;
public:
    TrieTree();
    ~TrieTree();
    //成员函数
	void destory(TrieNode *node);
	void insert(string word);
	int research(string word);
	void delete_word(string word);
	int prefixNumber(string pre);
};
//构造
TrieTree::TrieTree()
{
	root = new TrieNod();
}
//析构
TrieTree::~TrieTree()
{
	destory(root);
}	
//删除
void TrieTree::destory(TrieNode *root)
{
	if(root ==NULL)
		return;
	for(int i = 0; i < num; i++)
	{	//每个new出的数组需要删除
		destory(root->nexts[i]);
	}
	delete root;
	root = NULL;
}

3.成员函数

(1)添加函数

整体思路:如果插入的字符串为空,直接返回;将插入的字符串转换为字符数组存储,对整个数组遍历,如果当前不存在通向某个字符的路,就新建一条,再把path++,最后再把end++

void TrieTree::insert(string word)
{
	if(word == "")								//插入字符串为空
		return;
	char *buf = new char[word.size()];			//开辟字符串长度的数组
	strcpy(buf,word.c_str());					//转换为c的char字符串
	TrieNode *node = root;
	int index = 0;
	for(int i = 0;i < strlen(buf); i++)
	{
		index = buf[i] - 'a';					//将输入的字符转换为索引值a是0,b是1...
		if(node->nexts[index] == NULL)			//这条路未被创建
		{
			node->nexts[index] = new TrieNode();//创建路
		}
		node = node->nexts[index];				//指向新建的路
		node->path++;							//经过+1
	}
	node->end++;								//最后再记录结束
	

(2)查找函数

与添加函数基本类似,不同之处在于遍历时如果查找不到通向某个字符的路,就返回不存在,如果每个都能找到,返回记录终点的end。

int TrieTree::research(string word)
{
	if(word == "")
		return 0;
	char *buf = new char[word.size()];
	//char *strcpy(char* dest, const char *src)
	//string类对象的成员函数c_str()把string 对象转换成c中的字符串
	strcpy(buf,word.c_str());
	int index = 0;
	TrieNode *node = root;
	for(int i = 0;i < strlen(buf);i++)
	{
		index = buf[i] - 'a';
		if(NULL == node->nexts[index])
			return 0;
		node = node->nexts[index];
	}
	return node->end;
}

(3)删除函数

删除的过程先进行查找,如果不存在直接退出。存在时,数组遍历对每个经过的path减一,如果path变为0说明,这条子树链只有一次需要删除,删除时需要用中间变量记录后一个节点的位置。

void TrieTree::delete_word(string word)
{
	if(research(word) == 0)		//先进行查找确定存在
		return;
	TrieNode *node = root;
	TrieNode *tmp = root;
	int index = 0;
	char *buf = new char[word.size()];
	strcpy(buf,word.c_str());
	for(int i = 0;i < strlen(buf);i++)
	{
		index = buf[i] - 'a';
		tmp = node->nexts[index];			//使用中间变量记录当前节点的下一个节点
		if(--node->nexts[index]->path == 0)	//如果将--path后,节点的path为0,表示以后的节点都不存在
			delete node->nexts[index];		//释放
		node = tmp;							//当前节点移动到下一个节点
	}
	node->end--;
}

(4)查找某前缀出现次数

前缀不能通过查找函数进行查找,因为前缀上end不存在,所以需要遍历数组进行判断,如果在遍历中节点为0,表示不存在此前缀,直接返回0退出,遍历后都存在则返回path表示经过节点的次数就是某前缀的次数。

int TrieTree::prefixNumber(string pre) 
{
	if(pre == "")
		return 0;
	char *buf = new char[pre.size()];
	strcpy(buf,pre.c_str());

	TrieNode * node = root;
	int index = 0;
	for(int i = 0;i < strlen(buf);i++)
	{
		index = buf[i] - 'a';
		if(node->nexts[index] == NULL)	//不存在此前缀直接返回0个
			return 0;
		node = node->nexts[index];		
	}
	return node->path;
}

4.完整代码

#include<iostream>
#include<string>
using namespace std;
#define num 26

class TrieNode
{
public:
	int path;	//经过的次数
	int end;	//结尾的次数
	TrieNode* nexts[num];

	//构造
	TrieNode():path(0),end(0)
	{ //memset(结构体/数组名,用于替换的ASCII码对应字符,前n个字符 );
		memset(nexts,0,sizeof(nexts));
	}
	
};

class TrieTree
{
private:
	TrieNode *root;
public:
    TrieTree();
    ~TrieTree();
	void destory(TrieNode *node);
	void insert(string word);
	int research(string word);
	void delete_word(string word);
	int prefixNumber(string pre);
};

//构造
TrieTree::TrieTree()
{
	root = new TrieNode();
}
//析构
TrieTree::~TrieTree()
{
	destory(root);
}	
//删除
void TrieTree::destory(TrieNode *root)
{
	if(root ==NULL)
		return;
	for(int i = 0; i < num; i++)
	{	//每个new出的数组需要删除
		destory(root->nexts[i]);
	}
	delete root;
	root = NULL;
}
//添加函数
void TrieTree::insert(string word)
{
	if(word == "")								//插入字符串为空
		return;
	char *buf = new char[word.size()];			//开辟字符串长度的数组
	strcpy(buf,word.c_str());					//转换为c的char字符串
	TrieNode *node = root;
	int index = 0;
	for(int i = 0;i < strlen(buf); i++)
	{
		index = buf[i] - 'a';					//将输入的字符转换为索引值a是0,b是1...
		if(node->nexts[index] == NULL)			//这条路未被创建
		{
			node->nexts[index] = new TrieNode();//创建路
		}
		node = node->nexts[index];				//指向新建的路
		node->path++;							//经过+1
	}
	node->end++;								//最后再记录结束
	
}

//查找函数
int TrieTree::research(string word)
{
	if(word == "")
		return 0;
	char *buf = new char[word.size()];
	//char *strcpy(char* dest, const char *src)
	//string类对象的成员函数c_str()把string 对象转换成c中的字符串
	strcpy(buf,word.c_str());
	int index = 0;
	TrieNode *node = root;
	for(int i = 0;i < strlen(buf);i++)
	{
		index = buf[i] - 'a';
		if(NULL == node->nexts[index])
			return 0;
		node = node->nexts[index];
	}
	return node->end;
}
//删除函数
void TrieTree::delete_word(string word)
{
	if(research(word) == 0)		//先进行查找确定存在
		return;
	TrieNode *node = root;
	TrieNode *tmp = root;
	int index = 0;
	char *buf = new char[word.size()];
	strcpy(buf,word.c_str());
	for(int i = 0;i < strlen(buf);i++)
	{
		index = buf[i] - 'a';
		tmp = node->nexts[index];			//使用中间变量记录当前节点的下一个节点
		if(--node->nexts[index]->path == 0)	//如果将--path后,节点的path为0,表示以后的节点都不存在
			delete node->nexts[index];		//释放
		node = tmp;							//当前节点移动到下一个节点
	}
	node->end--;
}
//查找某前缀次数函数
int TrieTree::prefixNumber(string pre) 
{
	if(pre == "")
		return 0;
	char *buf = new char[pre.size()];
	strcpy(buf,pre.c_str());

	TrieNode * node = root;
	int index = 0;
	for(int i = 0;i < strlen(buf);i++)
	{
		index = buf[i] - 'a';
		if(node->nexts[index] == NULL)	//不存在此前缀直接返回0个
			return 0;
		node = node->nexts[index];		
	}
	return node->path;
}


int main()
{
	//////string str = "aaaaa";
	////////vector<char> b(str.size());
	//////char *buf = new char[str.size()];
	//////strcpy(buf, str.c_str());
	TrieTree a;
	a.insert("abdd");
	a.insert("abc");
	a.insert("abcd");
	a.research("abdd");
	a.delete_word("abc");
	a.insert("abd");
	cout<<a.prefixNumber("abcd")<<endl;

	return 0;
}

发布了51 篇原创文章 · 获赞 1 · 访问量 1366

猜你喜欢

转载自blog.csdn.net/shi_xiao_xuan/article/details/104251651