高级数据结构-Trie树、并查集

1. Trie树

Trie树,又称字典树或前缀树,是一种有序的、 用于统计、排序和存储字符串的数据结构,它 与二叉查找树不同,关键字不是直接保存在节点 中,而是由节点在树中的位置决定,每个节点 代表了一个字符,从第一层孩子节点到中间的 某个标记的节点代表了存储的字符串。 一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串 。一般情况下,不是所有的节点都有对应的字符 串,只有叶子节点和部分内部节点进行标记, 才存储了字符串。

trie树的最大优点就是利用字符串的公共前缀来 减少存储空间与查询时间,从而最大限度地减少 无谓的字符串比较,是非常高效的字符串查找数 据结构,查找和插入字符串都可达到O(1)算法复 杂度。

trie中的键通常是字符串,但也可以是其它的结构。trie的算法可以很容易地修改为处理其它结构的有序序列,比如一串数字或者形状的排列。比如,bitwise trie中的键是一串位元,可以用于表示整数或者内存地址

基本性质

1,根节点不包含字符,除根节点意外每个节点只包含一个字符。
2,从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
3,每个节点的所有子节点包含的字符串不相同。

优点:
可以最大限度地减少无谓的字符串比较,故可以用于词频统计和大量字符串排序。

跟哈希表比较:

1,最坏情况时间复杂度比hash表好

2,没有冲突,除非一个key对应多个值(除key外的其他信息)

3,自带排序功能(类似Radix Sort),中序遍历trie可以得到排序。

缺点:
1,虽然不同单词共享前缀,但其实trie是一个以空间换时间的算法。其每一个字符都可能包含至多字符集大小数目的指针(不包含卫星数据)。

每个结点的子树的根节点的组织方式有几种。1>如果默认包含所有字符集,则查找速度快但浪费空间(特别是靠近树底部叶子)。2>如果用链接法(如左儿子右兄弟),则节省空间但查找需顺序(部分)遍历链表。3>alphabet reduction: 减少字符宽度以减少字母集个数。,4>对字符集使用bitmap,再配合链接法。

2,如果数据存储在外部存储器等较慢位置,Trie会较hash速度慢(hash访问O(1)次外存,Trie访问O(树高))。

3,长的浮点数等会让链变得很长。可用bitwise trie改进。

Trie树的数据结构和遍历代码:

#include<stdio.h>
#define TIRE_MAX_NUM 26

struct TrieNode{
    TrieNode *child[TIRE_MAX_NUM];
    bool is_end;
    TrieNode():is_end(false){
        for(int i=0; i<TIRE_MAX_NUM; ++i){
            child[i] = 0;
        }
    }
};

void preorder_trie(TrieNode* node, int layer){
    for(int i=0; i<TIRE_MAX_NUM; ++i){
        if(node->child[i]){
            for(int j=0; j<layer; ++j){
                printf("---");
            }
            printf("%c", i+'a');
            if(node->is_end){
                printf("(end)");
            }
            printf("\n");
            preorder_trie(node->child[i], layer++);
        }
    }
}

Trie树的insert,search,startwith操作:
LeetCode 208
https://leetcode-cn.com/problems/implement-trie-prefix-tree/

class Trie {
    Trie *child[26];
    bool is_end;
    
public:
    /** Initialize your data structure here. */
    Trie() {
        is_end = false;
        for(int i=0; i<26; ++i){
            child[i] = nullptr;
        }
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        Trie *t = this;
        for(char c:word){
            int pos = c-'a';
            if(!t->child[pos]){
                t->child[pos] = new Trie();
            }
            t = t->child[pos];
        }
        t->is_end = true;
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        Trie *t = this;
        for(char c:word){
            int pos = c-'a';
            if(!t->child[pos]){
                return false;
            }
            t = t->child[pos];
        }
        return t->is_end;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        Trie *t = this;
        for(char c:prefix){
            int pos = c-'a';
            if(!t->child[pos]){
                return false;
            }
            t = t->child[pos];
        }
        return true;
    }
};

2.并查集

