0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

數(shù)據(jù)結(jié)構(gòu)字典樹的實現(xiàn)

算法與數(shù)據(jù)結(jié)構(gòu) ? 來源:bigsai ? 作者:bigsai ? 2021-09-07 15:03 ? 次閱讀

什么是字典樹字典樹,是一種空間換時間的數(shù)據(jù)結(jié)構(gòu),又稱Trie樹、前綴樹,是一種樹形結(jié)構(gòu)(字典樹是一種數(shù)據(jù)結(jié)構(gòu)),典型用于統(tǒng)計、排序、和保存大量字符串。所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計。它的優(yōu)點是:利用字符串的公共前綴來減少查詢時間,最大限度地減少無謂的字符串比較,查詢效率比哈希樹高。

可能大部分情況你很難直觀或者有接觸的體驗,可能對前綴這個玩意沒啥概念,可能做題遇到前綴問題也是暴力匹配蒙混過關(guān),如果字符串比較少使用哈希表等結(jié)構(gòu)可能也能蒙混過關(guān),但如果字符串比較長、相同前綴較多那么使用字典樹可以大大減少內(nèi)存的使用和效率。一個字典樹的應(yīng)用場景:在搜索框輸入部分單詞下面會有一些神關(guān)聯(lián)的搜索內(nèi)容,你有時候都很神奇是怎么做到的,這其實就是字典樹的一個思想。

對于字典樹,有三個重要性質(zhì):

1:根節(jié)點不包含字符,除了根節(jié)點每個節(jié)點都只包含一個字符。root節(jié)點不含字符這樣做的目的是為了能夠包括所有字符串。

2:從根節(jié)點到某一個節(jié)點,路過字符串起來就是該節(jié)點對應(yīng)的字符串。

3:每個節(jié)點的子節(jié)點字符不同,也就是找到對應(yīng)單詞、字符是唯一的。

一個字典樹

設(shè)計實現(xiàn)字典樹上面已經(jīng)介紹了什么是字典樹,那么我們開始設(shè)計一個字典樹吧!

對于字典樹,可能不同的場景或者需求設(shè)計上有一些細(xì)致的區(qū)別,但整體來說一般的字典樹有插入、查詢(指定字符串)、查詢(前綴)。

我們首先來分析一下簡單情況吧,就是字符串中全部是26個小寫字母,剛好力扣208實現(xiàn)Trie樹可以作為一個實現(xiàn)的模板。

實現(xiàn) Trie 類:

Trie() 初始化前綴樹對象。

void insert(String word) 向前綴樹中插入字符串 word 。

boolean search(String word) 如果字符串 word 在前綴樹中,返回 true(即,在檢索之前已經(jīng)插入);否則,返回 false 。

boolean startsWith(String prefix) 如果之前已經(jīng)插入的字符串 word 的前綴之一為 prefix ,返回 true ;否則,返回 false 。怎么設(shè)計這個字典樹呢?

對于一個字典樹Trie類,肯定是要有一個根節(jié)點root的,而這個節(jié)點類型TrieNode也有很多設(shè)計方式,在這里我們?yōu)榱撕唵畏乓粋€26個大小的TrieNode類型數(shù)組,分別對應(yīng)‘a(chǎn)’-‘z’的字符,同時用一個boolean類型變量isEnd表示是否為字符串末尾結(jié)束(如果為true說明)。

class TrieNode {

TrieNode son[];

boolean isEnd;//結(jié)束標(biāo)志

public TrieNode()//初始化

{

son=new TrieNode[26];

}

}

用數(shù)組的話如果字符比較多的話可能會消耗一些內(nèi)存空間,但是這里26個連續(xù)字符還好的,如果向一個字典樹中添加big,bit,bz 那么它其實是這樣的:

那么再分析一下具體操作:

插入操作:遍歷字符串,同時從字典樹root節(jié)點開始遍歷,找到每個字符對應(yīng)的位置首先判斷是否為空,如果為空需要創(chuàng)建一個新的Trie。比如插入big的枚舉第一個b時候創(chuàng)建TrieNode,后面也是同理。不過重要的是要在停止的那個TrieNode將isEnd設(shè)為true表明這個節(jié)點是構(gòu)成字符串的末尾節(jié)點。

這部分對應(yīng)的關(guān)鍵代碼為:

TrieNode root;

/** 初始化 */

public Trie() {

root=new TrieNode();

}

/** Inserts a word into the trie. */

public void insert(String word) {

TrieNode node=root;//臨時節(jié)點用來枚舉

for(int i=0;i《word.length();i++)//枚舉字符串

{

int index=word.charAt(i)-‘a(chǎn)’;//找到26個對應(yīng)位置

if(node.son[index]==null)//如果為空需要創(chuàng)建

{

node.son[index]=new TrieNode();

}

node=node.son[index];

}

node.isEnd=true;//最后一個節(jié)點

}

