百度“水果开会”问题探讨

来源:互联网 发布:java重写调用 编辑:程序博客网 时间:2024/04/29 08:29


摘 要:现在,搜索技术被广泛的应用于我们的生活,而对于搜索技术来说速度是至关重要的,这里将百度“水果开会”问题作为讨论的对象,探讨一下散列在搜索、查找中的运用。“水果开会”问题的一般解法的时间复杂度为O(n2),本文讨论使用散列的方法有O(n)的时间复杂度下解决这类问题。

关键字:散列,双向链表,STL

   现在,在互联网上的信息正民惊人的速度增长,搜索技术为我们在这们大的空间中找到有用信息提供了可能,它正逐渐影响着我们的生活。在搜索技术中重要的一个因素就是搜索的速度。本文通过对“水果开会”问题三种解法的探讨和比较分析,介绍在查找中运用散列的算法,提高查找的速度。


 问题描述:
  每个百度工程师团队都有一笔还算丰裕的食品经费,足够每天购置多种水果。水果往往下午送达公司前台。前台的姐姐们只要看到同时出现五种或以上的水果,就称之为“水果开会”。
从搜索引擎切词的语法角度,只要两种水果的名字中有一个字相同就属于同样的类别。例如“小雪梨”和“大雪梨”是同一种水果,而“核桃”和“水蜜桃”也被认为是同一种水果。尤其要指出的是,如果有三种水果x, y, z同时在前台出现,且x和y是同一种水果,y和z也是同一种水果的时候,x和z在此时也被认为是同一种水果(即使x和z并不包含相同的字)。现在前台的姐姐们想知道,今天是否有“水果开会”——五种或更多的水果同时在前台出现。每种水果名称由不超过十个汉字组成。

  “水果开会”是一个“百度之星”比赛中出现的一个十分有趣的问题(百度的比赛中处处都贯穿着搜索的思想)。在的探讨中,我们简化一下问题,不讨论带下划线的问题。讨论一下使用散列的方法,在线性时间内解决“水果开会”问题。如果我们要判断的水果为{雪梨 柠檬 西瓜 苹果 花生 蜜 梨 冬瓜 },存放在一个string数组中。输入会给出的水果总数n。还要注意处理一种特殊情况,水果名中的汉字有重复,是应先处理掉一个水果名内本身的汉字重复,假设在这种处理之后的汉字总数k。

朴素的想法:
  在处理这个问题时最朴素就自然的想法是这样的:在第一种水果中取一个汉字,然后在余下的水果名中查找是否有这个字,如果有水果总数就减一,跳到下一个水果名中继续查询直到最后一个水果名时第一个汉字查找结束,接下来继续进行第二个汉字的查找,直到将最后一个水果中的汉字作为查找关键字时,整个查找才结束。这相当于一种穷举的该当,有点像排序中的“冒泡算法”。它的时间复杂度为: = =O(n2)

简化的算法:
  如果,把所有的字都放到了一个数组中,我选用STL,使用一个双向链表list,由于我们处理的是汉字每个汉字占两个字节,所以每一个字也都是一个string。将它们排序,使用list::unique()去掉重复的字。水果数就等于输入的水果数减去unique()后减少的size()。unique()之前的list.size()为k,若unique()之后list的长度为m,水平的总数就为k-m。

  看上去这出是一个不错的想法,程序的思路的确变得简单多了。但实际上算法时间效率并没有得本质上的改善,仍然为O(n2)。虽然排序的时间复杂度依赖于具体的实现,但现在最好的比较排序算法也为O(n lg n)[1],而unique()的时间复杂度总为O(n2),所以最后算法的时间复杂度为:   O(n lg n)+ O(n2)= O(n2)。

散列算法:
  思路:散列可以通过使用hash函数来获得数据存储位置的算法。如果我们有一张汉字表(数组),其中每一个表中的每一个汉字有唯一的存储位置,并且假定我们可以通过对汉字的一个hash函数来求得某个汉字在数组中的位置(下标)。具体方法,首先将一个一维数组置0,一个汉字如果通过它的hash函数求得的下标位置上的数组上的值为0就将它置1,如果数组上这个位置的值已经为1,说明这个字已经在此之前出现过,水果数减1。通过这种方式,我们只需要查询汉字它的时间复杂度就只为O(1)。现在已经接近问题的答案了。但有一些细节的问题还需要进一步的讨论。

        首是汉字,在这个问题处理的是汉字,每一个汉字有两个字节构成,所以每个字都应该用一个string保存。然后是选用什么样的hash函数来解决冲突问题(hash函数使当两个关键字散列到同一个值的时候称为冲突),如上面所描述的一样,要每个汉字在表中唯一,这就是说不能有冲突。一般构建这样的理想散列有一个简单的方法,你有多少个值我就用多大的数组,那么这个问题中是否也能用这一方法呢?在汉字的区位表[2]中汉字是从1061H-8794H,共保存了8794H-1061H=29076个汉字。

Hash如下:
size_t hash(string str1){
     sizt_t temp;
     temp=str1[1];
     temp=(tem<<8)+str[0];
     return temp-1061H;
}

算法:
itn main(){
     const int ARR_LEN=29076;
     int i,index,total_size=n;   //将total_size置为水果总数
     bool arr[ARR_LEN];
     for(int i=0;i<ARR_LEN;i++) arr[i]=0;  //数组置0
     for(i=0;i<k;i++){
         index=hash(str_hz[i]);
         if(arr[index]) total_size--;
         else arr[index]=1;
     }
     cout << “水果总数是:” << total_size << endl;
     return 0;
}

结论:
       这个算法是一个典型的以空间换时间的方法,只用语言内的基本--数组就达到了目的,实现也很简单,使用散列的算法,从汉字本身的信息中直接获得了它的位置信息,既判断它是否存在,来代替在数组一逐个的查找,从而提高了算法的效率。但这个解法也存在一个不足之处,就是空间大量的浪费,空间的利用率为k/29076,空间浪费太大。这主要是由于我们选取的hash函数还不够好,要选择一个好的hash函数,应该对汉字在区位码表中的汉字的分布情况进行分析,去除不常用汉字的那一部分。
汉字总数大概八万多,常用三千五[3],从实用的角度讲,水果品一般都是常用汉字,那么选取的这个数组就只需要小于6000的一个值。


参考文献:
[1] Mark Allen Weiss著 冯舜玺 译 《数据结构与算法分析》。北京:机械工业出版社
[2] 科蓝软件工作室 互联网网站
[3] “百度知道”主题“汉字的总数是多少”