字典樹(Trie樹)的Java實現(xiàn)

先上張圖汛聚,從百度百科盜過來的。


字典樹

又稱單詞查找樹短荐,是一種[樹形結(jié)構(gòu)]倚舀,是一種哈希樹的變種。典型應(yīng)用是用于統(tǒng)計忍宋,排序和保存大量的字符串(但不僅限于字符串)痕貌,所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計。它的優(yōu)點是:利用字符串的公共前綴來減少查詢時間讶踪,最大限度地減少無謂的字符串比較芯侥,查詢效率比哈希樹高。
根節(jié)點不包含字符乳讥,除根節(jié)點外每一個節(jié)點都只包含一個字符柱查; 從根節(jié)點到某一節(jié)點,路徑上經(jīng)過的字符連接起來云石,為該節(jié)點對應(yīng)的字符串唉工; 每個節(jié)點的所有子節(jié)點包含的字符都不相同。
圖中紅點表示有單詞結(jié)尾汹忠,比如兩個單詞app跟apple淋硝,其實apple是涵蓋了app的,所以必須得有一個End來表示單詞是否結(jié)尾宽菜,一個end來表示一個單詞谣膳。
這里我們用Java來模擬一個Trie樹

class TrieNode {
        TrieNode preNode = null;
        boolean isEnd = false;
        int deep = 0;//做hash使用,防止一個單詞里面有多個char的時候hash是一樣的铅乡,可能導(dǎo)致刪除出錯
        char content = 0;
        LinkedList<TrieNode> child = new LinkedList<>();
}

其實就幾個必要的東西:
1. isEnd:是否是紅點继谚,也就是是否是word的結(jié)尾
2. content:當(dāng)前節(jié)點到parent節(jié)點存儲的字母
3. LinkedList<TrieNode> child:子節(jié)點,當(dāng)前節(jié)點后續(xù)節(jié)點

其實字典樹最常見的兩個操作是 查詢添加 操作阵幸,其實就是很簡單的邏輯了花履,代碼貼在下面。

稍微復(fù)雜些的是刪除的操作挚赊,比如我有這么一個樹诡壁,樹中有這么兩個單詞appleapp

字典樹

如果我需要刪除 app這個單詞,我只需要把 紅點 p這個節(jié)點由紅色變?yōu)榘咨秃昧塑睿涂梢粤恕?br> 但如果我需要刪掉apple妹卿,那我要做的不止要把e變?yōu)榘咨€需要找到父節(jié)點l,把l也刪掉纽帖。
當(dāng)然如果我要刪掉apk這個樹中不存在的單詞宠漩,顯然也是失敗的。

所以移除word懊直,三種情況:

  1. word在list中不存在扒吁,直接返回失敗
  2. word最后一個char 沒有child,則刪掉此節(jié)點并朝 root 查找沒有child && isEnd=false 的節(jié)點都刪掉
  3. word最后一個char 有child室囊,則把isEnd置為false

而為了能找到父節(jié)點雕崩,我在Node中加了個parentNode屬性,可能還有更好的解決辦法融撞。

還有個稍微復(fù)雜些的是遍歷操作盼铁,樹的遍歷需要用到遞歸,說到遞歸尝偎,就得想到回溯法饶火,可以看一下我寫的回溯法的一個文章。


import util.LogUtil;

import java.util.LinkedList;

/**
 * Created by yocn on 2019/6/13.
 * 字典樹實現(xiàn)
 */
public class TrieTree {
    private TrieNode root = new TrieNode();

    public void test() {
        addWord("abc");
        addWord("abcd");
        addWord("abe");
//        addWord("akl");
//        addWord("apple");
//        addWord("world");
//        addWord("word");
//        traverseTree();
//        removeWord("abcd");
        removeWord("abc");
        traverseTree();
    }

    static class TrieNode {
        TrieNode preNode = null;
        boolean isEnd = false;
        int deep = 0;//做hash使用致扯,防止一個單詞里面有多個char的時候hash是一樣的肤寝,可能導(dǎo)致刪除出錯
        char content = 0;
        LinkedList<TrieNode> child = new LinkedList<>();