查詢操作:查詢是建立在字典樹已經(jīng)建好的情況下,這個過程和查詢有些類似但不需要創(chuàng)建TrieNode,如果枚舉的過程一旦發(fā)現(xiàn)該TrieNode未被初始化(即為空)則返回false,如果順利到最后看看該節(jié)點的isEnd是否為true(是否已插入已改字符結(jié)尾的字符串),如果為true則返回true。

這里用一個例子可能更好懂。插入big串,如果查找ba會因為第二次a對應(yīng)TrieNode為null為為空。如果查找bi也會返回失敗,因為之前插入的big只在g字符對應(yīng)TrieNode標(biāo)識isEnd=true,但i字符下面的isEnd為false,即不存在bi字符串。

該部分對應(yīng)的核心代碼為:

public boolean search(String word) {

TrieNode node=root;

for(int i=0;i《word.length();i++)

{

int index=word.charAt(i)-‘a(chǎn)’;

if(node.son[index]==null)//為null直接返回false

{

return false;

}

node=node.son[index];

}

return node.isEnd==true;

}

前綴查找:和查詢很相似但是有點區(qū)別,查找失敗的話返回false,但是如果能進(jìn)行到最后一步那么返回true。上面例子插入big查找bi同樣返回true,因為存在以它為前綴的字符串。

該對應(yīng)對應(yīng)的核心代碼為:

public boolean startsWith(String prefix) {

TrieNode node=root;

for(int i=0;i《prefix.length();i++)

{

int index=prefix.charAt(i)-‘a(chǎn)’;

if(node.son[index]==null)

{

return false;

}

node=node.son[index];

}

//能執(zhí)行到最后即返回true

return true;

}

上面代碼合在一起就是完整的字典樹了,最基礎(chǔ)的版本。完整版為:

22af1a8a-0f8c-11ec-8fb8-12bb97331649.png

代碼

字典樹小思考字典樹基礎(chǔ)班很容易,但很可能會出現(xiàn)一些延伸。

對于上面是26個字符的,我們很容易用ASCII找到對應(yīng)索引,如果字符可能性比較多,用數(shù)組可能浪費(fèi)的空間比較大,那我們也可以用HashMap或者List來存儲元素啊,用List的話就需要順序枚舉,用HashMap就可以直接查詢,這里就講解一個使用HashMap()實現(xiàn)的字典樹。

使用HashMap替代數(shù)組(不過使用哈希就不自帶排序功能了),其實邏輯是一樣的,只需要判斷時候用HashMap判斷是否存在對應(yīng)的key即可,HashMap的類型為:

Map《Character,TrieNode》 sonMap;

使用HashMap實現(xiàn)的字典樹完整代碼為:

import java.util.HashMap;

import java.util.Map;

public class Trie{

class TrieNode{

Map《Character,TrieNode》 sonMap;

boolean idEnd;

public TrieNode()

{

sonMap=new HashMap《》();

}

}

TrieNode root;

public Trie()

{

root=new TrieNode();

}

public void insert(String word) {

TrieNode node=root;

for(int i=0;i《word.length();i++)

{

char ch=word.charAt(i);

if(!node.sonMap.containsKey(ch))//不存在插入

{

node.sonMap.put(ch,new TrieNode());

}

node=node.sonMap.get(ch);

}

node.idEnd=true;

}

public boolean search(String word) {

TrieNode node=root;

for(int i=0;i《word.length();i++)

{

char ch=word.charAt(i);

if(!node.sonMap.containsKey(ch))

{

return false;

}

node=node.sonMap.get(ch);

}

return node.idEnd==true;//必須標(biāo)記為true證明有該字符串

}

public boolean startsWith(String prefix) {

TrieNode node=root;

for(int i=0;i《prefix.length();i++)

{

char ch=prefix.charAt(i);

if(!node.sonMap.containsKey(ch))

{

return false;

}

node=node.sonMap.get(ch);

}

return true;//執(zhí)行到最后一步即可

}

}

前面講了,字典樹用于大量字符的統(tǒng)計、排序、儲存,其實排序就是和采用數(shù)組的方式可以進(jìn)行排序,因為字符的ASCII有序,在讀取時候可以按照這個規(guī)則讀取,這個思想就和基數(shù)排序有點像了。

而統(tǒng)計的話可能會面臨數(shù)量上統(tǒng)計,可能是出現(xiàn)過次數(shù)或者前綴單詞數(shù)量統(tǒng)計,如果每次都枚舉可能有點浪費(fèi)時間,但你可以TrieNode中添加一個變量,每次插入的時候可以統(tǒng)計次數(shù)。如果字符串有重復(fù)那可以直接添加,如果字符串要去重那可以確定插入成功再給路徑上前綴單詞總數(shù)分別自增。這個的話就要具體問題具體分析了。