并查集(Union Find),又称不相交集合(Disjiont Set),它应用于N个元素的集合求并与查 询问题,在该应用场景中,我们通常是在开始时让每个元素构成一个单元素的集合, 然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在 哪个集合中。虽然该问题并不复杂,但面对极大的数据量时,普通的数据结构往往无 法解决,并查集就是解决该种问题最为优秀的算法。

使用森林存储集合之间的关系,属于同一集合的不同元素,都有一个相同的根节点 ,代表着这个集合。

当进行查找某元素属于哪个集合时,即遍历该元素到根节点,返回根节点所代表的集 合;在遍历过程中使用路径压缩的优化算法,使整体树的形状更加扁平,从而优化查 询的时间复杂度。 当进行合并时,即将两颗子树合为一颗树,将一颗子树的根节点指向另一颗子树的根 节点;在合并时可按子树的大小,将规模较小的子树合并到规模较大的子树上,从而 使树规模更加平衡,从而优化未来查询的时间复杂度。

并查集的find和union操作:

#include<iostream>
using namespace std;
class DisJointSet{
    private:
        vector<int> id;
        vector<int> size;
        int count;
    public:
    DisJointSet(int n){
        for(int i=0; i<n; ++i){
            id[i] = i;
            size[i] = 1;
        }
        count = n;
    }
    int find(int p){
        while(p!=id[p]){
            id[p] = id[id[p]];
            p = id[p];
        }
        return p;
    }
    void union_(int p, int q){
        int i = find(p);
        int j = find(q);
        if(i==j){
            return;
        }
        if(size[i]<size[j]){ 
            id[i] = j;
            size[j] += size[i];
        }
        else{
            id[j] = i;
            size[i] += size[j];
        }
        count--;
    }
    int count_(){
        return count;
    }
};

class Solution {
public:
    int findCircleNum(vector<vector<int>>& M) {
        DisJointSet djset(M.size());
        for(int i=0; i<M.size(); ++i){
            for(int j=i+1; j<M.size(); ++j){
                if(M[i][j]){
                    djset.union_(i, j);
                }
            }
        }
        return djset.count_();
    }
};

int main(){
    Solution s;
    s.findCircleNum(M);
}

Leetcode 547 朋友圈
https://leetcode-cn.com/problems/friend-circles/

方法1:DFS
用时96 ms
内存消耗83.4 MB

class Solution {
public:
    void DFS(int i, vector<vector<int>> M, vector<int> &visited){
        visited[i]=1;
        for(int j=0; j<M.size(); ++j){
            if(!visited[j] && M[i][j]==1){
                DFS(j, M, visited);
            }
        }
    }
    int findCircleNum(vector<vector<int>>& M) {
        int n = M.size();
        vector<int> visited(n, 0);
        int count = 0;
        for(int i=0; i<n; ++i){
            if(visited[i]==0){
                DFS(i, M, visited);
                count++;
            }
        }
        return count;

    }
    
};

方法2:并查集
用时16 ms
内存消耗11 MB,比DFS方法要好很多

#include<iostream>
using namespace std;
class DisJointSet{
        public:

        vector<int> id;
        vector<int> size;
        int count;
    DisJointSet(int n){
        for(int i=0; i<n; ++i){
            id.push_back(i);
            size.push_back(1);
        }
        count = n;
    }
    int find(int p){
        while(p!=id[p]){
            id[p] = id[id[p]];
            p = id[p];
        }
        return p;
    }
    void union_(int p, int q){
        int i = find(p);
        int j = find(q);
        if(i==j){
            return;
        }
        if(size[i]<size[j]){ 
            id[i] = j;
            size[j] += size[i];
        }
        else{
            id[j] = i;
            size[i] += size[j];
        }
        count--;
    }
    int count_(){
        return count;
    }
};

class Solution {
public:
    int findCircleNum(vector<vector<int>>& M) {
        DisJointSet djset(M.size());
        for(int i=0; i<M.size(); ++i){
            for(int j=i+1; j<M.size(); ++j){
                if(M[i][j]==1){
                    djset.union_(i, j);
                }
            }
        }
        return djset.count_();
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值