《演算法筆記》5. 字首樹、桶排序、排序演算法總結
阿新 • • 發佈:2020-07-17
[TOC]
# 1 字首樹結構(trie)、桶排序、排序總結
## 1.1 字首樹結構
> 單個字串中,字元從前到後的加到一顆多叉樹上
> 字元放在路上,節點上有專屬的資料項(常見的是pass和end值)
> 所有樣本都這樣新增。如果沒有路就新建,如果有路就複用
> 沿途節點的pass值增加1.每個字串結束時來到的節點end值增加1
> 一個字串陣列中,所有字串的字元數為N,整個陣列加入字首樹種的代價是O(N)
功能一:構建好字首樹之後,我們查詢某個字串在不在字首樹中,某字串在這顆字首樹中出現了幾次都是特別方便的。例如找"ab"在字首樹中存在幾次,可以先看有無走向a字元的路徑(如果沒有,直接不存在),再看走向b字元的路徑,此時檢查該節點的end標記的值,如果為0,則字首樹中不存在"ab"字串,如果e>0則,e等於幾則"ab"在字首樹種出現了幾次
功能二:如果單單是功能一,那麼雜湊表也可以實現。現查詢所有加入到字首樹的字串,有多少個以"a"字元作為字首,來到"a"的路徑,檢視p值大小,就是以"a"作為字首的字串數量
```Java
package class05;
import java.util.HashMap;
public class Code02_TrieTree {
public static class Node1 {
// pass表示字元從該節點的路徑通過
public int pass;
// end表示該字元到此節點結束
public int end;
public Node1[] nexts;
public Node1() {
pass = 0;
end = 0;
// 每個節點下預設26條路,分別是a~z
// 0 a
// 1 b
// 2 c
// .. ..
// 25 z
// nexts[i] == null i方向的路不存在
// nexts[i] != null i方向的路存在
nexts = new Node1[26];
}
}
public static class Trie1 {
// 預設只留出頭節點
private Node1 root;
public Trie1() {
root = new Node1();
}
// 往該字首樹中新增字串
public void insert(String word) {
if (word == null) {
return;
}
char[] str = word.toCharArray();
// 初始引用指向頭節點
Node1 node = root;
// 頭結點的pass首先++
node.pass++;
// 路徑的下標
int path = 0;
for (int i = 0; i < str.length; i++) { // 從左往右遍歷字元
// 當前字元減去'a'的ascii碼得到需要新增的下個節點下標
path = str[i] - 'a'; // 由字元,對應成走向哪條路
// 當前方向上沒有建立節點,即一開始不存在這條路,新開闢
if (node.nexts[path] == null) {
node.nexts[path] = new Node1();
}
// 引用指向當前來到的節點
node = node.nexts[path];
// 當前節點的pass++
node.pass++;
}
// 當新加的字串所有字元處理結束,最後引用指向的當前節點就是該字串的結尾節點,end++
node.end++;
}
// 刪除該字首樹的某個字串
public void delete(String word) {
// 首先要查一下該字串是否加入過
if (search(word) != 0) {
// 沿途pass--
char[] chs = word.toCharArray();
Node1 node = root;
node.pass--;
int path = 0;
for (int i = 0; i < chs.length; i++) {
path = chs[i] - 'a';
// 在尋找的過程中,pass為0,提前可以得知在本次刪除之後,該節點以下的路徑不再需要,可以直接刪除。
// 那麼該節點之下下個方向的節點引用置為空(JVM垃圾回收,相當於該節點下的路徑被刪了)
if (--node.nexts[path].pass == 0) {
node.nexts[path] = null;
return;
}
node = node.nexts[path];
}
// 最後end--
node.end--;
}
}
// 在該字首樹中查詢
// word這個單詞之前加入過幾次
public int search(String word) {
if (word == null) {
return 0;
}
char[] chs = word.toCharArray();
Node1 node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
// 尋找該字串的路徑中如果提前找不到path,就是未加入過,0次
if (node.nexts[index] == null) {
return 0;
}
node = node.nexts[index];
}
// 如果順利把word字串在字首樹中走完路徑,那麼此時的node對應的end值就是當前word在該字首樹中添加了幾次
return node.end;
}
// 所有加入的字串中,有幾個是以pre這個字串作為字首的
public int prefixNumber(String pre) {
if (pre == null) {
return 0;
}
char[] chs = pre.toCharArray();
Node1 node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
// 走不到最後,就沒有
if (node.nexts[index] == null) {
return 0;
}
node = node.nexts[index];
}
// 順利走到最後,返回的pass就是有多少個字串以當前pre為字首的
return node.pass;
}
}
/**
* 實現方式二,針對各種字串,路徑不僅僅是a~z對應的26個,