此外,字典樹還有一個在ACM中用于解決求異或最值的問題,我們稱之為:01字典樹,大家感興趣也可以自行了解(后面可能會介紹)。

總結(jié)通過本文,想必你對字典樹有了一個較好的認(rèn)識,本篇的話目的還是在于讓讀者能夠認(rèn)識和學(xué)會基礎(chǔ)的字典樹,對其它變形優(yōu)化能有個初步的認(rèn)識。

字典樹可以最大限度地減少無謂的字符串比較,用于詞頻統(tǒng)計和大量字符串排序。自帶排序功能,使用中序遍歷序列即可得到排序序列。但是如果字符很多相同前綴很少的話那字典樹就沒啥效率優(yōu)勢的(因為要一個一個訪問節(jié)點)。

字典樹的真實應(yīng)用有很多,例如字符串檢索、文本預(yù)測、自動完成,see also,拼寫檢查、詞頻統(tǒng)計、排序、字符串最長公共前綴、字符串搜索的前綴匹配、作為其他數(shù)據(jù)結(jié)構(gòu)和算法的輔助結(jié)構(gòu)等等,這里就不再介紹啦。

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    6808

    瀏覽量

    88743
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    2966

    瀏覽量

    73814
  • 字符串
    +關(guān)注

    關(guān)注

    1

    文章

    575

    瀏覽量

    20470

原文標(biāo)題:字典樹,不就有點不一樣的一顆樹

