码迷,mamicode.com
首页 > 其他好文 > 详细

Trie图

时间:2015-09-18 23:26:01      阅读:329      评论:0      收藏:0      [点我收藏+]

标签:

DFA 确定性有限状态自动机

    DFA确定性有限状态自动机是一种图结构的数据结构,可以由(Q, q0, A, Sigma, Delta)来描述,其中Q为状态集,q0为初始状态,A为终态集合,Sigma为字母表,Delta为转移函数。它表示从唯一一个起始状态q0开始,经过有限步的Delta转移,转移是根据字母表Sigma中的元素来进行,最终到达终态集合A中的某个状态的状态移动。 
技术分享 
如图所示是一个终态集合为{"nano"}的DFA。 
    DFA只能有一个起点而可以有多个终点。每个节点都有字符集大小数条有向边,并且任一节点,都不会存在相同字符的有向边指向不同的节点。

Trie树

    Trie树为单词前缀树,m个模式串中的前缀所组成的集合A与根节点到每一个树中的节点的路径上的字符组成的字符串S所组成的集合B,是满射关系。具体参见:Trie树 
    例如可以利用Trie树求字符串S的所有不同子串: 
    假设当前字符串为S,用S的所有后缀作为len(S)个模式串,插入到一棵Trie树中,Trie树中的每个节点对应的字符串就是字符串S中的一个子串,不同子串一定对应不同的节点。

Trie图

1. Trie图结构 
    Trie图为以Trie树为基础构造出来的一种DFA。对于插入的每个模式串,其插入过程使用的最后一个节点都作为DFA的一个“终止”节点。 
    如果要求一个母串包含哪些模式串(即该母串的某个子串恰好等于预先给定的某个模式串),以母串作为DFA的输入 ,在DFA上行走,走到“终止”节点就意味着匹配了相应的模式串。

2. 失配处理 
    在行走的过程中,如果出现母串中的下一个字符在Trie图中当前位置处没有一个子节点与之对应,或者Trie图中的当前位置正好匹配了一个模式串,那么需要调整母串重新匹配的位置。一般,可以调整母串上开始匹配的位置,使之加1,再尝试从Trie图的根节点位置开始匹配。这样显然效率很低。可以参考KMP算法的最长相同前后缀的方法,来避免回溯。

3. 前缀指针 
    为了避免回溯,参考KMP的next数组,在Trie图中定义“前缀指针”:

从根节点到节点P可以得到一个字符串S,节点P的前缀指针定义为 指向树中出现过的S的最长后缀(不能等于S)

4. 高效的构造前缀指针 
    根据深度一一求出每一个节点的前缀指针。对于当前节点,设它的父节点与它的边上的字符为ch,如果它的父节点的前缀指针所指向的节点的儿子节点中,有通过ch字符指向的儿子,那么当前节点的前缀指针指向该儿子节点,否则通过当前节点父节点的前缀指针指向节点的前缀指针,继续向上查找,直到到达根为止。 
技术分享

5. 危险节点 
(1)“终止”节点是危险节点 
(2)如果一个节点的前缀指针指向危险节点,则该节点为危险节点

6. 在建立好的Trie图上遍历 
    从root出发,按照当前串的下一个字符ch进行在树上的移动。若当前点P不存在通过ch连接的儿子,那么将P的前缀指针所指向的节点Q作为当前节点; 
    如果还无法找到通过ch连接的儿子节点,再考虑Q的前缀指针指向的节点(将之作为当前节点)....; 
    直到找到通过ch连接的儿子,再继续遍历; 
    如果遍历的过程中经过了某个非终止节点的危险节点,则可以断定S包含某个模式串,要找出是哪个模式串,沿着危险节点的前缀指针走,碰到终止节点即可。

前缀指针思想

    Trie通过前缀指针来避免母串的回溯,其思想和KMP算法非常相似。 
KMP算法是通过确定子串中失配点之前的子串的最长相同前后缀,失配时,母串当前点不回溯,而是直接和最长相同前后缀的前缀处继续进行匹配。 
技术分享 
                                            (kmp 避免母串指针回溯)

    和KMP类似,Trie图中的每个节点都对应一个模板串(节点为终止节点)或者模板串的子串(节点不是终止节点),记为S。S可以确定len(S)-1个后缀(从S中的第2到第len(S)-1个位置到S的末尾确定),其中有些后缀串Si可能正好对应该Trie图中从root节点出发的到某个节点Pi确定的串。

