"top k"問題的深刻探討

      經常遇到這樣的一個問題:在海量數據中找出出現頻率最高的前K個數,或者從海量數據中找出最大的前K個數,這類問題一般稱爲「top K」問題,如:在搜索引擎中,統計搜索最熱門的10個查詢詞;在歌曲庫中統計下載率最高的前10首歌等等。html

    ①: 本人初次學習軟件工程,近來便遇到一個相似的問題,問題是關於統計一片文章中統計出現頻率最高的前十個單詞。其實剛拿到這個程序時,也以爲很容易:無非是文件的導入、單詞的分類、以及排序算法。因此便開始編程(由於大學沒有養成需求設計的習慣,更加註重編寫代碼),因此,編着編着便越遍越多,仔細看來不只代碼冗餘,並且結構混亂。正如師所說,本身的代碼不只別人看不懂,本身幾天後都不知道本身的某個類時期什麼做用。因而我便開始着手分析程序(由於本身熟悉java,以爲java的封裝作的好,其實論速度仍是c語言比較快)。java

    First:理解這道題作什麼的?正則表達式

               這道小程序作的是統計一篇文章出現頻率最高的十個詞,及相同的次的次數排序。算法

   Second:理解這道題須解決哪些問題?編程

              (1)文件的導入(其實很簡單,一個BufferedReader便能搞定)。小程序

               (2)單詞的讀取,由於是一整篇文章,須要分割單詞依據「,。?!」等這些進行分割。數組

               (3)相同字數的統計。多線程

                (4)排序算法。ide

   Third:功能的實現?學習

            (1)文件的導入,依據java提供的基於字符型的文件的輸入輸出與緩衝流的應用(相信學過I/O的輸入輸出都應該會)

                   關鍵代碼:

File file=new File(path);    
FileReader fileReader=new FileReader(file);     //創建文件輸入流
BufferedReader bufferedReader=new BufferedReader(fileReader);  //創建緩衝輸入流
View Code

            (2)單詞的提取,其實剛開始原本想利用數組分割,可是以爲麻煩,後來查閱資料,之前學過的java正則表達式,起到了關鍵做用。
              

      public String[] split(String regex,int limit)
根據匹配給定的 正則表達式來拆分此字符串。

此方法返回的數組包含此字符串的子字符串,每一個子字符串都由另外一個匹配給定表達式的子字符串終止,或者由此字符串末尾終止。數組中的子字符串按它們在此字符串中出現的順序排列。若是表達式不匹配輸入的任何部分,那麼所得數組只具備一個元素,即此字符串。

關鍵代碼:

String []splittStr=str.split("[\\s,.;!?]");      //利用java正則表達式實現單詞的分離
View Code

(3)相同字數的統計:由於學過Hashmap集合類,想到了Hashmap中有個方法:

containsKey(Object key)

          若是此映射包含對於指定鍵的映射關係,則返回 true。故能夠利用此方法進行相同歸類:

 關鍵代碼:

 while((str=bufferedReader.readLine())!=null)
       {
           String []splittStr=str.split("[\\s,.;!?]");      //利用java正則表達式實現單詞的分離
       for(int i=0;i<splittStr.length;i++)
           {
                 if(!splittStr[i].equals(""))
        {
           splittStr[i]=splittStr[i].toLowerCase();
              //利用hashmap的containsKey判斷是否包含鍵的映射關係
                  if(!hashMap.containsKey(splittStr[i]))                          
                  {
                               hashMap.put(splittStr[i], 1);
                                  }
                              else 
                               {
             hashMap.put(splittStr[i], Integer.parseInt(""+hashMap.get(splittStr[i]))+1);
                               }
         }
              }
       }
View Code

       (4)排序算法:將Map轉化位List,利用sort(List<T> list, Comparator<? super T> c) 
             根據指定比較器產生的順序對指定列表進行排序。

         關鍵代碼:

 List<Map.Entry<String,Integer>> list_Data = new ArrayList<Map.Entry<String, Integer>>(hashMap.entrySet());  
    Collections.sort(list_Data, new Comparator<Map.Entry<String, Integer>>()  
      {   
     public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2)  
            {  
            if(o2.getValue()!=null&&o1.getValue()!=null&&o2.getValue().compareTo(o1.getValue())>0){  
                    return 1;  
                 }else{  
                   return -1;  
                 }  
              }  
         });
View Code

   Forth:結果測試:

  ②:公司解決如此問題:

     針對top k類問題,一般比較好的方案是【分治 + trie樹/hash + 小頂堆】,即先將數據集按照hash方法分解成多個小數據集,而後使用trie樹或者 hash統計每一個小數據集中的query詞頻,以後用小頂堆求出每一個數據集中出頻率最高的前K個數,最後在全部top K中求出最終的top K

      實際上,最優的解決方案應該是最符合實際設計需求的方案,在實際應用中,可能有足夠大的內存,那麼直接將數據扔到內存中一次性處理便可,也可能機器有多個核,這樣能夠採用多線程處理整個數據集。

哈哈,到此本人第一次軟件工程初步認識,但願你們多提意見多多進步。

附源代碼:

package com.su.test;

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.File;

import java.io.FileReader;

import java.io.FileWriter;

import java.util.ArrayList;

import java.util.Collections;

import java.util.Comparator;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.Scanner;

public class Test {

    //沒有處理異常,將異常拋出

        public static void main(String[] args) throws Exception{

              // TODO Auto-generated method stub

             System.out.print("請輸入所在文件的位置:");

             Scanner scanner=new Scanner(System.in);   //文本輸入

       String path=scanner.next();

            File file=new File(path);    

            FileReader fileReader=new FileReader(file);     //創建文件輸入流

       BufferedReader bufferedReader=new BufferedReader(fileReader);  //創建緩衝輸入流

       String str=null;

       HashMap<String,Integer> hashMap=new HashMap<String, Integer>();   //hashmap集合,利用鍵值:key-單詞、value-出現次數

       while((str=bufferedReader.readLine())!=null)

       {

           String []splittStr=str.split("[\\s,.;!?]");      //利用java正則表達式實現單詞的分離

           for(int i=0;i<splittStr.length;i++)

           {

                                    if(!splittStr[i].equals(""))

                   {

                                         splittStr[i]=splittStr[i].toLowerCase();

                                         //利用hashmap的containsKey判斷是否包含鍵的映射關係

                                         if(!hashMap.containsKey(splittStr[i]))                         

                     {

                                      hashMap.put(splittStr[i], 1);

                                      }

                                      else

                                     {

                    hashMap.put(splittStr[i], Integer.parseInt(""+hashMap.get(splittStr[i]))+1);

                  }

                   }

          }

       }

       /*

        * *對Hashmap進行排序(按value值進行排序)

        */

       List<Map.Entry<String,Integer>> list_Data = new ArrayList<Map.Entry<String, Integer>>(hashMap.entrySet());  

         Collections.sort(list_Data, new Comparator<Map.Entry<String, Integer>>()  

             {   

                public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2)  

                 {  

                 if(o2.getValue()!=null&&o1.getValue()!=null&&o2.getValue().compareTo(o1.getValue())>0){  

                  return 1;  

                 }else{  

                   return -1;  

                 }  

              }  

           });

        int len=list_Data.size();   //獲取list_Data的大小

        for(int i=0;i<10;i++)

        {

            System.out.print(list_Data.get(i)+"  ");

        }

       bufferedReader.close();   

    }

}
View Code
相關文章
相關標籤/搜索