• Suffix Tree(后缀树)


      这篇简单的谈谈后缀树原理及实现。

      如前缀树原理一般,后缀trie树是将字符串的每个后缀使用trie树的算法来构造。例如banana的所有后缀:

    0: banana
    1:  anana
    2:   nana
    3:    ana
    4:     na
    5:      a
    

      按字典序排列后:

    5: a
    3:  ana
    1:     anana
    0: banana
    4: na
    2:   nana
    

      形成一个树形结构。

      代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    // banana中不重复的字符有:a b n
    /*
     *   a   b   n
     *  n $  a   a
     *  a    n  n $
     * n $   a  a
     * a     n  $
     * $     a
             $*/
    #define SIZE 27
    #define Index(c) ((c) - 'a')
    #define rep(i, a, b) for(i = a; i < b; i++)
    typedef struct BaseNode {
    	struct BaseNode*next[SIZE];
    	char c;
    	int num;
    } suffix_tree, *strie;
    void initialize(strie* root)
    {
    	int i;
    	*root = (strie)malloc(sizeof(suffix_tree));
    	(*root)->c = 0;
    	(*root)->num = -1;
    	rep(i, 0, SIZE) (*root)->next[i] = NULL;
    }
    void insert(strie*root, const char*str, int k)
    {
    	suffix_tree*node = *root, *tail;
    	int i, j;
    	for (i = 0; str[i] != ''; i++)
    	{
    		if (node->next[Index(str[i])] == NULL)
    		{
    			tail = (strie)malloc(sizeof(suffix_tree));
    			tail->c = str[i];
    			tail->num = -1;
    			rep(j, 0, SIZE) tail->next[j] = NULL;
    			node->next[Index(str[i])] = tail;
    		}
    		node = node->next[Index(str[i])];
    	}
    	tail = (strie)malloc(sizeof(suffix_tree));
    	tail->c = '$';
    	tail->num = k;
    	rep(i, 0, SIZE) tail->next[i] = NULL;
    	node->next[SIZE - 1] = tail;
    }
    void show(suffix_tree*root)
    {
    	if (root)
    	{
    		int i;
    		rep(i, 0, SIZE) show(root->next[i]);
    		printf("%c
    ", root->c);
    		if (root->num > -1)
    		{
    			printf("%d
    ", root->num);
    		}
    	}
    }
    void destory(strie*root)
    {
    	if (*root)
    	{
    		int i;
    		rep(i, 0, SIZE) destory(&(*root)->next[i]);
    		free(*root);
    		*root = NULL;
    	}
    }
    int main()
    {
    	suffix_tree*root;
    
    	initialize(&root);
    
    	char str[] = "banana", *p = str;
    	int i = 0;
    	while(*p)
    	{
    		insert(&root, p, i);
    		p++;
    		i++;
    	}
    	show(root);
    	destory(&root);
    	return 0;
    }

      时间复杂度分析:算法中对于建立一串长m的字符串,需要一个外层的m次循环 + 一个内层m次循环 + 一些常数,于是建立一颗后缀字典树所需的时间为O(m2),27的循环在这里可看作常数;

      空间复杂度分析:一个字符的字符串长度为1,需要消耗的1个该字符 + 1个根节点 + 1个$字符的空间,两个字符的字符串长度为2,需要消耗3个字符空间+ 1个根节点 + 2个$空间...以此类推,发现总是含有1个根节点和m个$字符,$的个数等于字符串长度m,而存储的源字符串后缀所需的空间有如下规律:

    $$ egin{aligned} O(s_1) &= 1 \ O(s_2) &= 1+2 \ O(s_3) &= 1+2+3 \ cdot cdot cdot \ O(s_m) &= 1+2+ cdot cdot cdot + m end{aligned} $$

      设以长为m的字符串s建立后缀树T,于是有:

    $$ O(T) = O(frac{(1 + m)m}{2} + 1 + m) = O(m^2) $$

      由于上面算法对于无重复的字符串来说空间复杂度比较大,所以使用路径压缩以节省空间,这样的树就称为后缀树,也可以通过下标来存储,如图:

      p.s.写压缩路径的后缀树时,脑子犯傻了...错了,改天再把正确的补上。。。

      路径压缩版后缀树:

    #include <iostream>
    using namespace std;
    #define rep(i, a, b) for(int i = a; i < b; i++)
    #define trans(c) (c - 'a')
    #define SIZE 26
    #define MAX (100010 << 2)
    struct BaseNode {
    	int len;
    	const char*s;
    	int pos[MAX];
    	BaseNode*next[SIZE];
    	BaseNode()
    	{
    		len = 0;
    		rep(i, 0, MAX) pos[i] = 0;
    		rep(i, 0, SIZE) next[i] = nullptr;
    	}
    	BaseNode(const char*s, int p)
    	{
    		this->s = s, this->len = p;
    		rep(i, 0, MAX) pos[i] = 0;
    		rep(i, 0, SIZE) next[i] = nullptr;
    	}
    };
    class SuffixTree {
    private:
    	BaseNode*root;
    	/**/
    	void add(const char*s, int p);
    	void print(BaseNode*r);
    	void destory(BaseNode*&r);
    public:
    	SuffixTree()
    	{
    		root = nullptr;
    	}
    	void insert(const char*s);
    	void insert(string s)
    	{
    		insert(s.c_str());
    	}
    	void remove(const char*s)
    	{
    
    	}
    	void visual()
    	{
    		print(root);
    	}
    	bool match(const char*s);
    	bool match(string s)
    	{
    		match(s.c_str());
    	}
    	~SuffixTree()
    	{
    		destory(root);
    	}
    };
    void SuffixTree::add(const char*s, int p)
    {
    	int i = 0; while (s[i]) i++;
    	if (!root->next[p]) root->next[p] = new BaseNode(s, i);
    	root->next[p]->pos[i] = i;
    }
    void SuffixTree::insert(const char*s)
    {
    	root = new BaseNode();
    	while (*s)
    	{
    		add(s, trans(*s));
    		s++;
    	}
    }
    bool SuffixTree::match(const char*s)
    {
    	const char* ps = root->next[trans(*s)]->s;
    	while (*s) if (*ps++ != *s++) return false;
    	return true;
    }
    void SuffixTree::print(BaseNode*r)
    {
    	if (r)
    	{
    		rep(i, 0, SIZE)
    			if (r->next[i])
    			{
    				cout << i << ':' << endl;
    				rep(j, 0, r->next[i]->len + 1)
    					if (r->next[i]->pos[j])
    					{
    						rep(k, 0, r->next[i]->pos[j])
    							cout << r->next[i]->s[k];
    						cout << '$' << endl;
    					}
    			}
    	}
    }
    void SuffixTree::destory(BaseNode*&r)
    {
    	if (r)
    	{
    		rep(i, 0, SIZE) destory(r->next[i]);
    		delete r;
    	}
    }
    int main()
    {
    	SuffixTree st;
    	st.insert("banana");
    	st.visual();
    	if (st.match("na")) cout << "Yes" << endl;
    	else cout << "No" << endl;
    	return 0;
    }

      上面的后缀树都是对于一个字符串的处理方法,而广义后缀树将算法推广到了不同的字符串上,但我还没写过,改天补上。。。

      参考:https://en.wikipedia.org/wiki/Suffix_tree

  • 相关阅读:
    【Java】Junit快速入门
    【Redis】Redis Sentinel 哨兵模式搭建
    【Redis】Redis 主从模式搭建
    Android开发过程中的坑及解决方法收录(六)
    杂牌机搞机之旅(一)——获得root权限(刷入magisk)
    Java 学习笔记 泛型
    Java 学习笔记 反射与迭代器
    Java 学习笔记 正则表达式
    Java 学习笔记 执行外部命令 包装类 枚举类型
    IDEA设置显示中文文档API方法说明
  • 原文地址:https://www.cnblogs.com/darkchii/p/9116558.html
Copyright © 2020-2023  润新知