技术分享
    如上图所示,绿色方块区域为从母串上一个开始匹配点到失配点之前的匹配区域,红色为失配点,该绿色匹配区域中有两个后缀子串sub1[S1,A]区域和sub2[S2,A]区域,分别对应Trie图中从root出发到P1,P2点确定的串。且母串中[S1,E1]和[S2,E2]分别对应一个模式串。

母串不回溯,Trie图上当前点的移动,可以匹配母串中存在的所有的模式串 
    以上图为例此时,需要确定母串指针之后的移动可以找到[S1,E1]和[S2,E2]两个模式串,策略是先匹配起点靠前的那个串,即[S1,E1]。 
case 1 E1 > E2 
技术分享
    母串指针不回溯,Trie图的当前点转移到P1(从root到P1对应[S1,A]),然后尝试匹配。匹配成功,到达E1点,此时将Trie图中点从E1移动到E1的前缀指针,由于[S2,E1]为[S1,E1]的最长后缀,即移动到点P,使得root到P为[S2,E1]。因为Trie图中[S2,E2]对应从root出发到某个点Q的串,那么root到Q的路径必然经过点P,此时从点P继续匹配,必然能够到达Q; 
这样,就得到了[S1,E1]和[S2,E2]两个模式串。

case 2 E1 < E2 
技术分享
    母串指针不回溯,Trie图的当前点转移到P1(从root到P1对应[S1,A]),然后尝试匹配。由于[S1,E1]对应一个模式串,即对应Trie图中的某个终止节点,从P1点开始会一直匹配到达P(从root到P对应[S1,E1])。在匹配的过程中,会碰到某个点危险节点M,M指向节点Q(从root到Q对应[S2,E2])(这是在设置Trie图中各个节点的前缀指针的时候确定的),根据Trie图的遍历规则,会得到[S2,E2]的模式串。这样,就得到了[S1,E1]和[S2,E2]两个模式串。

Trie图实现(c++)

#include<iostream>
#include<vector>
#include<queue>
#include<string>
using namespace std;
#define LETTERS 26
int gNodeCount = 1;
struct Node{
	Node* childs[LETTERS];	//子节点
	Node* prev;				//前缀指针
	bool danger_node;		//是否危险节点
	Node(){
		Init();
	}
	void Init(){
		memset(childs, 0, sizeof(childs));
		danger_node = false;
		prev = NULL;
	}
};
Node gNodes[2000];
void Insert(Node* root, char* str){
	char* p = str;
	Node* node = root;
	while (*p != ‘\0‘){
		int index = *p - ‘A‘;
		if (node->childs[index] == 0){
			node->childs[index] = gNodes + gNodeCount ++;
		}
		node = node->childs[index];
		p++;
	}
	node->danger_node = true;
}

//在Trie树上添加前缀指针
void BuildDfa(){
	for (int i = 0; i < LETTERS; i++){ //0 为一个虚拟节点
		gNodes[0].childs[i] = gNodes + 1;
	}
	gNodes[0].prev = NULL;
	gNodes[1].prev = gNodes;
	deque<Node*> Q;
	Q.push_back(gNodes + 1);
	while (!Q.empty){
		Node* node = Q.front();
		Node* prev = node->prev;
		Node*p;
		Q.pop_front();
		for (int i = 0; i < LETTERS; i++){
			if (node->childs[i]){
				p = prev;
				while (p && !p->childs[i]){
					p = p->prev;
				}
				node->childs[i]->prev = p->childs[i];
				node->childs[i]->danger_node = p->childs[i]->danger_node;
				Q.push_back(node->childs[i]);
			}
		}
	}
}

bool SearchDfa(char* str){
	char*p = str;
	Node* node = gNodes + 1;
	while (*p != ‘\0‘){
		int index = *p - ‘A‘;
		if (node->danger_node)
			return true;

		while (node&& !node->childs[index]){
			node = node->prev;
		}
	}
	return false;
}

 

Trie图

标签:

原文地址:http://www.cnblogs.com/gtarcoder/p/4820560.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!