高級數據結構-0

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_();
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章