        TrieNode() {
        }

        TrieNode(char content) {
            this.content = content;
        }

        @Override
        public String toString() {
            return "\n" + "{" +
                    "End=" + isEnd +
                    ", d=" + deep +
                    ", c=" + content +
                    ", c=" + child +
                    '}';
        }

        public String toSimpleString() {
            return "\n" + "{" +
                    "End=" + isEnd +
                    ", d=" + deep +
                    ", c=" + content +
                    '}';
        }

        @Override
        public int hashCode() {
            return content + deep;
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof TrieNode && (((TrieNode) obj).content == content);
        }

        void setPreNode(TrieNode node) {
            preNode = node;
        }

        TrieNode getPreNode() {
            return preNode;
        }

        /**
         * child中刪掉某個Node
         *
         * @param node 需要刪掉的node
         */
        void removeChild(TrieNode node) {
            for (TrieNode aChild : child) {
                if (aChild.content == node.content) {
                    child.remove(aChild);
                    break;
                }
            }
        }

        /**
         * child中是否有此Node
         *
         * @param character 保存的char
         * @return 存在返回不存在返回Null
         */
        TrieNode getNode(Character character) {
            for (TrieNode aChild : child) {
                if (aChild.content == character) {
                    return aChild;
                }
            }
            return null;
        }
    }

    /**
     * 添加一個word
     * apple
     *
     * @param word 需要添加的詞
     */
    public void addWord(String word) {
        int deep = 0;
        TrieNode currNode = root;
        while (deep < word.length()) {
            /*
             * 判斷當(dāng)前node的child,如果為空直接添加抖僵,不為空鲤看,查找是否含有,不含有則添加并設(shè)為currNode耍群,含有則找到并設(shè)置為currNode
             */
            char c = word.charAt(deep);
            if (currNode.child.contains(new TrieNode(c))) {
                currNode = currNode.getNode(c);
            } else {
                TrieNode node = new TrieNode(c);
                node.setPreNode(currNode);
                node.deep = deep + 1;
                currNode.child.add(node);
                currNode = node;
            }
            if (deep == word.length() - 1) {
                currNode.isEnd = true;
            }
            deep++;
        }
    }

    /**
     * word在map中是否存在
     *
     * @param word 需要查找的word
     * @return 是否存在
     */
    public boolean hasWord(String word) {
        int deep = 0;
        TrieNode currNode = root;
        while (deep < word.length()) {
            char c = word.charAt(deep);
            if (currNode.child.contains(new TrieNode(c))) {
                currNode = currNode.getNode(c);
            } else {
                return false;
            }
            if (deep == word.length() - 1) {
                return currNode.isEnd;
            }
            deep++;
        }
        return false;
    }

    /**
     * 移除word义桂,幾種情況:
     * 1、word在list中不存在蹈垢,直接返回失敗
     * 2慷吊、word最后一個char 沒有child,則刪掉此節(jié)點并朝 root 查找沒有child && isEnd=false 的節(jié)點都刪掉
     * 3曹抬、word最后一個char 有child罢浇,則把isEnd置為false
     *
     * @param word 需要移除的word
     * @return 是否移除成功
     */
    public boolean removeWord(String word) {
        if (word == null || word.trim().equals("")) {
            return false;
        }
        if (!hasWord(word)) {
            return false;
        }
        int deep = 0;
        TrieNode currNode = root;
        while (deep < word.length()) {
            char c = word.charAt(deep);
            if (currNode.child.contains(new TrieNode(c))) {
                currNode = currNode.getNode(c);
            } else {
                return false;
            }
            if (deep == word.length() - 1) {
                // 把isEnd置為false
                currNode.isEnd = false;
                if (currNode.child.size() > 0) {
                    //3、word最后一個char 有child,結(jié)束
                    return true;
                } else {
                    //2、word最后一個char 沒有child掩浙,則刪掉此節(jié)點并朝 root 查找沒有child && isEnd=false 的節(jié)點都刪掉
                    TrieNode parent = currNode.getPreNode();
                    while (parent != null) {
                        if (parent.child.size() == 0 && !parent.isEnd) {
                            parent.removeChild(currNode);
                            currNode = parent;
                            parent = currNode.preNode;
                        } else {
                            parent.removeChild(currNode);
                            return true;
                        }
                    }
                }
            }
            deep++;
        }

        return false;
    }

