Android程序員會遇到的算法(part 5 字典樹)

Android程序員面試會遇到的算法系列:html

Android程序員面試會遇到的算法(part 1 關於二叉樹的那點事) 附Offer狀況前端

Android程序員面試會遇到的算法(part 2 廣度優先搜索)java

Android程序員面試會遇到的算法(part 3 深度優先搜索-回溯backtracking)node

Android程序員面試會遇到的算法(part 4 消息隊列的應用)程序員

Android程序員會遇到的算法(part 5 字典樹)面試

Android程序員會遇到的算法(part 6 優先級隊列PriorityQueue)算法

Android程序員會遇到的算法(part 7 拓撲排序)數據庫

隔了三個月,終於下定決心繼續更新了,仍是想把 關於算法這部分寫完整,此次我會開始介紹一些數據結構的用法,原本想說更新一個關於並查集的問題的。可是思前想後,仍是字典樹的實際用例更多一些,例子也容易讓人理解。因此此次我要詳細的講一下Trie-> AKA 字典樹這個數據結構的用法和一些實際的例子。後端

首先Trie這個單詞是一個新的詞彙,中文的翻譯通常說是字典樹,或者叫前綴樹(由於用這個樹結構能夠經過前綴搜索數據),中文的翻譯就比較容易讓人理解。可是有趣的是這個單詞的英文發音卻頗有爭議,若是你在Google上搜索Trie Pronunciation,你會發現你們都在糾結於究竟是發Tree的音仍是Try的音。數組

trie1.png

固然,這些都不是重點,隨便哪一個我以爲你們都能理解,反正千萬別到時候由於某些同窗和你認知的發音不同就去鄙視人家。。。

image

華麗麗的分割線


1. 字典樹的原理

那麼說會重點,通俗的來說,字典樹是一種能夠方便人們經過前綴查找單詞的一種數據結構,它把具備相同前綴的單詞合併到一塊兒,節省了存儲空間(和直接用HashMap存相比),同時也能夠作前綴查找或者自動補全。

舉個例子,我就搬來維基百科的例子

假設咱們要收錄一下幾個單詞Tea, Ted, Ten,Inn。那麼咱們會以如下的方式把這些單詞逐一錄入樹種:

  1. 建立初始節點Root, 當前插入節點爲根節點
  2. 先準備錄入Tea,分解Tea的字母T, E, A,先把第一個字母T放入根節點下面。而且把當前插入節點改成T
  3. T也就是當前插入節點下面放入E,當前插入節點改成E
  4. E也就是當前插入節點下面放入A,當前插入節點改成A,可是單詞全部字母都輸入結束了,所以在A上面打個tag,標記該節點是某個單詞的結束字母。

第一個單詞錄入完畢

trie2.jpg

  1. 準備錄入Ted,當前插入節點爲根節點。分解單詞Ted爲TED.準備插入第一個字母T
  2. 先檢查根節點下面有沒有T這個字母。答案是有,那麼不須要再插入一個T了,直接將插入節點改成T。
  3. 第二個輸入字母爲E,一樣的,T下面也有一個E,也不須要再插入E了,直接將插入節點改成E.
  4. 第三個輸入字母爲D,此次當前的插入節點E下面沒有D,那麼咱們插入D,當前插入節點也改成D
  5. 由於TED輸入完畢,因此在D上面打一個tag,標記標記該節點是某個單詞的結束字母。

trie3.jpg

剩下的字母同理可得

trie4.jpg

在討論代碼的實現以前,咱們先看看字典樹的實際應用:

2.字典樹的應用。

2.1自動補全

字典樹的最經典的應用確定就是自動補全了,好比你在Google或者百度進行搜索的時候

trie5.png

谷歌將不少熱門的搜索單詞用字典樹的方式存儲在後端,當Web網頁搜索單詞的前綴的時候,後端查找到該前綴最後一個節點的位置,將下面的子節點所有排列出來返回給前端。

好比用以上的例子,當用戶搜索TE的時候,定位到最後一個節點E,將E節點下面的節點返回而且拼湊出一個完整的字符串,TED和TEA。

2.2 搜索文件

還有好比說當咱們在Android Studio按下:alt+control+o,而且搜索文件的時候:

trie6.png

當咱們搜索Main的時候,顯示以Main開頭的文件。

這個功能也能夠用字典樹實現(固然這是能夠,不必定就是用字典樹實現,事先聲明省得誤人子弟,由於作索引還能夠用B樹來作,相似數據庫,這裏不展開,感興趣的能夠看阮一峯大神的關於數據庫原理的文章)

2.3 通信錄

trie7.jpg

