字首樹

n1ce2cv發表於2024-09-26

概念:

簡述:又名單詞查詢樹,tries樹,一種多路樹形結構,常用來操作字串(但不限於字串),和hash效率有一拼(二者效率高低是相對的,後面比較)。
性質:不同字串的相同字首只儲存一份。
操作:查詢,插入,刪除等。

舉個例子:
假設有這麼幾個單詞

abcd
abc
abd
bcq
edj

字首樹畫出如下


7888877-07b9e30807d65db4.png
字首樹.png

如上圖可以看出,abcd等字元是在邊上的,a的那條邊和指向的節點我們可以看成一個整體,儲存的資訊是在節點上

定義的資料結構:


    //節點
    public static class TrieNode{
        //以此節點結尾
        int end;
        //走過的路徑的次數,可以理解為一個節點和指向 它的路徑的值
        int path;
        //節點
        private TrieNode[] arr;

        public TrieNode(){
            end = 0;
            path = 0;
            arr= new TrieNode[26];
        }
    }

操作:

插入:

先建立一個根節點,根節點代表起點,起點可以有26條不同的路,代表26個字母,你可以想想一個根節點走出26個字元的路徑。然後用 index = chs[i] - 'a' 來計算屬於哪條路,在這裡解釋一下,-'a' 減去的是'a'的阿斯克碼,比如傳進來的是b 就相當於用b的阿斯克碼減去a的阿斯克碼,求出的就是由當前結點出去的那條路徑,其中end代表以當前結點結尾,path代表經過當前結點的個數,如上所說,當前結點和指向它的路徑可以看成一個整體,比如一個節點,指向它的由a,b,c,它的path就是3,知道了變數的意思,插入就簡單了,就判斷現在要加入的節點之前有沒有,沒有就加入,加完之後,把尾節點的end++即可

程式碼:

public static class Trie {
        private TrieNode root;

        public Trie() {
            root = new TrieNode();
        }

        public void insert(String word) {
            if (word == null) {
                return;
            }
            char[] chs = word.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i] - 'a';
                if (node.arr[index] == null) {
                    node.arr[index] = new TrieNode();
                }
                node = node.arr[index];
                node.path++;
            }
            node.end++;
        }
}

查詢:

查詢的整體操作和插入類似,查詢就隨著字串向下查詢,如果查的當前節點沒有,就返回false,直到查完,看尾節點的end是否大於零,大於零說明有該字元,如果為0就說明沒有,比如有字串abcd,你要查有沒有abc,雖然一直能查到c,但是c的end不是1,所以沒有

程式碼:


    public static class Trie {
        private TrieNode root;

        public Trie() {
            root = new TrieNode();
        }
              
        public boolean search(String word) {
            if (word == null) {
                return false;
            }
            char[] chs = word.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i] - 'a';
                if (node.arr[index] == null) {
                    return false;
                }
                node = node.arr[index];
            }
            return node.end != 0;
        }
}

刪除:

刪除就判斷當前節點的--path是否為0,為0就說明要刪除的就是這個路徑,而且只有要刪除的那一個字串在這條大路徑上,刪除就行,另一種情況,abcd,abc,要刪除abc,就相當於大路徑還在,只不過把每個節點的--path,然後c的end--就行了

程式碼:

public static class Trie {
        private TrieNode root;

        public Trie() {
            root = new TrieNode();
        }

                public void delete(String word) {
            if (search(word)) {
                char[] chs = word.toCharArray();
                TrieNode node = root;
                int index = 0;
                for (int i = 0; i < chs.length; i++) {
                    index = chs[i] - 'a';
                    if (node.arr[index].path-- == 1) {
                        node.arr[index] = null;
                        return;
                    }
                    node = node.arr[index];
                }
                node.end--;
            }
        }

查詢是否包含此字首:

如果當前結點的下一個節點不為空,就一直找下去,為空就返回0,查到最後就,返回當前結點的path值,代表有幾個字串有此字首

程式碼:

public static class Trie {
        private TrieNode root;

        public Trie() {
            root = new TrieNode();
        }

        public int prefixNumber(String pre) {
            if (pre == null) {
                return 0;
            }
            char[] chs = pre.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i] - 'a';
                if (node.arr[index] == null) {
                    return 0;
                }
                node = node.[index];
            }
            return node.path;
        }
    }

相關文章