Trie樹:字串頻率統計排序
題目:一個文字檔案,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前10個詞,請給出思想,給出時間複雜度分析。
首先我們給出答案:
1. 建立Trie樹,記錄每顆樹的出現次數,O(n*le); le:平均查詢長度 2. 維護一個10的小頂堆,O(n*lg10); 3. 總複雜度: O(n*le) + O(n*lg10);
接著我們再分析:
根據題目的意思,我們知道就是對每一個單詞進行計數,計數完成後進行排序。
如果學過資料結構的一定會想起hash,我們可以使用hashMap進行實現,但是key是一個字串,大機率會出現衝突。
而衝突的解決就需要消耗時間。
我們先來計算hashmap的時間複雜度
Hash 表號稱是 O(1) 的,但在計算 hash 的時候就肯定會是 O(k) ,而且還有碰撞之類的問題;
所以其時間複雜度大於O(k).
直接定址法-hash演算法
我們都知道hash演算法查詢時間複雜度是O(1),但是如果遇到了衝突就會退化成線性查詢。
int * n = new int[ 50000 ] ; for ( int i = 0 ; i < 50000 ; ++i ) { n[ i ] = rand( ) % 100 ; } // 統計每個數字出現個次數 map< int , int > Counter ; for ( int i = 0 ; i < 50000 ; ++i ) { ++Counter[ n[ i ] ] ; }
但是衝突解決很費時間,因為本身就是數字我們可以使用直接定址法,就是根據字元本身的號進行定位,這樣就一定不會產生衝突。
統計每個數字出現個次數 int Counter[ 100 ] = { 0 } ; for ( int i = 0 ; i < 50000 ; ++i ) { ++Counter[ n[ i ] ] ; }
這就是直接定址法,是hash的一種特殊的形式,效率很高,不會存在衝突。
但是當key從數字變為字串,如何確定字串的唯一位置。
Trie樹
要唯一的確定字串的位置,我們首先想到的就是字典,對單詞進行字典排序後,每一個單詞的位置就是確定的了。
那麼如何最佳化對“字典”的插入和查詢,我們想到了樹。
Trie 的強大之處就在於它的時間複雜度。它的插入和查詢時間複雜度都為 O(k) 。
而且其中的K為單詞的長度。同時其不會產生任何碰撞,所以其最大的時間複雜度為O(k)
但是當字串的重複率較大,資料較多時,這個時間複雜差的還是比較大的。
簡單地說,Trie就是直接定址表和樹的結合的產物。
但是Trie樹佔據的空間還是比較大的。
class TrieNode // 字典樹節點 { private int num;// 有多少單詞透過這個節點,即由根至該節點組成的字串模式出現的次數 private TrieNode[] son;// 所有的兒子節點 private boolean isEnd;// 是不是最後一個節點 private char val;// 節點的值 //對每一個節點的初始化 TrieNode() { num = 1; son = new TrieNode[SIZE]; isEnd = false; } }
堆排序
但我們計算每一個單詞的重複數量後,就涉及到一個統計排序的問題,我們的目的是取出其中的前10個。
排序演算法大家都已經不陌生了,,我們要注意的是排序演算法的時間複雜度是NlgN。
題目要求是求出Top 10,因此我們沒有必要對所有的資料都進行排序,我們只需要維護一個10個大小的陣列,每讀一條記錄就和陣列最後一個資料對比,如果小於這個資料,那麼繼續遍歷,否則,將陣列中的資料進行調整。
演算法的最壞時間複雜度是N*K, 其中K是指top多少。
但是每次調整前K資料資料的時間複雜度是K,因為我們採用的是順序比較,可是前K陣列是有序的可以進行二分查詢,可以將查詢的時間複雜度變為logk,但是確定插入資料的位置,而資料的移動又變為一大問題。
有沒有一種既能快速查詢,又能快速移動元素的資料結構呢?
回答是肯定的,那就是堆。
藉助堆結構,我們可以在log量級的時間內查詢和調整/移動。
public class HeapSort { private static void heapSort(int[] arr) { int len = arr.length -1; for(int i = len/2 - 1; i >=0; i --){ //堆構造 heapAdjust(arr,i,len); } while (len >=0){ swap(arr,0,len--); //將堆頂元素與尾節點交換後,長度減1,尾元素最大 heapAdjust(arr,0,len); //再次對堆進行調整 } } public static void heapAdjust(int[] arr,int i,int len){ int left,right,j ; while((left = 2*i+1) <= len){ //判斷當前父節點有無左節點(即有無孩子節點,left為左節點) right = left + 1; //右節點 j = left; //j"指標指向左節點" if(j < len && arr[left] < arr[right]) //右節點大於左節點 j ++; //當前把"指標"指向右節點 if(arr[i] < arr[j]) //將父節點與孩子節點交換(如果上面if為真,則arr[j]為右節點,如果為假arr[j]則為左節點) swap(arr,i,j); else //說明比孩子節點都大,直接跳出迴圈語句 break; i = j; } } public static void swap(int[] arr,int i,int len){ int temp = arr[i]; arr[i] = arr[len]; arr[len] = temp; } public static void main(String[] args) { int array[] = {20,50,20,40,70,10,80,30,60}; System.out.println("排序之前:"); for(int element : array){ System.out.print(element+" "); } heapSort(array); System.out.println("n排序之後:"); for(int element : array){ System.out.print(element+" "); } } }
最終的時間複雜度就降到了N*logK
但是然後道題首先要同時沒顆樹出現的次數
最終題目的時間複雜度為:
總複雜度: O(nle) + O(nlg10);
作者:張曉天a
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3486/viewspace-2816929/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 字串演算法--$\mathcal{KMP,Trie}$樹字串演算法KMP
- 統計一個字串出現頻率最高的字母/數字字串
- Trie樹,字典樹
- trie字典樹
- 字典樹Trie
- 字典樹(Trie)
- go最大堆 實現頻率排序Go排序
- 一個簡單的統計問題(解決方案:Trie樹)
- 1636 按照頻率將陣列升序排序陣列排序
- python做頻率統計圖 完整版Python
- 雙陣列TRIE樹Double-Array Trie理解引導陣列
- 資料庫redolog切換頻率統計分析資料庫
- LeetCode_1636_按照頻率將陣列升序排序LeetCode陣列排序
- 線段樹也能是 Trie 樹 題解
- 報警系統QuickAlarm之頻率統計及介面封裝UI封裝
- 208. 實現 Trie (字首樹)-pythonPython
- 雙陣列字典樹(Double Array Trie)陣列
- 由簡入繁--Trie樹實戰
- 統計英文名著中單詞出現頻率
- python 計算txt文字詞頻率Python
- LeetCode 1032. Stream of Characters 4行Trie樹LeetCode
- 字串-簡單字串排序字串排序
- 詞頻統計
- 字串統計字串
- cf888G. Xor-MST(Boruvka最小生成樹 Trie樹)
- [翻譯]資料結構——trie樹介紹資料結構
- 【動畫】看動畫輕鬆理解「Trie樹」動畫
- 等精度頻率計的設計與驗證
- 牛客網字串排序程式設計題字串排序程式設計
- 詞頻統計mapreduce
- 資料結構 實驗六(二叉排序樹字元統計)資料結構排序字元
- Trie樹【P3879】 [TJOI2010]閱讀理解
- 頻率元件 LL元件
- Trie——解決字串搜尋、異或最值問題字串
- js字串排序方法JS字串排序
- MySQL 對字串排序MySql字串排序
- 祖先樹統計
- 淺談樹形結構的特性和應用(上):多叉樹,紅黑樹,堆,Trie樹,B樹,B+樹...