输入:正整数K和大文本。实际上,文本可以被视为单词序列。因此,我们不必担心如何将其分解为单词序列 输出:文本中最常见的K字。
我的想法是这样的。
使用哈希表在遍历整个单词序列时记录所有单词的频率。在此阶段,键是“字”,值是“字频”。这需要O(n)时间。
对(word,word-frequency)对进行排序;关键是“字频”。这需要使用正常排序算法的O(n * lg(n))时间。
排序后,我们只取第一个K字。这需要O(K)时间。
总而言之,总时间是O(n + n lg(n)+ K),因为K肯定小于N,所以它实际上是O(n lg(n))
我们可以改善这一点。实际上,我们只想要前K个词。换句话说,频率对我们来说并不重要。因此,我们可以使用“部分堆排序”。对于步骤2)和3),我们不仅仅进行排序。相反,我们将其更改为
2')构建一堆(word,word-frequency)对,以“word-frequency”为关键。构建堆需要花费O(n)时间;
3')从堆中提取前K个单词。每次提取为O(lg(n))。所以,总时间是O(k * lg(n))。
总而言之,该解决方案花费时间O(n + k * lg(n))。
这只是我的想法。我还没有找到改进步骤1)的方法 我希望一些信息检索专家能够更好地阐述这个问题。
答案 0 :(得分:59)
这可以在O(n)时间内完成
解决方案1:
步骤:
计算单词并对其进行哈希处理,这将最终出现在像
这样的结构中var hash = {
"I" : 13,
"like" : 3,
"meow" : 3,
"geek" : 3,
"burger" : 2,
"cat" : 1,
"foo" : 100,
...
...
遍历散列并找到最常用的单词(在本例中为“foo”100),然后创建该大小的数组
然后我们可以再次遍历哈希并使用单词出现次数作为数组索引,如果索引中没有任何内容,则创建一个数组,否则将其附加到数组中。然后我们得到一个像:
这样的数组 0 1 2 3 100
[[ ],[ ],[burger],[like, meow, geek],[]...[foo]]
然后从最后遍历数组,并收集k个单词。
解决方案2:
步骤:
答案 1 :(得分:21)
你不会比你描述的解决方案获得更好的运行时间。你必须至少做O(n)工作来评估所有的单词,然后O(k)额外的工作来找到前k个术语。
如果您的问题集真的很大,您可以使用分布式解决方案,例如map / reduce。 n个映射工作者在每个文本的1 / n处计算频率,并且对于每个单词,将其发送给基于单词的散列计算的m个reducer工作者中的一个。然后减速器将计数相加。对减速器输出的合并排序将为您提供最流行的单词,以便受欢迎。
答案 2 :(得分:13)
如果我们不关心排名最高的K,并且 O(n + k * lg(k)),解决方案的一个小变化会产生 O(n)算法)解决方案,如果我们这样做。我相信这两个界限都是恒定因子内的最佳界限。
在我们遍历列表并插入哈希表之后,这里再次进行优化。我们可以使用median of medians算法来选择列表中的第K个最大元素。该算法可证明是O(n)。
选择第K个最小元素后,我们就像在quicksort中一样对该元素进行分区。这显然也是O(n)。枢轴“左”侧的任何东西都在我们的K元素组中,所以我们已经完成了(我们可以随便扔掉其他所有东西)。
所以这个策略是:
如果要对K个元素进行排名,只需在O(k * lg(k))时间内使用任何有效的比较排序对它们进行排序,得到总运行时间为O(n + k * lg(k))。
O(n)时间限制在常数因子内是最佳的,因为我们必须至少检查一次每个单词。
O(n + k * lg(k))时间界限也是最佳的,因为没有基于比较的方式在小于k * lg(k)时间内对k个元素进行排序。
答案 3 :(得分:8)
如果您的“大单词列表”足够大,您可以简单地抽样并获得估算值。否则,我喜欢散列聚合。
修改:
通过样本,我的意思是选择一些页面子集并计算这些页面中最常用的单词。如果您以合理的方式选择页面并选择具有统计意义的样本,则您对最常用单词的估计应该是合理的。
如果您拥有如此多的数据来处理所有数据只是一种愚蠢的做法,这种方法真的是合理的。如果你只有几个megs,你应该能够撕掉数据并计算出准确的答案,而不必费心而不是费心去计算估计值。
答案 4 :(得分:2)
您可以使用单词的第一个字母进行分区,然后使用下一个字符对最大的多字集进行分区,直到您有k个单字集,从而进一步缩短时间。您将使用256路树的排序,其中包含叶子上的部分/完整单词列表。您需要非常小心,不要在任何地方造成字符串副本。
该算法为O(m),其中m是字符数。它避免了对k的依赖,这对于大k来说是非常好的[通过你的发布运行时间错误的方式,它应该是O(n * lg(k)),并且我不确定那是什么意思米]。
如果你并排运行两种算法,你会得到我非常确定的渐近最优O(min(m,n * lg(k)))算法,但我的平均速度应该更快,因为它不会不涉及散列或排序。
答案 5 :(得分:2)
这是代码
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;
}
}
}
这是单元测试
@Test
public void test() {
TopKFrequentItems stream = new TopKFrequentItems(2);
stream.add("hell");
stream.add("hello");
stream.add("hello");
stream.add("hello");
stream.add("hello");
stream.add("hello");
stream.add("hero");
stream.add("hero");
stream.add("hero");
stream.add("hello");
stream.add("hello");
stream.add("hello");
stream.add("home");
stream.add("go");
stream.add("go");
assertThat(stream.getItems()).hasSize(2).contains(new TopK("hero", 3), new TopK("hello", 8));
}
有关详细信息,请参阅this test case
答案 6 :(得分:2)
如果你所追求的是 k 文本中最常见的单词列表,对于任何实际 k 以及任何自然语言,那么你算法的复杂性不相关。
只需示例,例如,您的文字中的几百万字,使用任何算法在几秒钟内处理,并且最常用的计数将非常准确
作为旁注,虚拟算法的复杂性(1.计数全部2.排序计数3.取最佳值)是O(n + m * log(m)),其中m是不同的数量你的文字中的单词。 log(m)远小于(n / m),因此它保持为O(n)。
实际上,漫长的步骤正在计算中。
答案 7 :(得分:2)
你的问题与此相同 - http://www.geeksforgeeks.org/find-the-k-most-frequent-words-from-a-file/
使用Trie和min heap来有效地解决它。
答案 8 :(得分:2)
您的描述中有一个错误:计数需要O(n)时间,但排序需要O(m * lg(m)),其中m是唯一字数。这通常比单词总数小得多,因此可能应该优化散列的构建方式。
答案 9 :(得分:1)
使用哈希表在遍历整个单词序列时记录所有单词的频率。在此阶段,键是“字”,值是“字频”。这需要O(n)时间。这与上面解释的每一个相同
在将自己插入hashmap时,保留Treeset(特定于java,每种语言都有实现),大小为10(k = 10),以保留前10个常用词。直到小于10,继续添加。如果大小等于10,则插入元素大于最小元素,即第一元素。如果是,请删除它并插入新元素
要限制树集的大小,请参阅this link
答案 10 :(得分:0)
我只是找到了解决这个问题的另一个解决方案。但我不确定这是对的。 解决方案:
答案 11 :(得分:0)
我也在努力解决这个问题,并受到@aly的启发。我们可以只维护一个预先排序的单词列表(List<Set<String>>
)而不是后面的排序,单词将位于位置X的集合中,其中X是单词的当前计数。通常,这是它的工作原理:
Map<String, Integer>
。这样做的缺点是列表可能很大 - 可以使用TreeMap<Integer, Set<String>>
进行优化 - 但这会增加一些开销。最终,我们可以混合使用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);
}
}
答案 12 :(得分:0)
尝试考虑特殊的数据结构来解决这类问题。在这种情况下,特殊类型的树像trie以特定方式存储字符串,非常有效。或者第二种方法来构建自己的解决方案,比如计算单词我想这个数据TB将是英文的,那么我们一般有大约600,000个单词所以只能存储那些单词并计算哪些字符串会被重复+这个解决方案需要正则表达式来消除一些特殊字符。第一种解决方案会更快,我很确定。
答案 13 :(得分:0)
我相信这个问题可以通过O(n)算法来解决。我们可以在运行中进行排序。换句话说,在这种情况下的排序是传统排序问题的子问题,因为每次访问哈希表时只有一个计数器增加1。最初,列表已排序,因为所有计数器都为零。当我们继续在哈希表中递增计数器时,我们预订另一个按频率排序的哈希值数组,如下所示。每次我们递增计数器时,我们都会检查排序数组中的索引,并检查其计数是否超过列表中的前一个。如果是这样,我们交换这两个元素。因此,我们获得的解决方案最多为O(n),其中n是原始文本中的单词数。
答案 14 :(得分:0)
这是一个有趣的搜索想法,我可以找到与Top-K相关的论文https://icmi.cs.ucsb.edu/research/tech_reports/reports/2005-23.pd f
还有一个here的实现。
答案 15 :(得分:0)
最简单的代码,用于获取最常用单词的出现。
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])
}
答案 16 :(得分:0)
假设我们有一个单词序列“ad”“ad”“boy”“big”“bad”“com”“come”“cold”。并且K = 2。 正如你提到的“使用单词的第一个字母进行分区”,我们得到了 (“ad”,“ad”)(“男孩”,“大”,“坏”)(“com”“来”“冷”) “然后使用下一个字符对最大的多字集进行分区,直到你有k个单字集。” 它将分区(“男孩”,“大”,“坏”)(“com”“来”“冷”),第一个分区(“广告”,“广告”)被遗漏,而“广告”实际上是最常见的词。
也许我误解了你的观点。您能否详细说明您的分区流程?
答案 17 :(得分:0)
在这些情况下,我建议使用Java内置功能。因为,它们已经过良好的测试和稳定。在这个问题中,我通过使用HashMap数据结构找到了单词的重复。然后,我将结果推送到一个对象数组。我通过Arrays.sort()对对象进行排序,并打印前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();
}
}
}
有关详细信息,请访问JSBin。我希望它有所帮助。
答案 18 :(得分:0)
**
C ++ 11上述思想的实现
**
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;
}
};