算法:用Java實現有序trie樹,並用於實現單詞字典排序的功能

上一遍博客,我已經給大家介紹過trie樹。trie樹算是一種前綴樹,用來儲存單詞再合適不過了,而且很多單詞都有相同的前綴,如果用這個樹來存單詞,那是相當省空間的。今天我要實現的這個有序trie樹,和上一篇博客的trie樹的最大不同,就是今天的有序trie樹,字母的下級結點集合是用的TreeSet實現的。這就使得每個字母結點的下級結點都是按照字典序列來排布的。

這樣設計有什麼好處呢?如果我們仿照二叉樹的先序遍歷方式來遍歷有序trie樹,那麼結果會是相當有趣的:有序trie樹儲存的單詞會被按照字典序,在遍歷過程中有序生成出來。這裏當然不是先序遍歷,但是類似先序遍歷:先遍歷根結點,然後再遍歷從左起第一個結點,之後就依次遍歷右邊的節點了。由於是遞歸遍歷,所以遍歷過程中會先走遍trie樹左邊的分支,左邊的分支都走完了,纔開始走右邊的分支。你也可以理解爲一種貪婪算法,見到左邊的元素就先遍歷,直到沒有左邊的元素了,纔開始嘗試右邊的元素。

來個圖,比如我構建以下幾個單詞的有序trie樹:he、hell、hop、boil、base、bass、bee,那麼構建的有序trie樹就會是這樣(有序trie樹跟加入單詞的順序無關,也就是你無論以什麼順序加入這些單詞,最終生成的有序trie樹都是一樣的):
在這裏插入圖片描述
上面的圖,哪裏體現了有序呢?每一個字母結點的所有子節點,從左到右都是按字母順序來排列的。下圖的藍色框,就標出了有序的地方:
在這裏插入圖片描述
而前面說到的類似二叉樹先序遍歷的遍歷反式,大概是如下圖所示(畫得不好,請見諒):
在這裏插入圖片描述
接下來上代碼,有序trie樹的字母結點的實現與添加單詞的實現和上一篇博客的trie樹實現一樣,只不過是字母結點的下一字母集合變成了有序的TreeSet,也就是上面第二張圖藍色長方形框住的地方。然後我在這個 TrieTreeSort 類裏面實現了將有序trie樹的所有單詞排序後封裝爲List的遍歷獲取方法。下面是源碼:

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;

/**
 * @Author: LiYang
 * @Date: 2020/2/27 22:23
 * @Description: 有序trie樹(有序字典樹)做單詞的字典排序
 */
public class TrieTreeSort {

    /**
     * 有序trie樹的結點類(TrieTreeSort的靜態內部類)
     */
    private static class TrieNode implements Comparable<TrieNode> {

        //當前結點的字母
        private char letter;

        //當前結點字母是否可以作爲結束字母
        private boolean isEndLetter = false;

        //當前結點的下一結點集合
        //注意,這裏用TreeSet,使得下一結點的letter在樹枝上自然有序
        private TreeSet<TrieNode> next;

        /**
         * 空構造方法
         */
        public TrieNode() {

        }

        /**
         * 入參letter的構造方法
         * @param letter
         */
        public TrieNode(char letter) {
            this.letter = letter;

            //爲每一個結點創建一個空的next集合
            this.next = new TreeSet<>();
        }

        /**
         * 重寫equals方法,結點字母相等,結點即相等
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            //如果不是同一類,則絕對不等
            if (!(obj instanceof TrieNode)) {

                //返回不等
                return false;
            }

            //字母相等,則結點相等,否則不等
            return this.letter == ((TrieNode)obj).letter;
        }

        /**
         * 重寫了equals方法,就得跟着重寫hashCode方法
         * @return
         */
        @Override
        public int hashCode() {
            //以letter的數值代表hashcode
            return this.letter;
        }

        /**
         * 重寫compareTo方法,實現字符串排序
         * @param o
         * @return
         */
        @Override
        public int compareTo(TrieNode o) {
            //按照char的原始順序排序
            return this.letter - o.letter;
        }

        /**
         * 查找某字母的下級字母結點
         * @param letter 查找的下一個字母
         * @return 下一個字母的結點
         */
        public TrieNode findNext(char letter) {
            //遍歷查找
            for (TrieNode node : next) {

                //如果找到了
                if (node.letter == letter) {

                    //返回該結點
                    return node;
                }
            }

            //沒找到,返回null
            return null;
        }

    }


    /************** 接下來是TrieTreeSort類的屬性和方法 **************/

    //有序trie樹的根結點,默認爲'/'
    private TrieNode root = new TrieNode('/');


    /**
     * 空構造方法
     */
    public TrieTreeSort() {

    }

    /**
     * 帶初始單詞數組的構造方法
     * @param words
     */
    public TrieTreeSort(String[] words) {
        //遍歷單詞數組
        for (int i = 0; i < words.length; i++) {

            //加入初始單詞
            addWord(words[i]);
        }
    }

