上篇讲述了topK问题的N个数中最大的前K个数,本篇则讲述统计一篇很长的英文文章中频次出现最高的10个单词。
例题2:统计一篇很长的英文文章中频次出现最高的10个单词。
思路:
(1) 定义一个关联容器map<string,int>,用于统计英文文章中每个单词出现的次数;定义一个vector<map<string,int>::iterator>,用于存储小根堆K;
(2) 将英文文章中的前K个单词先填满top堆;
(3) 调整top堆为小根堆结构;
(4) 通过遍历将后面的单次出现的次数与堆顶单词出现的次数(此时堆顶单词出现的次数最小)比较,大于堆顶元素就入堆,并下调堆结构。
(5) 遍历结束,则小根堆中的单词即英文文章中频次出现最高的10个单词。
代码如下:
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include<map>
#include<cassert>
using namespace std;
const int K = 10;
void adjustDown(vector<map<string,int>::iterator> &top,int i )
{
int min = i;
int child = 2 * min + 1;
while (i < K / 2)
{
if ((child + 1 < K) && (top[child]->second > top[child + 1]->second))
child++;
if ((child<K) && (top[min]->second>top[child]->second))
{
swap(top[min], top[child]);
min = child;
child = 2 * min + 1;
}
else
break;
}
}
void topK(map<string, int> &essay, vector<map<string, int>::iterator> &top)
{
auto it = essay.begin();
// 初始化完全二叉树
for (int i = 0; i < K; i++)
{
top[i] = it;
it++;
}
// 建立小根堆
for (int i = K / 2 - 1; i >= 0; i--)
{
adjustDown(top, i);
}
// 取topK
while (it != essay.end())
{
if (it->second > top[0]->second)
{
top[0] = it; // 大于堆顶元素,则入堆;
adjustDown(top, 0); // 重新调整为小根堆;
}
it++;
}
for (int i = 0; i < K; i++)
{
cout << top[i]->first << " ";
}
cout << endl;
}
int main()
{
ifstream in("data.txt"); // 打开文件
if (!in)
{
cerr << "无法打开输入文件" << endl;
return -1;
}
string word;
map<string, int> essay;
while (in >> word) // istream &in 遇到空白字符(空格符、制表符和换行符)即停止读入。
{
essay[word]++; // 添加到vector中 成员函数push_back()是用来添加未知数量的vector。
}
vector < map<string, int> ::iterator> top(K, essay.begin());
topK(essay, top);
return 0;
}