6

这个问题实际上很简单,但我想在开始编码之前听听一些想法。给定一个每行包含一个单词的文件,计算最常见的 n 个数字。

不幸的是,我脑海中突然出现的第一件事是使用std::map. 我知道 C++ 的同胞会说这unordered_map非常合理。

我想知道是否可以将任何东西添加到算法方面,或者这基本上只是“选择最佳数据结构的人获胜”类型的问题。我已经在互联网上搜索了它并阅读了该哈希表和优先级队列可能会提供一个运行时间为O(n)的算法,但是我认为实现起来会很复杂

有任何想法吗?

4

5 回答 5

6

用于此任务的最佳数据结构是 Trie:

http://en.wikipedia.org/wiki/Trie

它将胜过用于计算字符串的哈希表。

于 2012-04-18T01:43:46.813 回答
2

这个问题有很多不同的方法。它最终将取决于场景和其他因素,例如文件的大小(如果文件有十亿行),那么 aHashMap将不是一种有效的方法。根据您的问题,您可以执行以下操作:

  1. 如果您知道唯一词的数量非常有限,您可以使用 a TreeMapor 在您的情况下std::map
  2. 如果单词的数量非常大,那么您可以在另一个数据结构中构建trie并保留各种单词的计数。这可能是一个 size 的堆(最小/最大取决于你想要做什么)n。所以你不需要存储所有的单词,只需要存储必要的单词。
于 2012-04-17T23:56:12.173 回答
1

如果我有很多选择,我不会std::map(or unordered_map) 开始(尽管我不知道可能会应用哪些其他限制)。

你这里有两个数据项,你用一个作为时间的关键部分,而另一个作为另一部分时间的关键。为此,您可能需要类似Boost BimapBoost MultiIndex 之类的东西。

以下是使用 Bimap 的总体思路:

#include <boost/bimap.hpp>
#include <boost/bimap/list_of.hpp>
#include <iostream>

#define elements(array) ((sizeof(array)/sizeof(array[0])))

class uint_proxy {
    unsigned value;
public:
    uint_proxy() : value(0) {}
    uint_proxy& operator++() { ++value; return *this; }
    unsigned operator++(int) { return value++; }
    operator unsigned() const { return value; }
};

int main() {    
    int b[]={2,4,3,5,2,6,6,3,6,4};

    boost::bimap<int, boost::bimaps::list_of<uint_proxy> > a;

    // walk through array, counting how often each number occurs:
    for (int i=0; i<elements(b); i++) 
        ++a.left[b[i]];

    // print out the most frequent:
    std::cout << a.right.rbegin()->second;
}

目前,我只打印出最频繁的数字,但迭代 N 次以打印出最频繁的 N 非常简单。

于 2012-04-18T00:43:08.170 回答
1

如果您只对前 N 个最常用的词感兴趣,并且不需要精确,那么您可以使用一个非常巧妙的结构。我是通过 Udi Manber 听说的,它的工作原理如下:

您创建一个包含 N 个元素的数组,每个元素跟踪一个值和一个计数,您还保留一个索引到该数组的计数器。此外,您有一个从值到索引到该数组的映射。每次使用值更新结构时(例如文本流中的单词),您首先检查您的地图以查看该值是否已经在您的数组中,如果是,则增加该值的计数。如果不是,那么您减少计数器指向的任何元素的计数,然后增加计数器。

这听起来很简单,算法的任何内容都使它看起来会产生任何有用的东西,但对于典型的真实数据,它往往做得很好。通常,如果您希望跟踪前 N 个事物,您可能希望创建容量为 10*N 的结构,因为其中会有很多空值。使用 King James Bible 作为输入,以下是该结构列出的最常用词(无特定顺序):

0 : in
1 : And
2 : shall
3 : of
4 : that
5 : to
6 : he
7 : and
8 : the
9 : I

以下是前十个最常用的词(按顺序):

0 : the ,  62600
1 : and ,  37820
2 : of ,  34513
3 : to ,  13497
4 : And ,  12703
5 : in ,  12216
6 : that ,  11699
7 : he ,  9447
8 : shall ,  9335
9 : unto ,  8912

您会看到,前 10 个单词中有 9 个是正确的,而且它只使用了 50 个元素的空间。根据您的用例,此处节省的空间可能非常有用。它也非常快。

这是我用 Go 编写的 topN 的实现:

type Event string

type TopN struct {
  events  []Event
  counts  []int
  current int
  mapped  map[Event]int
}
func makeTopN(N int) *TopN {
  return &TopN{
    counts: make([]int, N),
    events: make([]Event, N),
    current: 0,
    mapped: make(map[Event]int, N),
  }
}

func (t *TopN) RegisterEvent(e Event) {
  if index, ok := t.mapped[e]; ok {
    t.counts[index]++
  } else {
    if t.counts[t.current] == 0 {
      t.counts[t.current] = 1
      t.events[t.current] = e
      t.mapped[e] = t.current
    } else {
      t.counts[t.current]--
      if t.counts[t.current] == 0 {
        delete(t.mapped, t.events[t.current])
      }
    }
  }
  t.current = (t.current + 1) % len(t.counts)
}
于 2012-04-18T07:44:40.673 回答
0

给定一个每行包含一个单词的文件,计算最常见的n 个数字。...我已经在互联网上搜索并阅读了该哈希表和优先级队列可能会提供O(n)的算法

如果您的意思是 * n * 是相同的,那么不,这是不可能的。但是,如果您只是指时间与输入文件的大小呈线性关系,那么使用哈希表的简单实现将满足您的需求。

可能存在具有次线性内存的概率近似算法。

于 2012-04-17T23:59:15.503 回答