"top k"问题的深入探讨

      常常遇到这样的一个问题:在海量数据中找出出现频率最高的前K个数,或者从海量数据中找出最大的前K个数,这类问题通常称为“top K”问题,如:在搜索引擎中,统计搜索最热门的10个查询词;在歌曲库中统计下载率最高的前10首歌等等。

    ①: 本人初次学习软件工程,近来便遇到一个类似的问题,问题是关于统计一片文章中统计出现频率最高的前十个单词。其实刚拿到这个程序时,也觉得很容易:无非是文件的导入、单词的分类、以及排序算法。所以便开始编程(因为大学没有养成需求设计的习惯,更加注重编写代码),所以,编着编着便越遍越多,仔细看来不仅代码冗余,而且结构混乱。正如师所说,自己的代码不仅别人看不懂,自己几天后都不知道自己的某个类时期什么作用。于是我便开始着手分析程序(因为自己熟悉java,觉得java的封装做的好,其实论速度还是c语言比较快)。

    First:理解这道题做什么的?

               这道小程序做的是统计一篇文章出现频率最高的十个词,及相同的次的次数排序。

   Second:理解这道题须解决哪些问题?

              (1)文件的导入(其实很简单,一个BufferedReader便能搞定)。

               (2)单词的读取,因为是一整篇文章,需要分割单词依据“,。?!”等这些进行分割。

               (3)相同字数的统计。

                (4)排序算法。

   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

 

       

posted @ 2014-02-28 20:01  苏林东  阅读(929)  评论(4编辑  收藏  举报