Algorithm 在大词序列中查找前K个频繁词的最有效方法
输入:一个正整数K和一个大文本。文本实际上可以看作是单词序列。所以我们不必担心如何把它分解成单词序列。 输出:文本中最常见的K个单词 我的想法是这样的 在遍历整个单词序列时,使用哈希表记录所有单词的频率。在这个阶段,关键是单词,值是单词频率。这需要时间 对单词、词频对进行排序;关键是词频。使用正常的排序算法,这需要*lgn时间 排序后,我们只取前K个单词。这需要很长时间 总之,总时间是On+nlgn+K,因为K肯定小于N,所以它实际上是ONGN 我们可以改进这一点。事实上,我们只想要前K个单词。其他单词的频率与我们无关。因此,我们可以使用部分堆排序。对于第2步和第3步,我们不仅仅进行排序。相反,我们将其更改为 2'构建一堆单词,单词频率对,单词频率为键。建立一个堆需要时间 3'从堆中提取前K个单词。每次提取都是Olgn。所以,总时间是可以的 总之,此解决方案在+k*lgn上花费时间 这只是我的想法。我还没有找到改进第一步的方法。Algorithm 在大词序列中查找前K个频繁词的最有效方法,algorithm,word-frequency,Algorithm,Word Frequency,输入:一个正整数K和一个大文本。文本实际上可以看作是单词序列。所以我们不必担心如何把它分解成单词序列。 输出:文本中最常见的K个单词 我的想法是这样的 在遍历整个单词序列时,使用哈希表记录所有单词的频率。在这个阶段,关键是单词,值是单词频率。这需要时间 对单词、词频对进行排序;关键是词频。使用正常的排序算法,这需要*lgn时间 排序后,我们只取前K个单词。这需要很长时间 总之,总时间是On+nlgn+K,因为K肯定小于N,所以它实际上是ONGN 我们可以改进这一点。事实上,我们只想要前K个单词。
我希望一些信息检索专家能对这个问题有更多的了解。如果你的大词表足够大,你可以简单地取样并得到估计值。否则,我喜欢散列聚合 编辑: 通过示例,我的意思是选择一些页面子集,并计算这些页面中最常用的单词。如果您以合理的方式选择页面,并选择具有统计意义的样本,那么您对最常用单词的估计应该是合理的
只有当您拥有如此多的数据,以至于处理这些数据有点愚蠢时,这种方法才是真正合理的。如果你只有几个兆欧表,你应该能够在不费吹灰之力的情况下仔细阅读数据并计算出准确的答案,而不必费心计算估计值。你可以通过使用单词的第一个字母进行划分来进一步缩短时间,然后使用下一个字符对最大的多词集进行分区,直到有k个单词集。您可以使用sortof 256路树,在叶子处列出部分/完整单词。您需要非常小心,不要在任何地方造成字符串副本 这个算法是Om,其中m是字符数。它避免了对k的依赖,这对于大k来说非常好[顺便说一下,你发布的运行时间是错误的,它应该是在*lgk上,我不确定m是什么]
如果你同时运行这两种算法,你会得到一种我很确定是渐近最优的OMIM,n*lgk算法,但我的平均速度应该更快,因为它不涉及散列或排序。你不会得到比你描述的解决方案更好的运行时。你必须至少做一些工作来评估所有的单词,然后确定额外的工作来找到前k个词
如果您的问题集非常大,您可以使用分布式解决方案,如map/reduce。让n个map Worker在每个文本的1/n上计算频率,对于每个单词,将其发送给m个reducer Worker中的一个,这些Worker是根据单词的哈希计算的。然后,减速器将计数相加。对减缩器的输出进行合并排序,将按照流行程度的顺序为您提供最流行的单词。假设我们有一个单词序列ad BAY big bad com come cold。K=2。 正如您提到的使用单词的第一个字母进行分区,我们得到 广告,广告男孩,大的,坏的,感冒了 然后使用下一个字符对最大的多词集进行分区,直到有k个单词集。 它将分区男孩,大,坏com来冷,第一分区广告,广告是错过,而广告实际上是最常见的字
也许我误解了你的意思。你能详细描述一下你的分区过程吗?你的描述中有一个错误:计数需要时间,但排序需要Om*lgm,其中m是唯一单词的数量。这通常比总字数小得多,因此可能应该优化散列的构建方式。如果我们不关心排名前K,则解决方案的一个小变化会产生一个on算法,如果我们关心排名前K,则会产生一个on+K*lgk解决方案。我相信这两个界限在一个常数因子内都是最优的 这里的优化在我们遍历列表并插入到哈希表之后再次出现。我们可以使用该算法选择列表中第k个最大的元素。该算法是可证明的 选择第k个最小元素后,我们将该元素周围的列表进行分区,就像在快速排序中一样。这显然也在进行中。轴心左侧的任何东西都在我们的K元素组中,所以我们完成了,我们可以简单地扔掉其他所有东西 我们继续前进 因此,这一战略是: 检查每个单词并将其插入哈希表:On 选择第k个最小元素:On 围绕该元素的分区:On 如果要对K个元素进行排序,只需在Ok*lgk时间内使用任何有效的比较排序对它们进行排序,就可以得到On+K*lgk的总运行时间 由于我们必须至少检查每个单词一次,所以On时间范围在常数因子内是最佳的
On+k*lgk时间范围也是最佳的,因为没有基于比较的方法在小于k*lgk的时间内对k个元素进行排序 我相信这个问题可以通过On算法解决。我们可以随时进行分拣。换句话说,这种情况下的排序是传统排序问题的一个子问题,因为每次访问哈希表时只有一个计数器递增一。最初,由于所有计数器均为零,因此对列表进行排序。当我们不断增加哈希表中的计数器时,我们会保留另一个按频率排序的哈希值数组,如下所示。每次递增计数器时,我们都会检查其在排序数组中的索引,并检查其计数是否超过列表中的前一个计数器。如果是这样,我们交换这两个元素。因此,我们得到了一个解决方案,其中n最多是原文中的字数。我也在努力解决这个问题,并从@aly获得了灵感。我们不需要事后排序,只需维护一个预先排序的单词列表,单词将被设置在X位置,X是单词的当前计数。一般来说,它的工作原理如下: 对于每个单词,将其存储为其出现的map:map的一部分。 然后,根据计数,将其从以前的计数集中删除,并将其添加到新的计数集中。 这样做的缺点是列表可能很大-可以使用树形图进行优化-但这会增加一些开销。最终,我们可以混合使用HashMap或我们自己的数据结构 代码
public class WordFrequencyCounter {
private static final int WORD_SEPARATOR_MAX = 32; // UNICODE 0000-001F: control chars
Map<String, MutableCounter> counters = new HashMap<String, MutableCounter>();
List<Set<String>> reverseCounters = new ArrayList<Set<String>>();
private static class MutableCounter {
int i = 1;
}
public List<String> countMostFrequentWords(String text, int max) {
int lastPosition = 0;
int length = text.length();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
if (c <= WORD_SEPARATOR_MAX) {
if (i != lastPosition) {
String word = text.substring(lastPosition, i);
MutableCounter counter = counters.get(word);
if (counter == null) {
counter = new MutableCounter();
counters.put(word, counter);
} else {
Set<String> strings = reverseCounters.get(counter.i);
strings.remove(word);
counter.i ++;
}
addToReverseLookup(counter.i, word);
}
lastPosition = i + 1;
}
}
List<String> ret = new ArrayList<String>();
int count = 0;
for (int i = reverseCounters.size() - 1; i >= 0; i--) {
Set<String> strings = reverseCounters.get(i);
for (String s : strings) {
ret.add(s);
System.out.print(s + ":" + i);
count++;
if (count == max) break;
}
if (count == max) break;
}
return ret;
}
private void addToReverseLookup(int count, String word) {
while (count >= reverseCounters.size()) {
reverseCounters.add(new HashSet<String>());
}
Set<String> strings = reverseCounters.get(count);
strings.add(word);
}
}
我只是想找出解决这个问题的另一个办法。但我不确定这是否正确。 解决方案: 使用哈希表记录所有单词的频率Tn=On 选择哈希表的前k个元素,并将它们还原到一个空格为k的缓冲区中。Tn=正常 每次,我们首先需要找到缓冲区的当前min元素,然后将缓冲区的min元素与哈希表的n-k元素逐一进行比较。如果哈希表的元素大于缓冲区的最小元素,则删除当前缓冲区的最小值,并添加哈希表的元素。因此,每次我们在缓冲区中找到最小值时需要Tn=Ok,并遍历整个哈希表需要Tn=On-k。所以这个过程的时间复杂度是Tn=On-k*k。 遍历整个哈希表后,结果将在此缓冲区中。 整个时间复杂度:Tn=On+Ok+Okn-k^2=Okn+n-k^2+k。因为,k通常比n小。所以对于这个解决方案,时间复杂度是Tn=Okn。这是线性时间,当k很小时。是这样吗?我真的不确定。
这可以按时完成 解决方案1: 步骤: 对单词进行计数并对其进行散列,结果会像这样出现在结构中
var hash = {
"I" : 13,
"like" : 3,
"meow" : 3,
"geek" : 3,
"burger" : 2,
"cat" : 1,
"foo" : 100,
...
...
遍历散列并找到本例中最常用的单词foo 100,然后创建该大小的数组
然后我们可以再次遍历散列,并使用出现的单词数作为数组索引,如果索引中没有任何内容,则创建一个数组,否则将其追加到数组中。然后我们得到一个数组,如:
0 1 2 3 100
[[ ],[cat],[burger],[like, meow, geek],[]...[foo]]
然后从末尾遍历数组,并收集k个单词
解决方案2:
步骤:
同上
使用min heap并将min heap的大小保持为k,对于散列中的每个单词,我们将单词的出现次数与min进行比较,1如果大于min值,则删除min(如果min heap的大小等于k),并在min heap中插入数字。2休息条件简单。
遍历数组之后,我们只需将最小堆转换为数组并返回数组。
尝试考虑特殊的数据结构来处理此类问题。在这种情况下,像trie这样的特殊树以特定的方式存储字符串,非常高效。或者第二种构建自己解决方案的方法,比如数词。我猜这TB的数据将是英文的,那么我们通常有大约600000个单词,因此可以只存储这些单词并计算哪些字符串将被重复+此解决方案将需要正则表达式来消除一些特殊字符。我敢肯定,第一个解决方案会更快
你的问题与此相同-
使用Trie和min-heap有效地解决它。这是一个有趣的搜索想法,我可以找到这篇与Top-K f相关的文章
还有一个实现。如果你想要的是文本中任何实用k和任何自然语言的k个最常见单词的列表,那么你的算法的复杂性是无关的 比如说,只是一些样品 从你的文本中提取一百万个单词,用任何算法在几秒钟内处理,最频繁的计数将非常准确 作为旁注,虚拟算法的复杂性为1。数到2。将计数排序为3。最好的是+m*logm,其中m是文本中不同单词的数量。logm比n/m小得多,因此它保持打开状态
实际上,最长的步骤是计数。获取最常用单词出现次数的最简单代码
function strOccurence(str){
var arr = str.split(" ");
var length = arr.length,temp = {},max;
while(length--){
if(temp[arr[length]] == undefined && arr[length].trim().length > 0)
{
temp[arr[length]] = 1;
}
else if(arr[length].trim().length > 0)
{
temp[arr[length]] = temp[arr[length]] + 1;
}
}
console.log(temp);
var max = [];
for(i in temp)
{
max[temp[i]] = i;
}
console.log(max[max.length])
//if you want second highest
console.log(max[max.length - 2])
}
利用内存效率高的数据结构存储单词
使用MaxHeap查找前K个常用词。
这是密码
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import com.nadeem.app.dsa.adt.Trie;
import com.nadeem.app.dsa.adt.Trie.TrieEntry;
import com.nadeem.app.dsa.adt.impl.TrieImpl;
public class TopKFrequentItems {
private int maxSize;
private Trie trie = new TrieImpl();
private PriorityQueue<TrieEntry> maxHeap;
public TopKFrequentItems(int k) {
this.maxSize = k;
this.maxHeap = new PriorityQueue<TrieEntry>(k, maxHeapComparator());
}
private Comparator<TrieEntry> maxHeapComparator() {
return new Comparator<TrieEntry>() {
@Override
public int compare(TrieEntry o1, TrieEntry o2) {
return o1.frequency - o2.frequency;
}
};
}
public void add(String word) {
this.trie.insert(word);
}
public List<TopK> getItems() {
for (TrieEntry trieEntry : this.trie.getAll()) {
if (this.maxHeap.size() < this.maxSize) {
this.maxHeap.add(trieEntry);
} else if (this.maxHeap.peek().frequency < trieEntry.frequency) {
this.maxHeap.remove();
this.maxHeap.add(trieEntry);
}
}
List<TopK> result = new ArrayList<TopK>();
for (TrieEntry entry : this.maxHeap) {
result.add(new TopK(entry));
}
return result;
}
public static class TopK {
public String item;
public int frequency;
public TopK(String item, int frequency) {
this.item = item;
this.frequency = frequency;
}
public TopK(TrieEntry entry) {
this(entry.word, entry.frequency);
}
@Override
public String toString() {
return String.format("TopK [item=%s, frequency=%s]", item, frequency);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + frequency;
result = prime * result + ((item == null) ? 0 : item.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TopK other = (TopK) obj;
if (frequency != other.frequency)
return false;
if (item == null) {
if (other.item != null)
return false;
} else if (!item.equals(other.item))
return false;
return true;
}
}
有关更多详细信息,请参考这些情况,我建议使用Java内置功能。因为,它们已经经过了良好的测试和稳定。在这个问题中,我使用HashMap数据结构查找单词的重复。然后,我将结果推送到一个对象数组中。我按数组对对象排序。排序并打印前k个单词及其重复
import java.io.*;
import java.lang.reflect.Array;
import java.util.*;
public class TopKWordsTextFile {
static class SortObject implements Comparable<SortObject>{
private String key;
private int value;
public SortObject(String key, int value) {
super();
this.key = key;
this.value = value;
}
@Override
public int compareTo(SortObject o) {
//descending order
return o.value - this.value;
}
}
public static void main(String[] args) {
HashMap<String,Integer> hm = new HashMap<>();
int k = 1;
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("words.in")));
String line;
while ((line = br.readLine()) != null) {
// process the line.
//System.out.println(line);
String[] tokens = line.split(" ");
for(int i=0; i<tokens.length; i++){
if(hm.containsKey(tokens[i])){
//If the key already exists
Integer prev = hm.get(tokens[i]);
hm.put(tokens[i],prev+1);
}else{
//If the key doesn't exist
hm.put(tokens[i],1);
}
}
}
//Close the input
br.close();
//Print all words with their repetitions. You can use 3 for printing top 3 words.
k = hm.size();
// Get a set of the entries
Set set = hm.entrySet();
// Get an iterator
Iterator i = set.iterator();
int index = 0;
// Display elements
SortObject[] objects = new SortObject[hm.size()];
while(i.hasNext()) {
Map.Entry e = (Map.Entry)i.next();
//System.out.print("Key: "+e.getKey() + ": ");
//System.out.println(" Value: "+e.getValue());
String tempS = (String) e.getKey();
int tempI = (int) e.getValue();
objects[index] = new SortObject(tempS,tempI);
index++;
}
System.out.println();
//Sort the array
Arrays.sort(objects);
//Print top k
for(int j=0; j<k; j++){
System.out.println(objects[j].key+":"+objects[j].value);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
欲了解更多信息,请访问。我希望有帮助
在遍历整个单词序列时,使用哈希表记录所有单词的频率。在这个阶段,关键是单词,值是单词频率。这需要时间。这和上面解释的每一个都一样
在hashmap中插入时,保持Treesetspecific为java,每种语言都有大小为10k=10的实现,以保持前10个常用词。直到尺寸小于10,继续添加。如果大小等于10,如果插入的元素大于最小元素,即第一个元素。如果是,请将其拆下并插入新元素
要限制树集的大小,请参见
上述思想的C++11实现
**
}) 你所描述的叫做“trie”。嗨,斯特里兰克。你能详细解释一下分区的过程吗?这怎么不涉及排序??一旦你有了trie,你如何挑选出频率最大的k个单词。没有任何意义有时你必须重复多次,例如,如果你试图获得每个网站或每个主题的常用词列表。在这种情况下,不出一身汗并不能真正解决问题。你仍然需要找到一种尽可能有效的方法。对于一个实际的答案+1,它不能解决不相关的复杂性问题@itsadok:对于每一次跑步:如果足够大,就进行抽样;如果不是,那么获取日志因子就无关紧要了。当我们选择第k个最小的元素时,选择的是第k个最小的哈希键。在步骤3的左分区中不一定有K个字。您将无法在哈希表上运行中间值的中间值,因为它会交换。您必须将数据从哈希表复制到临时数组。因此,将需要On存储。我不明白如何在On中选择第k个最小元素?请查看此以了解在On中查找第k个最小元素的算法-即使使用哈希表+最小堆,复杂性也是相同的。我没有看到任何优化。这通常是一个好的方向-但它有一个缺陷。当计数增加时,我们不仅要检查它的前置项,还需要检查前置项。例如,数组很有可能是[4,3,1,1,1,1,1,1,1,1]-1的数量可以是一样多-这将降低它的效率,因为我们必须回顾所有的前辈,以找到合适的一个来交换。这实际上不是比上一代更糟糕吗?更像是^2,因为它本质上是一种效率很低的排序?嗨,肖恩。是的,我同意你的看法。但我怀疑你提到的问题是这个问题的根本。事实上,如果不只是保留一个排序的值数组,我们可以继续保留一个值数组,索引对,其中索引指向重复元素的第一次出现,那么问题应该可以及时解决。例如,[4,3,1,1,1,1,1,1,1,1,1]看起来像[4,0,3,1,1,1,2,1,2,…,1,2];索引从0开始。您的解决方案1是桶上排序,取代了标准的lg n比较排序。您的方法需要为bucket结构提供额外的空间,但是可以在适当的位置进行比较排序。您的解决方案2在lg k上及时运行-也就是说,On迭代所有单词,Olg k将每个单词添加到堆中。第一个解决方案确实需要更多的空间,但需要强调的是,它实际上是及时的。1:按字键控的哈希频率,On;2:遍历频率散列,创建第二个由频率键控的散列。启用此选项可遍历散列,并通过O1将一个单词以该频率添加到单词列表中。3:从最大频率向下遍历散列,直到达到k。充其量只能是开玩笑。Total=3*On=On。通常在计算字数时,解决方案1中的存储桶数被高估,因为第一个最频繁的字数比第二个和第三个最频繁的字数要频繁得多,因此数组稀疏且效率低下。当k为频繁工作的字数时,解决方案1不起作用
ds小于最常出现的单词的出现次数。100在这种情况下,当然,这在实践中可能不会发生,但我们不应该假设@其中,所提出的解决方案只是一个例子。数字将根据需求而定。对于on*logn排序,您会使用合并排序还是快速排序?对于实际应用,最好使用样本计数。这并不是说最频繁的单词会隐藏在你的样本中。对于复杂性极客来说,这是O1,因为样本的大小是固定的。如果你想要的是对你的复杂性分析的回顾,那么我最好提一下:如果n是你文本中的字数,m是不同字型的字数,我们称之为,第1步开始,但第2步是Om.lgm,请在这个问题上加上一个假设,我们有足够的内存来存储大文本中的所有单词。看到从10GB文件中查找k=100个单词的方法会很有趣,即所有单词都不能放入4GB RAM中@KGhatak如果超过RAM大小,我们将如何处理?您的链接返回404。这将以何种方式改进问题中概述的方法?请不要遗漏SE上代码中的注释。我建议使用Java内置功能,如和?正如您所知,设计高效算法的最重要因素之一是选择正确的数据结构。那么,你如何处理这个问题就很重要了。例如,您需要通过分而治之的方法来解决问题。你需要用贪婪来攻击另一个。正如您所知,Oracle公司正在开发Java。他们是世界上最好的科技公司之一。有一些最杰出的工程师在那里研究Java的内置特性。因此,这些功能都经过了很好的测试和防弹。如果我们能利用它们,我认为最好是利用它们。是的,我们可以用树形图代替列表,树形图有键值列表,数组列表有值列表。我们可以使用使用最高优先权的降序来提取值。
import java.io.*;
import java.lang.reflect.Array;
import java.util.*;
public class TopKWordsTextFile {
static class SortObject implements Comparable<SortObject>{
private String key;
private int value;
public SortObject(String key, int value) {
super();
this.key = key;
this.value = value;
}
@Override
public int compareTo(SortObject o) {
//descending order
return o.value - this.value;
}
}
public static void main(String[] args) {
HashMap<String,Integer> hm = new HashMap<>();
int k = 1;
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("words.in")));
String line;
while ((line = br.readLine()) != null) {
// process the line.
//System.out.println(line);
String[] tokens = line.split(" ");
for(int i=0; i<tokens.length; i++){
if(hm.containsKey(tokens[i])){
//If the key already exists
Integer prev = hm.get(tokens[i]);
hm.put(tokens[i],prev+1);
}else{
//If the key doesn't exist
hm.put(tokens[i],1);
}
}
}
//Close the input
br.close();
//Print all words with their repetitions. You can use 3 for printing top 3 words.
k = hm.size();
// Get a set of the entries
Set set = hm.entrySet();
// Get an iterator
Iterator i = set.iterator();
int index = 0;
// Display elements
SortObject[] objects = new SortObject[hm.size()];
while(i.hasNext()) {
Map.Entry e = (Map.Entry)i.next();
//System.out.print("Key: "+e.getKey() + ": ");
//System.out.println(" Value: "+e.getValue());
String tempS = (String) e.getKey();
int tempI = (int) e.getValue();
objects[index] = new SortObject(tempS,tempI);
index++;
}
System.out.println();
//Sort the array
Arrays.sort(objects);
//Print top k
for(int j=0; j<k; j++){
System.out.println(objects[j].key+":"+objects[j].value);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
**
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> map;
for(int num : nums){
map[num]++;
}
vector<int> res;
// we use the priority queue, like the max-heap , we will keep (size-k) smallest elements in the queue
// pair<first, second>: first is frequency, second is number
priority_queue<pair<int,int>> pq;
for(auto it = map.begin(); it != map.end(); it++){
pq.push(make_pair(it->second, it->first));
// onece the size bigger than size-k, we will pop the value, which is the top k frequent element value
if(pq.size() > (int)map.size() - k){
res.push_back(pq.top().second);
pq.pop();
}
}
return res;
}