本章阐述寻找最小的k个数的反面,即寻找最大的k个数,尽管寻找最大的k个树和寻找最小的k个数,本质上是一样的。但这个寻找最大的k个数的问题的实用范围更广,因为它牵扯到了一个Top K算法问题,以及有关搜索引擎,海量数据处理等广泛的问题,所以本文特意对这个Top K算法问题,进行阐述以及实现。

一:寻找最大的k个数

把之前第三章的问题,改几个字,即成为寻找最大的k个数的问题了,如下所述:

题目描述

输入n个整数,输出其中最大的k个。

例如输入1,2,3,4,5,6,7和8这8个数字,则最大的4个数字为8,7,6和5。

分析

由于寻找最大的k个数的问题与之前的寻找最小的k个数的问题,本质是一样的,所以,这里就简单阐述下一个思路:

维护k个元素的最小堆,即用容量为k的最小堆存储最先遍历到的k个数,建堆费时O(k),并调整堆(费时O(logk))。继续遍历数列,每次遍历一个元素x,与堆顶元素比较,若x>kmin,则更新堆(用时logk),否则不更新堆。这样下来,总费时O(k*logk+(n- k)*logk)=O(n*logk)

本文之后的例子主要采用这种思路,剩下的思路不在赘述。

二:搜索引擎热门查询统计

题目描述:

搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。

假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门),请统计最热门的10个查询串,要求使用的内存不能超过1G。

分析:

        第一步、先对这批海量数据预处理,在O(N)的时间内用Hash表完成统计;

        第二步、借助堆这个数据结构,找出Top K,时间复杂度为N*logK。或者:采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。

为了降低实现上的难度,假设这些记录全部是一些英文单词, ok,复杂问题简单化了之后,编写代码实现也相对轻松多了,下面为部分代码:

// 结点指针

typedef  struct  node_no_space *ptr_no_space;                //for  hashtable

typedef  struct  node_has_space *ptr_has_space;             //for  heap

ptr_no_space  head[HASHLEN];  //hash表

struct  node_no_space

{

char  *word;

int  count;

ptr_no_space  next;

};

struct  node_has_space

{

char  word[WORDLEN];

int  count;

ptr_has_space  next;

};

// 最简单hash函数

int  hash_function(char const  *p)

{

int  value = 0;

while (*p !='/0')

{

value  =  value* 31  +  *p++;

if (value  >  HASHLEN)

value  =  value %  HASHLEN;

}

return  value;

}

// 添加单词到hash表

void  append_word(char  const  *str)

{

int index = hash_function(str);

ptr_no_space  p = head[index];

while (p != NULL)

{

if (strcmp(str,  p->word) == 0)

{

(p->count)++;

return;

}

p = p->next;

}

// 新建一个结点

ptr_no_space  q =  new node_no_space;

q->count = 1;

q->word = new char [strlen(str)+1];

strcpy(q->word, str);

q->next = head[index];

head[index] = q;

}

// 将哈希表结果写入文件

void  write_to_file()

// 从上往下筛选,维持最小堆性质

void  shift_down(node_has_space  heap[],  int i,  int  len)

// 建立小根堆

void  build_min_heap(node_has_space  heap[], int len)

// 去除字符串前后符号

void  handle_symbol(char  *str,  int n)

int  main(int  argc,  char **argv)

{

if(argc  !=  2)

{

printf("argu  error\n");

return  -1;

}

//初始化哈希表

char  str[WORDLEN];

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

head[i] = NULL;

// 读取文件,建立哈希表

FILE  *fp_passage = fopen(argv[1],  "r");

assert(fp_passage);

while (fscanf(fp_passage,  "%s",  str) != EOF)

{

int  n = strlen(str) - 1;

if (n > 0)

handle_symbol(str,  n);

append_word(str);

}

fclose(fp_passage);

// 将统计结果输入文件

write_to_file();

int  n= 10;

ptr_has_space  heap = new  node_has_space [n+1];

int  c;

FILE  *fp_word = fopen("result.txt",  "r");

assert(fp_word);

for (int  j = 1;  j <= n;  j++)

{

fscanf(fp_word, "%s%d",  &str,  &c);

heap[j].count = c;

strcpy(heap[j].word, str);

}

// 建立最小堆

build_min_heap(heap, n);

个单词

while (fscanf(fp_word, "%s %d",&str, &c) != EOF)

{

if (c > heap[1].count)

{

heap[1].count = c;

strcpy(heap[1].word, str);

sift_down(heap, 1, n);

}

}

fclose(fp_word);

// 输出出现频率最大的单词

for (int k = 1; k <= n; k++)

cout << heap[k].count <<" " << heap[k].word << endl;

return 0;

}