請自動忽略我粉紅色的鍵盤。。。。

8_150416141903_13.jpg

好比說咱們在通信錄搜索185,app會自動返回以185開頭的號碼的聯繫人,這個道理也是同樣的,只不過樹的內容再也不是單詞,而是數字罷了。

有同窗問,貌似通信錄app不僅能夠搜索號碼吧,應該照理來講搜索名字也是能夠,好比根據姓來搜索。

這個答案很簡單,再根據名字作另一個字典樹不就好了。

image


3.字典樹的代碼。

這裏我用一個Leetcode的題目做爲參考,代碼是我本身實現的。

Leetcode 字典樹

/** * * 實現字典樹,訣竅就是用什麼數據結構保存children,我的偏向用hashmap,由於用數組寫起來也麻煩並且會浪費多餘的空間。26個字符不必定全都用獲得。 * */
	class TrieNode {
		// Initialize your data structure here.
		char c;
		HashMap<Character, TrieNode> children = new HashMap<Character, TrieNode>();
		boolean hasWord;

		public TrieNode() {

		}

		public TrieNode(char c) {
			this.c = c;
		}
	}

	public class Trie {
		private TrieNode root;

		public Trie() {
			root = new TrieNode();
		}

		// Inserts a word into the trie.
		public void insert(String word) {
			TrieNode cur = root;
            //curChildren 對應咱們講的當前插入節點,全部要插入的節點都要放在當前插入節點的子節點裏面
			HashMap<Character, TrieNode> curChildren = root.children;
			char[] wordArray = word.toCharArray();
            //循環遍歷此次要插入的單詞,從第一個字母開始
			for (int i = 0; i < wordArray.length; i++) {
				char wc = wordArray[i];
                //若是當前插入節點有這個字符對應的子節點的話,直接把該子節點變成當前插入節點
				if (curChildren.containsKey(wc)) {
					cur = curChildren.get(wc);
				} else {
                //若是沒有的話,建立一個。
					TrieNode newNode = new TrieNode(wc);
					curChildren.put(wc, newNode);
					cur = newNode;
				}
				curChildren = cur.children;
                //若是該節點是插入單詞的最後一個字符,打一個tag,表面這是一個單詞的結尾、
				if (i == wordArray.length - 1) {
					cur.hasWord = true;
				}
			}
		}

		// Returns if the word is in the trie.
        //有時候雖然該單詞做爲前綴,存在於字典樹中,可是卻沒有這個單詞。好比我搜索
        //TE。這個前綴存在,可是並無這個單詞,E字母對應的節點沒有tag
		public boolean search(String word) {
			if (searchWordNodePos(word) == null) {
				return false;
			} else if (searchWordNodePos(word).hasWord)
				return true;
			else
				return false;
		}

		// Returns if there is any word in the trie
		// that starts with the given prefix.
		public boolean startsWith(String prefix) {
			if (searchWordNodePos(prefix) == null) {
				return false;
			} else
				return true;
		}

		//搜索制定單詞在字典樹中的最後一個字符對應的節點。這個方法是搜索的關鍵
		public TrieNode searchWordNodePos(String s) {
			TrieNode cur = root;
			char[] sArray = s.toCharArray();
			for (int i = 0; i < sArray.length; i++) {
				char c = sArray[i];
				if (cur.children.containsKey(c)) {
					cur = cur.children.get(c);
				} else {
					return null;
				}
			}
			return cur;
		}
	}
    




複製代碼

這部分是簡單的搜索和生成字典樹,可是並無實現如何返回字符串。其實返回字符串就是暴力搜索的一個過程,把最後一個節點一下的全部子節點組合起來。這裏可使用咱們以前講到的深度優先的方法(其實就至關於找一個樹的全部path的過程,參考Leetcode 257. Binary Tree Paths))。

感興趣的同窗能夠先本身實現一下。

這裏最後貼上個人實現

public ArrayList<String> getAutoCompletionStrings(String preflix){

	TrieNode lastNode = searchWordNodePos(preflix);
    if( lastNode == null ){
    	return new ArrayList<String>();
    }
    else{
    	ArrayList<String> list = new ArrayList<String>();
        trieHelper(list,"",lastNode);
        return list;
    }
}



private void trieHelper(ArrayList<String> result, String current, TrieNode node) {
		if (node != null && node.hasWord) {
			result.add(current);
		} else if(node != null){
        	Set<Entry<Character,TrieNode>> set = node.children.entrySet();
            for( Entry<Character,TrieNode> entry : set ){
            	trieHelper(result, current+entry.getValue().c , entry.getValue());
            }
		}
        return;
	}


複製代碼
相關文章
相關標籤/搜索