题目一
给你一些词汇及其词频,如何实现求出词频最高的前k个?
思路
这题不难,你可以很容易使用堆结构来实现,比如,如果用大根堆的话,最后输出大根堆的前k个堆顶就行了。同时,如果用小根堆,也可以实现求词频最多的前k个,如何实现?可以创建一个大小为k的小根堆,小根堆的堆顶其实就相当于一个门槛,维护前k个中最小的值,当又来一个词汇时,如果他的词频大于门槛,则他干掉门槛进入小根堆,然后小根堆重新调整,如果新来的连门槛都没干掉,那么他不可能是前k个。
题目二
借鉴题目一的思路,我们可以使用小根堆来实现统计前k个最大值的操作。现在,题目要求改变了,题目一中,给你的是已经确定的词频,现在,如果词频不确定呢?或者说,词频是在动态改变的话,如何实现统计最大的前k个呢?显然,此时,再使用题目一的方法就不合适了,因为,每次词频改变,都需要更新堆中的元素及其位置,而这个过程,系统给我们提供的堆结构是没有这个功能的,系统只会重新创建一个,而不会动态修改,因此,每次更新就重新创建,这复杂度就太高了。
因此,此时,就需要我们自己实现一种能够动态调整的堆结构,来完成此题。
数据结构分析:
首先,大思路仍然不变,我们还是使用小根堆来实现统计k个最大值,因此,我们需要一个长度为k的数组,来表示小根堆。
其次,因为词频会动态改变,因此,我们需要维护一个词频表,来保存当前词频以及词频的更新操作。
最后,来分析一下,为什么系统的堆无法实现这个功能呢?最主要的问题就是,当我们修改一个词频的时候,我们是无法在系统已经构建好的堆上面,寻找到该词的位置,因此,只能遍历寻找,这个复杂度就很高了,因此,如果我们自己实现的话,可以直接定位在堆上的位置,应该很舒服吧,因为,最后还需要一个能够获取堆上每个词的位置的数据结构,用map来保存。
思路:
首先,每来一个词,就看看词频表中是否存在该次,如果不存在,就创建该词,并把词频标做1,然后,观察小根堆是否已满,如果未满,直接入堆,如果满了,就把该词的词频与小根堆堆顶比较,如果赢了,就入堆,然后调整堆。如果该词已经在小根堆中,则因为词频的更改调整小根堆。同时,对应小根堆位置的map中,保存该词及其在小根堆上的位置,如果不在小根堆上,就保存-1。
然后动态更新维护这三个数据结构。遍历小根堆就是输出当前最大的k个词频。
代码
public class MyHeap {
//小根堆
private String[] heap;
//保存词频
private HashMap<String,Integer> wordFrequence;
//保存在小根堆中的位置
private HashMap<String,Integer> heapSite;
//用来判断小根堆是否已满
private int index;
public MyHeap(int k){
heap=new String[k];
wordFrequence=new HashMap<>();
heapSite=new HashMap<>();
index=0;
}
//打印显示词频最高的k个
public void show(){
for (int i = 0; i < heap.length; i++) {
System.out.println(heap[i]);
}
}
//动态添加一个词
public void add(String str){
//先更新词频
if(!wordFrequence.containsKey(str)){
wordFrequence.put(str,1);
heapSite.put(str,-1);
}else {
//更新词频
wordFrequence.put(str,wordFrequence.get(str)+1);
}
//判断是否已经出现过,以及是否在小根堆中,如果在,直接调整堆即可
if(heapSite.containsKey(str)&&heapSite.get(str)!=-1){
heapify(heapSite.get(str));
}else {
//堆未满,直接入堆后向上调整堆
if(index<=heap.length-1){
heap[index]=str;
heapSite.put(str,index);
heapInsert(index);
index++;
}else {
//堆已满,判断是否词频比门槛高?
if(wordFrequence.get(str)>wordFrequence.get(heap[0])){
//比门槛高,交换后向下调整堆
heapSite.put(heap[0],-1);
heapSite.put(str,0);
heap[0]=str;
heapify(0);
}else {
//比不过门槛
heapSite.put(str,-1);
}
}
}
}
//堆不满时,插入堆后,向上调整
private void heapInsert(int index) {
while (wordFrequence.get(heap[index])<wordFrequence.get(heap[(index-1)/2])){
swap(index,(index-1)/2);
index=(index-1)/2;
}
}
//交换堆中两个位置,同时更新heapSite
private void swap(int index, int i) {
String temp=heap[index];
heap[index]=heap[i];
heap[i]=temp;
heapSite.put(heap[i],i);
heapSite.put(heap[index],index);
}
//干掉门槛后,向下调整堆
private void heapify(int index) {
while (index<heap.length){
int left=index*2+1;
int right=left+1;
int min;
if(left>=heap.length){
return ;
}else if(right>=heap.length){
min=left;
}else {
min=wordFrequence.get(heap[left]) < wordFrequence.get(heap[right]) ? left:right;
}
if(wordFrequence.get(heap[index])>wordFrequence.get(heap[min])){
swap(index,min);
}
index=min;
}
}
}