三:统计出现次数最多的数据

题目描述:

给你上千万或上亿数据(有重复),统计其中出现次数最多的前N个数据。

分析

上千万或上亿的数据,现在的机器的内存应该能存下(也许可以,也许不可以)。所以考虑采用hash_map/搜索二叉树/红黑树等来进行统计次数。然后就是取出前N个出现次数最多的数据了。当然,也可以堆实现。

此题与上题类似,最好的方法是用hash_map统计出现的次数,然后再借用堆找出出现次数最多的N个数据。不过,上一题统计搜索引擎最热门的查询已经采用过hash表统计单词出现的次数,特此,        本题改用红黑树取代之前的用hash表,来完成最初的统计,然后用堆更新,找出出现次数最多的前N个数据。下面为部分代码:

typedef  enum  rb_color{ RED,  BLACK } RB_COLOR;

typedef  struct  rb_node

{

int  key;

int  data;

RB_COLOR  color;

struct  rb_node * left;

struct  rb_node * right;

struct  rb_node * parent;

}RB_NODE;

RB_NODE  * RB_CreatNode(int  key,  int data)

/*左旋*/

RB_NODE  * RB_RotateLeft(RB_NODE  * node,  RB_NODE * root)

/* 右旋 */

RB_NODE  * RB_RotateRight(RB_NODE  * node,  RB_NODE * root)

/*红黑树查找结点*/

RB_NODE  *RB_SearchAuxiliary(int key, RB_NODE* root, RB_NODE** save)

/* 返回上述rb_search_auxiliary查找结果 */

RB_NODE  *RB_Search(int key, RB_NODE* root)

/* 红黑树的插入*/

RB_NODE  *RB_Insert(int key, int data, RB_NODE* root)

typedef  struct  rb_heap

{

int  key;  //key表示数值本身

int  data;  //data表示该数值出现次数

}RB_HEAP;

const  int  heapSize = 10;

RB_HEAP  heap[heapSize+1];

/*MAX_HEAPIFY函数对堆进行更新,使以i为根的子树成最小堆 */

void  MIN_HEAPIFY(RB_HEAP* A, const int& size,int i)

/*BUILD_MINHEAP函数对数组A中的数据建立最小堆*/

void  BUILD_MINHEAP(RB_HEAP * A,  const  int & size)

//中序遍历RBTree

void  InOrderTraverse(RB_NODE  * node)

{

if (node  ==  NULL)

{

return;

}

else

{

InOrderTraverse(node->left);

if(node->data > heap[1].data) //当前节点data大于最小堆的最小元素,更新堆数据

{

heap[1].data = node->data;

heap[1].key= node->key;

MIN_HEAPIFY(heap, heapSize, 1);

}

InOrderTraverse(node->right);

}

}

void  RB_Destroy(RB_NODE  * node)

int  main()

{

RB_NODE  * root = NULL;

RB_NODE  * node = NULL;

// 初始化最小堆

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

{

heap[i].key = i;

heap[i].data = -i;

}

BUILD_MINHEAP(heap,  heapSize);

FILE* fp = fopen("data.txt","r");

int num;

while (!feof(fp))

{

int res = -1;

res = fscanf(fp,"%d", &num);

if(res > 0)

{

root = RB_Insert(num, 1, root);

}

else

{

break;

}

}

fclose(fp);

InOrderTraverse(root);   //递归遍历红黑树

RB_Destroy(root);

for (i = 1; i <= 10; ++i)

{

printf("%d/t%d/n",heap[i].key, heap[i].data);

}

return 0;

}

由于在遍历红黑树采用的是递归方式比较耗内存,可以采用一个非递归的遍历的程序。