    /**
     * 加入需要排序的單詞
     * @param word 待加入的單詞
     */
    public void addWord(String word) {
        //將單詞轉化爲字符數組
        char[] charArray = word.toCharArray();

        //當前有序trie樹結點爲根結點
        TrieNode currentNode = root;

        //遍歷單詞每一個字母
        for (int i = 0; i < charArray.length; i++) {

            //在當前有序trie樹結點的下一個字母結點集合裏,尋找當前字母結點
            TrieNode nextNode = currentNode.findNext(charArray[i]);

            //如果當前字母存在
            if (nextNode != null) {

                //找到的當前字母結點作爲當前有序trie樹結點
                //接下來在當前字母結點的下一個字母集合裏找下一個字母
                currentNode = nextNode;

            //如果當前字母不存在
            } else {

                //爲當前字母創造有序trie樹結點
                TrieNode trieNode = new TrieNode(charArray[i]);

                //將當前字母創造的有序trie樹結點,加入到當前結點的下一個結點集合裏
                currentNode.next.add(trieNode);

                //以當前字母創造的有序trie樹結點作爲當前結點,繼續找下一個字母
                //有就繼續看下一個字母,沒有就創建,直到創建完整單詞樹枝
                currentNode = trieNode;
            }
        }

        //將最後一個字母,設置爲結束字母
        currentNode.isEndLetter = true;
    }

    /**
     * 將有序trie樹裏的單詞做排序的方法
     * @return 有序的trie樹單詞列表
     */
    public List<String> wordSort() {
        //接收最終排序的單詞的列表
        List<String> sortedWords = new ArrayList<>();

        //將有序trie樹的根結點的下級所有結點按序遍歷
        //這裏遍歷的,就是所有單詞的首字母
        for (TrieNode trieNode : this.root.next) {

            //調用同名的重載方法,類似二叉樹先序遍歷來遍歷有序trie樹
            wordSort(trieNode, new ArrayList<>(), sortedWords);
        }

        //最後,返回排好序的單詞列表
        return sortedWords;
    }

    /**
     * 將有序trie樹裏面的單詞樹,以類似先序遍歷的方式進行遍歷
     * 並一層層遞歸下去,直到葉結點,然後即完成了單詞排序
     * @param current 當前即將遍歷的字母結點
     * @param sequence 當前形成的單詞前綴序列
     * @param sortedWords 收集已經排好序了的單詞
     */
    public void wordSort(TrieNode current, List<Character> sequence, List<String> sortedWords) {
        //先將當前字母加入到前綴序列中
        sequence.add(current.letter);

        //如果當前字母是結束字母
        if (current.isEndLetter) {

            //完成當前字母結束的單詞的排序,將當前字母序列
            //轉化爲單詞字符串,並加入到收集列表中
            sortedWords.add(charSequenceToString(sequence));
        }

        //如果當前的字母結點還有下級結點(同樣是TreeSet,有序)
        if (current.next.size() > 0) {

            //遍歷下級有序TreeSet
            for (TrieNode trieNode : current.next) {

                //遞歸調用本方法,接着往下進行類先序遍歷式排序
                wordSort(trieNode, copyList(sequence), sortedWords);
            }
        }
    }

    /**
     * List<Character>的深度拷貝方法
     * @param source 拷貝源
     * @return 拷貝好的副本List<Character>
     */
    public List<Character> copyList(List<Character> source) {
        //新建拷貝副本列表
        List<Character> copy = new ArrayList<>();

        //將源字符全部加入
        copy.addAll(source);

        //返回拷貝副本
        return copy;
    }

    /**
     * 將List<Character>轉化爲String
     * @param sequence 待轉化的List<Character>
     * @return 轉化好的String,也就是單詞
     */
    public String charSequenceToString(List<Character> sequence) {
        //用StringBuffer來拼接
        StringBuffer sbuf = new StringBuffer();

        //遍歷字符,拼接
        for (Character character : sequence) {
            sbuf.append(character);
        }

        //返回拼接好的單詞
        return sbuf.toString();
    }


    /**
     * 測試有序trie樹
     * @param args
     */
    public static void main(String[] args) {
        //新建有序trie樹的實例
        TrieTreeSort trieTreeSort = new TrieTreeSort();

        //亂序加入各種單詞
        trieTreeSort.addWord("he");
        trieTreeSort.addWord("zip");
        trieTreeSort.addWord("kill");
        trieTreeSort.addWord("i");
        trieTreeSort.addWord("content");
        trieTreeSort.addWord("hallo");
        trieTreeSort.addWord("area");
        trieTreeSort.addWord("kiss");
        trieTreeSort.addWord("context");
        trieTreeSort.addWord("run");
        trieTreeSort.addWord("zoo");
        trieTreeSort.addWord("contact");
        trieTreeSort.addWord("hello");
        trieTreeSort.addWord("fire");
        trieTreeSort.addWord("hell");
        trieTreeSort.addWord("basic");

        //調用有序trie樹的單詞排序方法,返回排序結果
        List<String> sortedWordList = trieTreeSort.wordSort();

        //遍歷排序結果
        for (String word : sortedWordList) {

            //按排好的順序依次打印單詞
            System.out.println(word);
        }
    }

}

執行TrieTreeSort類的main方法,控制檯依次輸出排好序的所有單詞,測試通過:

area
basic
contact
content
context
fire
hallo
he
hell
hello
i
kill
kiss
run
zip
zoo
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章