1. 程式人生 > 實用技巧 >資料結構-06| 字典樹| 並查集

資料結構-06| 字典樹| 並查集

字典樹Trie

1. 字典樹的資料結構
2. 字典樹的核心思想
3. 字典樹的基本性質

1. 樹Tree

按層次列印一顆二叉樹,

在樹中深度優先搜尋:ABDH I EJ CFG

廣度優先搜尋:A BC DEFG HIJ

2. 二叉搜尋樹

二叉搜尋樹是子樹之間的關係,並不是兒子和父親的關係。

任何一個節點它的左子樹的所有節點都要小於這個根結點

它的右子樹的所有節點都要大於根結點,且對於它的任何子樹同樣地以此類推,對於任何子樹都滿足這樣的特性。

二叉搜尋樹是一個升序的序列,如果是中序遍歷左根右: 123 5678 9 10 11 12 13 15

二叉搜尋樹主要解決的問題是查詢的效率會更高,比如要查10,首先跟根結點比,10 > 9,左子樹不用找了,一分為二,只需要找右子樹即可,再比較13 > 10,左子樹,11 > 10,繼續左子樹。

3. 字典樹Trie

應用的場景: 搜尋時詞頻的感應,由字首來推後面的可能的詞語。

字典樹不再是儲存一個單詞本身,而是把字串拆成單個單個的字母,每個字母存在這個節點裡邊

3.1 基本結構

字典樹,即 Trie 樹,又稱單詞查詢樹或鍵樹,是一種樹形結構。典型應用是用於統計和排序大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。

它的優點是:最大限度地減少無謂的字串比較,查詢效率比雜湊表高。

節點本身不存任何單詞,它只存它要去到下一個路徑上面,這個路徑代表的字元,每個節點依次進行。

比如ten這個單詞,t -> te -> ten ,常用的字元放根結點上,根據輸入的不同字元,開始分叉。Trie樹不是一個二叉樹而是一個多叉樹。

根據走到不同地方,就開始分到不同的地方即所謂的分流,比如第一個字元是i(以i開始,後邊所有可能是單詞都是以i開頭),如果後邊路徑是n,還是n ..就是inn 小酒館,但這個節點它並不儲存inn這個單詞,它表這個地方是根了,且這個地方有一個單詞的終止符,要看它代表哪個單詞要從根節點捋下來,最後捋到這個節點它所走過的路徑,任何節點最後它代表的單詞就是走過的這條邊

3.2 基本性質

1. 結點本身不存完整單詞;

2. 從根結點到某一結點,路徑上經過的字元連線起來,為該結點對應的字串;

3. 每個結點的所有子結點路徑代表的字元都不相同。

3.3 節點儲存額外資訊

數字表示相應到這個結點所代表的單詞,它統計的計數就放在這個地方,數字表這個單詞出現的統計頻次。按照統計的頻次就可以給使用者做相應的推薦。

3.4 節點的內部實現

上圖只是abcdefg...,同時它還有大寫的ABCDEFG...

每個結點指向下一個節點的不同指標,這裡它的儲存不再是left和right來表示左右結點了,它直接用相應的字元來指向下一個結點,比如第一個字元是a,應該走到這個結點去,

如果是簡單單詞不分大小寫,可以認為這裡是26個分叉,從a一直分到z;如果是整個字串它的ASCII域是255,即255分叉。上圖可以看做是26分叉的一顆多叉樹。

到最後如果是葉子節點,就指向空。

最大可能情況變成26叉樹,它的空間相對來說是消耗比較大的,一層出去就是26.

它單詞的長度即深度,它查詢的次數即這個單詞有多少個字元。比如單詞to,它查t 和o,2次。

Trie 樹的核心思想是空間換時間。
  利用字串的公共字首來降低查詢時間的開銷以達到提高效率的目的。

4. 並查集 Disjoint Set

適用場景:

  • 組團、配對問題(很快地判斷這兩個個體是不是在一個集合當中如你倆是不是朋友,如果是在社交網路裡判斷兩個群體是不是一個群體以及快速地合併群組)
  • Group or not ?

如實現微信上的好友和所謂朋友圈的功能,以及分析這兩個人是不是好友,

假設有0-n-1個人,快速地判斷a和b到底是不是朋友,以及支援一些操作如把a變成b的朋友:

  用一個set,或者dic表示裡邊的人都是朋友,-----> 導致會建很多set,如兩兩是朋友,第三個不是他們的朋友,最後還要合併set...

• makeSet(s):建立一個新的並查集,其中包含 s 個單元素集合。

• unionSet(x, y):把元素 x 和元素 y 所在的集合合併,要求 x 和 y 所在的集合不相交,如果相交則不合並。

• find(x):找到元素 x 所在的集合的代表,該操作也可以用於判斷兩個元素是否位於同一個集合,只要將它們各自的代表比較一下就可以了。

一開始每個元素擁有一個parent陣列指向自己,表示它自己就是自己的集合

查詢,對任何一個元素,看它的parent,再看它的parent,一直往上,直到它的parent等於它自己的時候,說明找到了它的領頭元素 即它的集合的代表元素,就表示這個集合是誰。

合併,找到這兩個集合的領頭元素,這裡是a 和e,然後將parent[e] 指向a 或者將parent[a] 指向e, 要麼把e的parent指向a 或者把a的parent指向e都行。 最後即把這兩個合併。

d的parent是c,c的parent是b,b的parent是a,那麼我們可以直接把這條路上的所有元素它的parent都指向a,這樣的操作還是和原來的表是一樣的,但是它的查詢時間會快很多。 原來要查詢集合a的領頭元素是誰,要往上走一步走兩步走三步,壓縮後只需要一步即可。

  
class UnionFind {
  private
int count = 0; private int[] parent; public UnionFind(int n) { count = n; parent = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; //初始化,所有並查集開始初始化; 一個指向指標的關係,和陣列有異曲同工之妙 } } /* 給定任何一個p,如何找它的集合是誰; 即怎麼找它集合和它集合的領頭元素; 不斷的往上去找,知道parent[i] = i時說明找到了它所在集合的領頭元素 */ public int find(int p) { while (p != parent[p]) { parent[p] = parent[parent[p]]; p = parent[p]; } return p; }   /* 如何進行合併,調find找到它的p的集合所在的領頭元素, */ public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; parent[rootP] = rootQ; count--; }
}