下面是用hash和堆解决此题,很明显比采用上面的红黑树,整个实现简洁了不少,部分源码如下:

#define  HASHTABLESIZE    2807303

#define  HEAPSIZE                         10

#define  A  0.6180339887       // (A )

#define  M  16384                 //m=2^14

typedef  struct  hash_node

{

int  data;

int  count;

struct  hash_node* next;

}HASH_NODE;

HASH_NODE  * hash_table[HASHTABLESIZE];

HASH_NODE  * creat_node(int  & data)

{

HASH_NODE * node = (HASH_NODE*)malloc(sizeof(HASH_NODE));

if (NULL == node)

{

printf("malloc  node  failed!/n");

exit(EXIT_FAILURE);

}

node->data = data;

node->count = 1;

node->next = NULL;

return  node;

}

/**

* hash函数采用乘法散列法

* h(k)=int(m*(A*k mod 1))

*/

int  hash_function(int  & key)

{

double  result = A * key;

return  (int)(M * (result - (int)result));

}

void  insert(int  & data)

{

int  index =  hash_function(data);

HASH_NODE * pnode  =  hash_table[index];

while (NULL  !=  pnode)

{  // 以存在data,则count++

if (pnode->data == data)

{

pnode->count += 1;

return;

}

pnode = pnode->next;

}

// 建立一个新的节点,在表头插入

pnode  =  creat_node(data);

pnode->next  =  hash_table[index];

hash_table[index] = pnode;

}

typedef  struct  min_heap

{

int  count;

int  data;

}MIN_HEAP;

MIN_HEAP heap[HEAPSIZE + 1];

/**

*traverse_hashtale函数遍历整个hashtable,更新最小堆

*/

void  traverse_hashtale()

{

HASH_NODE  * p = NULL;

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

{

p = hash_table[i];

while (NULL != p)

{  // 如果当前节点的数量大于最小堆的最小值,则更新堆

if (p->count >heap[1].count)

{

heap[1].count = p->count;

heap[1].data = p->data;

min_heapify(heap, HEAPSIZE, 1);

}

p = p->next;

}

}

}

intmain()

{

// 初始化最小堆

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

{

heap[i].count = -i;

heap[i].data = i;

}

build_min_heap(heap,  HEAPSIZE);

FILE* fp = fopen("data.txt","r");

int  num;

while (!feof(fp))

{

intres = -1;

res =fscanf(fp, "%d", &num);

if(res> 0)

{

insert(num);

}

else

{

break;

}

}

fclose(fp);

traverse_hashtale();

for (i = 1; i <= 10; ++i)

{

printf("%d\t%d\n",heap[i].data, heap[i].count);

}

return 0;

}

 

四:海量数据处理问题一般总结

关于海量数据处理的问题,一般有Bloom filter,Hashing,bit-map,堆,trie树等方法来处理。更详细的介绍,请查看此文:十道海量数据处理面试题与十个方法大总结

首先TopK问题,肯定需要有并发的,否则串行搞肯定慢,IO和计算重叠度不高。其次在IO上需要一些技巧,当然可能只是验证算法,在实践中IO的提升会非常明显。最后上文的代码可读性虽好,但机器的感觉可能就会差,这样会影响性能。(比如读文件的函数使用fscanf)

        同时,TopK可以看成从地球上选拔k个跑的最快的,参加奥林匹克比赛,各个国家自行选拔,各个大洲选拔,层层选拔,最后找出最快的10个。发挥多机多核的优势。

http://blog.csdn.net/v_JULY_v/article/details/6403777

