假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。
1)典型的Top K算法:还是在这篇文章里头有所阐述,详情请参见:十一、从头到尾彻底解析Hash表算法。
文中,给出的最终算法是:
第一步、先对这批海量数据预处理,在O(N)的时间内用Hash表完成统计(之前写成了排序,特此订正。July、2011.04.27);
第二步、借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。
即,借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比所以,我们最终的时间复杂度是:O(N) + N'*O(logK),(N为1000万,N’为300万)。ok,更多,详情,请参考原文。
或者:采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。
2)Java实现:
1.还是制造数据,制造一亿个字符串,为了简单使用固定长度为4的字符串。(这样做的目的是为了减小hash表容量,防止溢出,因为自己制造是随机产生的字符串,不像真正的搜索引擎那样有大量的热词,所以只能这样模拟了,如果是真正的数据,就应该如上述算法所描述的那样一亿对三百万):
package com.carlosfu.chap2;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Random;
public class ProduceKeyWords {
private static int nums = 10000;
private static int wordLength = 4;
// private static int maxLength = 6;
public static void main(String[] args) throws Exception {
File file = new File("e:\\dataset\\keywords.txt");
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
// Random random = new Random();
for(int i=1;i<=nums;i++){
StringBuffer buffer = new StringBuffer();
for(int j=1;j<=nums;j++){
// String temp = produce(random.nextInt(maxLength)+1); // 1 -- 6
String temp = produce(wordLength);
buffer.append(temp);
//和chap1中差不多
if(j!=nums){
buffer.append("\r");
}
}
bw.write(buffer.toString());
if(i!=nums){
bw.newLine();
}
if(i0 == 0){
System.out.println(i);
}
}
bw.flush();
bw.close();
}
private static String produce(int n){
StringBuffer word = new StringBuffer();
Random random = new Random();
for(int i=1;i<=n;i++){
char code = (char) (random.nextInt(26) + 97);
word.append(code);
}
return word.toString();
}
}
2. 一个辅助类:
package com.carlosfu.chap2;
public class KeyValue {
private String key;
private int value;
public KeyValue() {
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
@Override
public String toString() {
return "KeyValue [key=" + key + ", value=" + value + "]";
}
}
3. TOP K算法的实现,实际上就是一个堆排序,感觉hashtable的values()和keys()真是不太好用,可能是我不太会用,所以在构造完hashtable后,我又用KeyValue[]来代替他,这样确实比较费内存,以后再改吧,暂时忘记咋改了:
package com.carlosfu.chap2;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Hashtable;
import java.util.Iterator;
//结果:
//456976
//KeyValue [key=xcpi, value=296]
//KeyValue [key=tqpf, value=292]
//KeyValue [key=oufk, value=291]
//KeyValue [key=ctqy, value=287]
//KeyValue [key=ngky, value=283]
//KeyValue [key=hdap, value=283]
//KeyValue [key=ljpi, value=282]
//KeyValue [key=lgkb, value=282]
//KeyValue [key=ahis, value=282]
//KeyValue [key=upcm, value=282]
//KeyValue [key=yzyy, value=282]
//KeyValue [key=klow, value=281]
public class TopKWords {
private static int topK = 10;
public static void main(String[] args) throws Exception {
String path = "e:\\dataset\\keywords.txt";
BufferedReader br = new BufferedReader(new FileReader(new File(path)));
Hashtable<String, Integer> table = new Hashtable<String, Integer>();
int j = 0;
for(String str = br.readLine();str!=null;){
Integer num = table.get(str);
if(num == null){
table.put(str, 1);
}else{
table.put(str, num + 1);
}
j++;
System.out.println("record: " + j);
System.out.println("table: " + table.size());
str = br.readLine();
}
System.out.println(table.keySet().size());
//上面的无需多做解释
Iterator<String> it1 = (Iterator<String>) table.keys();
Iterator<Integer> it2 = table.values().iterator();
int length = table.values().size();
//构造KeyValue[]
KeyValue[] a = new KeyValue[length+1];
for(int i=1;i<a.length;i++){
KeyValue node = new KeyValue();
if(it1.hasNext() && it2.hasNext()){
node.setKey(it1.next());
node.setValue(it2.next());
}
a[i] = node;
}
//堆排序算法
// int[] a = new int[length+1];
int n = length;
createHeap(a,n);
System.out.println(a[1]);
//输出每个堆的头
for(int i=n;i>=n-topK ;i--){
KeyValue temp = a[i];
a[i] = a[1];
a[1] = temp;
// produceMax(a,1,i-1);
createHeap(a,i-1);
System.out.println(a[1]);
}
}
private static void createHeap(KeyValue[] a,int n){
int length = n/2;
for(int i=length;i>=1;i--){
produceMax(a,i,n);
}
}
private static void produceMax(KeyValue[] a, int i, int n) {
int left = 2 * i;
int right = 2 * i + 1;
int max = i;
if(left <= n && a[left].getValue()>a[max].getValue() ){
max = left;
}
if(right <= n && a[right].getValue()>a[max].getValue()){
max = right;
}
if(max!=i){
KeyValue temp = a[i];
a[i] = a[max];
a[max] = temp;
produceMax(a,max,n); //把max以后的也要符合堆的规则
}
}
}
4. 附上一个简单的堆排序算法和一个堆排序的演示动画:
package com.carlosfu.chap2;
public class HeapSort2 {
public static void main(String[] args) {
int[] a = new int[9];
a[1] = 46;
a[2] = 55;
a[3] = 13;
a[4] = 42;
a[5] = 94;
a[6] = 5;
a[7] = 17;
a[8] = 70;
print(a);
int n = a.length-1;
createHeap(a,n);
for(int i=n;i>=2;i--){
int temp = a[i];
a[i] = a[1];
a[1] = temp;
// produceMax(a,1,i-1);
createHeap(a,i-1);
}
print(a);
}
private static void createHeap(int[] a,int n){
int length = n/2;
for(int i=length;i>=1;i--){
produceMax(a,i,n);
}
}
private static void produceMax(int[] a, int i, int n) {
int left = 2 * i;
int right = 2 * i + 1;
int max = i;
if(left <= n && a[left]>a[max] ){
max = left;
}
if(right <= n && a[right]>a[max]){
max = right;
}
if(max!=i){
int temp = a[i];
a[i] = a[max];
a[max] = temp;
produceMax(a,max,n);
}
}
private static void print(int[] a) {
int n = a.length - 1;
for (int i = 1; i <= n; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
}
原理动画:在flash中输入46,55,13,42,94,5,17,70