文章出處:【微信號:TheAlgorithm,微信公眾號:算法與數(shù)據(jù)結(jié)構(gòu)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    嵌入式常用數(shù)據(jù)結(jié)構(gòu)有哪些

    在嵌入式編程中,數(shù)據(jù)結(jié)構(gòu)的選擇和使用對于程序的性能、內(nèi)存管理以及開發(fā)效率都具有重要影響。嵌入式系統(tǒng)由于資源受限(如處理器速度、內(nèi)存大小等),因此對數(shù)據(jù)結(jié)構(gòu)的選擇和使用尤為關(guān)鍵。以下是嵌入式編程中常用的幾種數(shù)據(jù)結(jié)構(gòu),結(jié)合具體特點和
    的頭像 發(fā)表于 09-02 15:25 ?362次閱讀

    探索編程世界的七大數(shù)據(jù)結(jié)構(gòu)

    結(jié)構(gòu)就像是一顆倒掛的小樹,有根、有枝、有葉。它是一種非線性的數(shù)據(jù)結(jié)構(gòu),以層級的方式存儲數(shù)據(jù),頂部是根節(jié)點,底部是葉節(jié)點。
    的頭像 發(fā)表于 04-16 12:04 ?345次閱讀

    TASKING編譯器是否可以將數(shù)據(jù)結(jié)構(gòu)設(shè)置為 \"打包\"?

    TASKING 編譯器是否可以將數(shù)據(jù)結(jié)構(gòu)設(shè)置為 \"打包\"? GCC 很早以前就提供了這種可能性,可以將__attribute__((packed))與對齊指令結(jié)合使用。 對于
    發(fā)表于 03-05 06:00

    矢量與柵格數(shù)據(jù)結(jié)構(gòu)各有什么特征

    矢量數(shù)據(jù)結(jié)構(gòu)和柵格數(shù)據(jù)結(jié)構(gòu)是地理信息系統(tǒng)(GIS)中最常用的兩種數(shù)據(jù)結(jié)構(gòu)。它們在存儲和表示地理要素上有著不同的方法和特征。在接下來的文章中,我們將詳細(xì)介紹這兩種數(shù)據(jù)結(jié)構(gòu)并比較它們的特點
    的頭像 發(fā)表于 02-25 15:06 ?2243次閱讀

    區(qū)塊鏈?zhǔn)鞘裁礃拥?b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)組織

    區(qū)塊鏈?zhǔn)且环N特殊的數(shù)據(jù)結(jié)構(gòu),它以分布式、去中心化的方式組織和存儲數(shù)據(jù)。區(qū)塊鏈的核心原理是將數(shù)據(jù)分布在網(wǎng)絡(luò)的各個節(jié)點上,通過密碼學(xué)算法保證數(shù)據(jù)的安全和可靠性。在區(qū)塊鏈上,
    的頭像 發(fā)表于 01-11 10:57 ?1857次閱讀

    C語言數(shù)據(jù)結(jié)構(gòu)之跳表詳解

    大家好,今天分享一篇C語言數(shù)據(jù)結(jié)構(gòu)相關(guān)的文章--跳表。
    的頭像 發(fā)表于 12-29 09:32 ?780次閱讀
    C語言<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>之跳表詳解

    redis數(shù)據(jù)結(jié)構(gòu)的底層實現(xiàn)

    Redis是一種內(nèi)存鍵值數(shù)據(jù)庫,常用于緩存、消息隊列、實時數(shù)據(jù)分析等場景。它的高性能得益于其精心設(shè)計的數(shù)據(jù)結(jié)構(gòu)和底層實現(xiàn)。本文將詳細(xì)介紹Redis常用的
    的頭像 發(fā)表于 12-05 10:14 ?574次閱讀

    redis hash底層實現(xiàn)原理

    數(shù)據(jù)結(jié)構(gòu)是如何實現(xiàn)的呢?本文將詳細(xì)介紹Redis哈希底層的實現(xiàn)原理。 在Redis中,每個哈希都是由一個類似于字典(Dictionary)的結(jié)構(gòu)
    的頭像 發(fā)表于 12-04 16:27 ?544次閱讀

    不同數(shù)據(jù)結(jié)構(gòu)的定義代碼

    數(shù)據(jù)結(jié)構(gòu)是相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合。
    的頭像 發(fā)表于 11-29 14:13 ?609次閱讀

    與二叉的定義

    結(jié)構(gòu) 是一類重要的 非線性數(shù)據(jù)結(jié)構(gòu) ,其中以和二叉最為常用,直觀來看,是以分支關(guān)系定義
    的頭像 發(fā)表于 11-24 15:57 ?1247次閱讀
    <b class='flag-5'>樹</b>與二叉<b class='flag-5'>樹</b>的定義

    redis的數(shù)據(jù)結(jié)構(gòu)一般分為哪幾種?

    緩存、計數(shù)器、分布式鎖等。字符串類型支持很多操作,如設(shè)置、獲取、刪除、追加等。 哈希表(Hashes): 哈希表是 Redis 提供的一個鍵值對的數(shù)據(jù)結(jié)構(gòu),它類似于一個字典,可以存儲多個字段和值的映射關(guān)系。哈希表適用于存儲對象,每個字段代表對象的一個屬性,而值則存
    的頭像 發(fā)表于 11-16 11:19 ?404次閱讀

    redis的五種數(shù)據(jù)類型底層數(shù)據(jù)結(jié)構(gòu)

    Redis是一種內(nèi)存數(shù)據(jù)存儲系統(tǒng),支持多種數(shù)據(jù)結(jié)構(gòu)。這些數(shù)據(jù)結(jié)構(gòu)不僅可以滿足常見的存儲需求,還能夠通過其底層數(shù)據(jù)結(jié)構(gòu)提供高效的操作和查詢。以下是Redis中常用的五種
    的頭像 發(fā)表于 11-16 11:18 ?672次閱讀

    無鎖CAS如何實現(xiàn)各種無鎖的數(shù)據(jù)結(jié)構(gòu)

    ,可用于在多線程編程中實現(xiàn)不被打斷的數(shù)據(jù)交換操作,從而避免多線程同時改寫某?數(shù)據(jù)時由于執(zhí)行順序不確定性以及中斷的不可預(yù)知性產(chǎn)?的數(shù)據(jù)不一致問題 有了CAS,我們就可以用它來
    的頭像 發(fā)表于 11-13 15:38 ?728次閱讀
    無鎖CAS如何<b class='flag-5'>實現(xiàn)</b>各種無鎖的<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>

    定時器的實現(xiàn)數(shù)據(jù)結(jié)構(gòu)選擇

    在后端的開發(fā)中,定時器有很廣泛的應(yīng)用。 比如: 心跳檢測 倒計時 游戲開發(fā)的技能冷卻 redis的鍵值的有效期等等,都會使用到定時器。 定時器的實現(xiàn)數(shù)據(jù)結(jié)構(gòu)選擇 紅黑 對于增刪查,時間復(fù)雜度為O
    的頭像 發(fā)表于 11-13 14:22 ?490次閱讀
    定時器的<b class='flag-5'>實現(xiàn)</b><b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>選擇

    ringbuffer數(shù)據(jù)結(jié)構(gòu)介紹

    最近在研究srsLTE的代碼,其中就發(fā)現(xiàn)一個有意思的數(shù)據(jù)結(jié)構(gòu)------ringbuffer。 雖然,這是一個很基本的數(shù)據(jù)結(jié)構(gòu),但時,它在LTE這種通信協(xié)議棧系統(tǒng)中卻大行其道,也是很容易被協(xié)議
    的頭像 發(fā)表于 11-13 10:44 ?1506次閱讀
    ringbuffer<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>介紹