Trie樹:字串頻率統計排序

lvxfcjf發表於2021-09-09

題目:一個文字檔案,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章