给定一个file, 查找出里面出现频率最高的10个单词

来源:互联网 发布:csgo处理器优化 编辑:程序博客网 时间:2024/06/05 22:54

之前已经总结了给定一组数字, 如何在线性时间内找到第k小的数字。

这两个问题看似有十分subtle的关系。 很显然这里是找最大的前K个单词。 单词相当于卫星数据, 直接对单词的键值, 即频率排序啦。 

现在我们对这个求top K frequent words做一个小小的总结。

方法一: minheap + external sort(即小顶堆 + 外部排序)

之所以使用外部排序, 是因为考虑到文件很大, 无法一次装入内存中。

首先第一步就是统计单词的频率。 例如 “the”, "word"等等单词。 例如 [1233, "the"], 表示单词“the”出现了1233次。统计完成之后, 我们开始对单词的频率进行排序。 可以使用一个大小为K的minHeap。 一旦有一个单词的频率大于minHeap的root(对应着小顶堆中, 关键字(即频率最小(的单词))), 我们就用这个新加进来的单词替换掉root, 然后再调整heap以得到满足heap性质的新的heap。 

方法二: min heap + hashmap(C++11中, 对应着unordered_map)

使用哈希。 首先把所有的单词一个一个的映射到一个hash table(哈希表)中。 如果一个单词已经出现在表中, 就对count加1操作。 最终, 映射完成之后, 我们得到一个具有文件中所有的单词的个数的统计信息。 我们只需要遍历哈希表, 返回具有最大的Count的k个单词即可。


方法三: Trie+ MinHeap(字典树 + 最小堆)

我还不知道Trie是干嘛的。 先恶补一下, 其实很简单。

Trie又被称为字典树, 或者称为前缀树。

为了理解Trie, 首先看看我们可以用Trie干嘛的呀。 

(1)Trie的典型应用是进行词频统计的。 通常被搜索引擎系统用来进行文本词频统计的。 优点是利用字符串的公共前缀来减少查询时间, 从而减少无谓的字符串比较。 查询效率很高。


不难看出如下的性质:

(1) 根节点不包含任何字符, 或者我们认为是空字符。

(2) 从根节点沿着某一路径到达某个节点, 路径上经过的字符链接起来, 就是该节点对应的字符串。

(3)每个单词的公共前缀作为一个字符节点保存。

(4)词频统计的时候, 叶子节点的键值为这个单词, 而对应的卫星数据是该word的频率统计。

使用Trie进行词频统计的好处就是非常的节省内存,这也是Trie较之于hash或者一个heap进行词频统计的优点所在。 因为公共前缀都是保存在Trie的一个节点中的。


(2)字符串串排序。  给定N个互不相同的仅由一个单词构成的英文名。 让你将他们按照字典序从小到达输出。

我们可以利用字典树进行排序, 采用数组的方式创建字典树。 这个树的每个节点的所有儿子要按照其字母从小到达的顺序进行排序。 然后我们对这个树进行前序遍历即可。


(3)最长公共前缀。 对所有的字符串建立字典树。

对所有串建立字典树,对于任意两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为当时公共祖先问题。 我们只需要求出所有字符串在字典树的公共祖先即可。


下面编写Trie:

一个Trie节点:

首先, 一个Trie节点应该包含:

(1)指向record的指针, record 对应着这个节点的(单词, 频率)对。

(3)需要一个信息去标记到这个Trie的节点了, 是否可以结束分支了。 因为给定的是字符串, 我们必须知道一个单词的结束的位置, 例如空格等。

(2) Trie 是多叉树。

参考如下程序:

#include <iostream>#include <vector>using namespace std;class Node { // 节点类public:    // 建构子, 该节点默认的建构子内容为空字符, 不是单词的结束    Node() { mContent = ' '; mMarker = false; }    // 析构函数    ~Node() {}    // 返回这个节点的内容    char content() { return mContent; }    // 设置这个节点的内容    void setContent(char c) { mContent = c; }   // 这个节点是否是这个单词的结束标志     bool wordMarker() { return mMarker; }        // 设置当前的节点为单词结束标志    void setWordMarker() { mMarker = true; }    // 给定字符c, 找到这个字符对应当前节点的的孩子节点    Node* findChild(char c);    // 将一个节点作为当前节点的孩子节点, append上    void appendChild(Node* child) { mChildren.push_back(child); }    // 放回当前节点的所有孩子节点    vector<Node*> children() { return mChildren; }private:    char mContent; // 节点的字符    bool mMarker; // 该节点是否为单词的结束位置    vector<Node*> mChildren; // 该节点的孩子, 是vector of nodes(为这个节点的孩子)};// 字典树的类class Trie {public:    Trie();    ~Trie();    // 添加一个单词到孩子节点    void addWord(string s);    //给定字符s, 查找当前的子点数中是否有这个单词    bool searchWord(string s);    // 给定单词, 删除当前字典树中的这个单词    void deleteWord(string s);private:    // 字典树的根节点    Node* root;};Node* Node::findChild(char c){    // 检查当前节点的孩子节点是否有字符c, 若有, 则返回这个节点    for ( int i = 0; i < mChildren.size(); i++ )    {        Node* tmp = mChildren.at(i);        if ( tmp->content() == c )        {            return tmp;        }    }    // 在当前节点的孩子孩子中没找到, 返回NULL    return NULL;}Trie::Trie(){    root = new Node();}Trie::~Trie(){    // Free memory}void Trie::addWord(string s){    Node* current = root;      //插入的字符为空字符, 直接把当前的(根节点)设置为    if ( s.length() == 0 )    {        current->setWordMarker(); // an empty word        return;    }    for ( int i = 0; i < s.length(); i++ )    {        Node* child = current->findChild(s[i]);        if ( child != NULL )        {            // 找到了这个节点的位置            current = child;        }        else        {            // 没有找到到, 则创建            Node* tmp = new Node();            tmp->setContent(s[i]);            // 将这个节点设置为孩子节点            current->appendChild(tmp);            current = tmp;        }        // 最后一个字符设置这里的单词结束标志        if ( i == s.length() - 1 )            current->setWordMarker();    }}bool Trie::searchWord(string s){    Node* current = root;    while ( current != NULL )    {        for ( int i = 0; i < s.length(); i++ )        {            Node* tmp = current->findChild(s[i]);            if ( tmp == NULL )                return false;            current = tmp;        }        if ( current->wordMarker() )            return true;        else            return false;    }    return false;}// Test programint main(){    Trie* trie = new Trie();    trie->addWord("Hello");    trie->addWord("Balloon");    trie->addWord("Ball");    if ( trie->searchWord("Hell") )        cout << "Found Hell" << endl;    if ( trie->searchWord("Hello") )        cout << "Found Hello" << endl;    if ( trie->searchWord("Helloo") )        cout << "Found Helloo" << endl;    if ( trie->searchWord("Ball") )        cout << "Found Ball" << endl;    if ( trie->searchWord("Balloon") )        cout << "Found Balloon" << endl;    delete trie;}
运行如下:




0 0