04Top K算法问题的更多相关文章

  1. 程序员编程艺术:第三章续、Top K算法问题的实现

    程序员编程艺术:第三章续.Top K算法问题的实现 作者:July,zhouzhenren,yansha.     致谢:微软100题实现组,狂想曲创作组.     时间:2011年05月08日    ...

  2. 强连通分量【k 算法、t 算法】

    连通分量就是一个各个顶点能互相达到的图 无向图的连通分量选取任意一个顶点使用DFS遍历即可,遍历完所有顶点所需的DFS的次数就是连通分量的数量 有向图的强连通分量由于是有向的[从A点开始DFS能访问到 ...

  3. P 算法与 K 算法

    P 算法与 K 算法 作者:Grey 原文地址: 博客园:P 算法与 K 算法 CSDN:P 算法与 K 算法 说明 P 算法和 K 算法主要用来解决最小生成树问题,即:不破坏连通性删掉某些边,使得整 ...

  4. top k 算法

    对于一个非有序的数组A[p..r],求数组中第k小的元素. 如何考虑 排序(部分排序)就不用说了..o(nlgn),当然如果在实际情况中要一直取值,当然要排序后,一次搞定,以后都是O(1) 我们这里提 ...

  5. 使用堆实现Top K 算法 JS 实现

    1. 堆算法Top,时间复杂度 O(LogN) function top(arr,comp){ if(arr.length == 0){return ;} var i = arr.length / 2 ...

  6. Top K算法

    应用场景: 搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节.        假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果 ...

  7. Top K 算法详解

    http://xingyunbaijunwei.blog.163.com/blog/static/7653806720111149318357/ 问题描述         百度面试题:        ...

  8. hihoCoder 1133 二分·二分查找之k小数(TOP K算法)

    #1133 : 二分·二分查找之k小数 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在上一回里我们知道Nettle在玩<艦これ>,Nettle的镇守府有很 ...

  9. 百度面试题——top K算法

    需求 从一亿个数据中,找出其中最小的10个数. 分析 最笨的方法就是将这一亿个数据,按从小到大进行排序,然后取前10个.这样的话,即使使用时间复杂度为nlogn的快排或堆排,由于元素会频繁的移动,效率 ...

随机推荐

  1. Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---命令模式之RemoteControlTest[转]

      1   2{<HeadFirst设计模式>之命令模式 }   3{ 本单元中的类为命令的接收者      }   4{ 编译工具 :Delphi7.0         }   5{ 联 ...

  2. windows device recovery tool 刷机

    ch 春节期间,拿出来诺基亚1020拍照,误删软件,无法登陆微软账号,考虑刷机处理 下载windows device recovery tool,进行刷机,但是固件下载一直失败 考虑下载好固件包,ff ...

  3. win10 系统同步时间出错

    设置->时间和语言->区域和语言->其他日期,区域和时间设置->设置时间和日期->Internet时间->更改设置 应该会有两个服务器,分别更新下时间,哪个正确就用 ...

  4. c++中merge的操作

    merge:将两个有序序列合并成一个新的序列,并对新的序列排序 所在库:<algorithm> 注意:排序规则必须和原序列规则相同.存储时下标从0开始. 函数参数:merge(first1 ...

  5. Ajax--serialize应用表单数据序列化

    一.jQuery+Ajax表单数据序列化 <!DOCTYPE html> <html> <head> <meta charset="UTF-8&qu ...

  6. 【JZOJ4709】【NOIP2016提高A组模拟8.17】Matrix

    题目描述 输入 输出 样例输入 4 3 5 4 1 7 3 4 7 4 8 样例输出 59716 数据范围 解法 40%暴力即可: 60%依然暴力: 100%依次计算第一行和第一列对答案的贡献即可: ...

  7. 第二周<岭回归>

    传统最小二乘法缺乏稳定性 额.就是曾加正则项 \( argmin||Xw-y||^2+\alpha||w||^2 \) 对应矩阵的求解方法为 \(w=(X^TX+\alpha*I)^{-1}X^Ty\ ...

  8. C/C++中运算符优先级汇总

    编程语言C运算符优先级 优先级1: ( ).[ ].->. . 含义:圆括号.下标运算符.指向结构体成员运算符.结构体成员运算符 优先级2:!.~.++.――.-.(类型).*.&.si ...

  9. keystone同步数据库的时候提示error

    keystone 在同步的时候报出以下错误: su -s /bin/sh -c "keystone-manage db_sync" keystone CRITICAL keysto ...

  10. TP3.2的URL重写省略index.php问题

    1. 在tp3框架的配置文件里,明确指定了路由的格式,这个配置位于thinkPHP文件夹下的conf文件夹里的convention.php中,修改以下字段 'URL_MODEL' => 2, # ...