    /**
     * 前序遍歷所有節(jié)點星立,需要用到回溯法
     */
    public void traverseTree() {
        visitNode(root, "");
    }

    private void visitNode(TrieNode node, String result) {
        LogUtil.Companion.d(node.toSimpleString());
        String re = result + node.content;
        for (TrieNode n : node.child) {
            visitNode(n, re);
//            LogUtil.Companion.d("result->" + re);
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市灾锯,隨后出現(xiàn)的幾起案子兢榨,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吵聪,死亡現(xiàn)場離奇詭異凌那,居然都是意外死亡,警方通過查閱死者的電腦和手機吟逝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門帽蝶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人块攒,你說我怎么就攤上這事励稳。” “怎么了囱井?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵驹尼,是天一觀的道長。 經(jīng)常有香客問我庞呕,道長新翎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任住练,我火速辦了婚禮地啰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘澎羞。我一直安慰自己髓绽,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布妆绞。 她就那樣靜靜地躺著顺呕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪括饶。 梳的紋絲不亂的頭發(fā)上株茶,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音图焰,去河邊找鬼启盛。 笑死,一個胖子當(dāng)著我的面吹牛技羔,可吹牛的內(nèi)容都是我干的僵闯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼藤滥,長吁一口氣:“原來是場噩夢啊……” “哼鳖粟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拙绊,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤向图,失蹤者是張志新(化名)和其女友劉穎泳秀,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榄攀,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡嗜傅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了檩赢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吕嘀。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖漠畜,靈堂內(nèi)的尸體忽然破棺而出币他,到底是詐尸還是另有隱情,我是刑警寧澤憔狞,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布蝴悉,位于F島的核電站,受9級特大地震影響瘾敢,放射性物質(zhì)發(fā)生泄漏拍冠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一簇抵、第九天 我趴在偏房一處隱蔽的房頂上張望庆杜。 院中可真熱鬧,春花似錦碟摆、人聲如沸晃财。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽断盛。三九已至,卻和暖如春愉舔,著一層夾襖步出監(jiān)牢的瞬間钢猛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工轩缤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留命迈,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓火的,卻偏偏與公主長得像壶愤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子馏鹤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于數(shù)據(jù)結(jié)構(gòu)中樹結(jié)構(gòu)的相關(guān)分享 本文參考: 樹結(jié)構(gòu)參考文獻 一剪芥、傳統(tǒng)的數(shù)據(jù)結(jié)構(gòu)中的樹結(jié)構(gòu) 樹結(jié)構(gòu)是一種非線性存儲結(jié)...
    ai_chen2050閱讀 3,732評論 0 3
  • 題目描述 貝茜正在領(lǐng)導(dǎo)奶牛們逃跑.為了聯(lián)絡(luò),奶牛們互相發(fā)送秘密信息. 信息是二進制的琴许,共有M(1≤M≤50000)...
    不給贊就別想跑哼閱讀 343評論 0 0
  • 一些概念 數(shù)據(jù)結(jié)構(gòu)就是研究數(shù)據(jù)的邏輯結(jié)構(gòu)和物理結(jié)構(gòu)以及它們之間相互關(guān)系税肪,并對這種結(jié)構(gòu)定義相應(yīng)的運算,而且確保經(jīng)過這...
    Winterfell_Z閱讀 5,821評論 0 13
  • --- layout: post title: "如果有人問你關(guān)系型數(shù)據(jù)庫的原理榜田,叫他看這篇文章(轉(zhuǎn))" date...
    藍墜星閱讀 793評論 0 3
  • (本文轉(zhuǎn)自百度搜索第一個CSDN博客) 一益兄、知識簡介 Trie 的強大之處就在于它的時間復(fù)雜度。它的插入和查詢時間...
    Alan66閱讀 829評論 0 0