您的位置:首页 > 其它

堆的使用:使用小顶堆处理问题----查找几个数组中最大的k个数

2016-04-07 15:25 375 查看
原问题是:给定两个数组,求两个数组中最大的k个数

刚上来看到是两个数组以为要排序,后来发现2个数组其实也只是幌子,可以使用维持一个k个数的集合,进一个(依次)出一个(最小值)的方式来解决,最后全部过滤完留在集合里的就是最大的k个数,时间上需要考虑的就是(N-k)*time1(k)+time2(k),其中N为这两个数组的总数目,time1(k)为在集合中每次新加一个元素然后从k+1个元素中踢出最小值的时间,time2(k)为构建初始集合的时间。

 


影响时间的因素主要是3部分(N-k),time1(k),time2(k).

这里使用小顶堆来做为过滤使用的集合,其:

time2: 建堆最坏klogk,平均kO(1);

time1: 过滤操作:相当于删除堆顶元素,然后执行一次堆顶元素的重定位(说重排序也不准确,不知道用什么词来表达)

 

1.      堆的数据结构

package ADT.Heap;

/**
* Created by Lijn on 2016/4/6.
*
* @author Lijn
*/
public interface IHeap<T extends Comparable<T>> {

/**
* @Description:  插入
* @return
*/
public void insert(T x);

/**
* @Description:  查找最小值
* @return
*/
public T findMin();

/**
* @Description:  删除最小值
* @return
*/
public T deleteMin();

/**
* @Description:  是否为空
* @return
*/
public boolean isEmpty();

/**
* @Description:  清空
*/
public void makeEmpty();
}
/**
* @Title:       BinaryHeap.java
* @Description:  小顶堆
* @date:        2014-11-3下午6:17:59
* @version:     V1.0
*/
package ADT.Heap;

import java.lang.reflect.Array;
import java.util.Arrays;

/**
* @Description:
* @author: 	 Lijn
* @time:        2014-11-3
*/
public class BinaryHeap<T extends Comparable<T>> implements IHeap<T>
{

/**
* @Description:  默认大小
*/
protected static final int DEFAULT_CAPACITY=8;

/**
* @Description: 存储的节点数目
*/
protected int currentSize;
protected T[] arrayData;

/**
* @Description: constructor
*/
public BinaryHeap()
{
arrayData =(T[])Array.newInstance(Comparable.class, DEFAULT_CAPACITY);
}

/**
* @Description: constructor
*/
public BinaryHeap(int n)
{
arrayData =(T[])Array.newInstance(Comparable.class, n+1);
}

/**
* @Description: constructor:初始数组
* @param items
* @throws Exception
*/
public BinaryHeap(T[] items) throws Exception
{
currentSize=items.length;
arrayData =(T[])new Comparable[(currentSize+1)*11/10];

//浅拷贝
int i=1;
for(T t:items)
{
arrayData[i++]=t;
}
buildHeap();
}

/**
* @Description:  插入2叉堆
* 堆的平均插入次数是O(1):2.607;最大插入次数是O(log(N))
* @param x
*/
public void insert(T x)
{
//数组不够则扩充
if(currentSize == arrayData.length - 1){
enlargeArray(arrayData.length*2);
}

//插入空穴
int hole = ++currentSize;

//上虑
for(;hole>1 && x.compareTo(arrayData[hole/2])<0;hole/=2){
arrayData[hole] = arrayData[hole/2];
}

arrayData[hole] = x;
}

/**
* @Description:  查找最小值
* @return
*/
public T findMin() {
if(isEmpty())
{
return null;
}else{
return arrayData[1];
}
}

/**
* @Description:  删除最小值
* @return
* @throws Exception
*/
public T deleteMin()
{
if(isEmpty()){
throw new RuntimeException("空堆");
}

T minT = findMin();
arrayData[1] = arrayData[currentSize--];
percolateDown(1);

return minT;
}

/**
* @Description:  是否为空
* @return
*/
public boolean isEmpty()
{
return currentSize==0?true:false;
}

/**
* @Description:  清空
*/
public void makeEmpty()
{
currentSize=0;
}

/**
* @Description:  下虑操作
* @param hole 下移的节点的位置
*/
protected void percolateDown(int hole)
{
int child ;
T tmp = arrayData[hole];
for(;hole*2<=currentSize;hole=child)
{
//下虑 操作需要考虑左右子树的大小,取较小的子节点
child = hole*2;
if(child!=currentSize && arrayData[child+1].compareTo(arrayData[child])<0)
{
child++;
}

//空穴下虑
if(arrayData[child].compareTo(tmp)<0)
{
arrayData[hole] = arrayData[child];
}else{
break;
}
}

arrayData[hole] = tmp;
}

/**
* @Description:  上率操作
* @param hole
*/
private void percolateUp(int hole)
{
T x = arrayData[hole];
for(;hole>1 && x.compareTo(arrayData[hole/2])<0;hole/=2){
arrayData[hole] = arrayData[hole/2];
}

arrayData[hole] = x;
}

/**
* @Description:  将传入的数组堆序
*/
private void buildHeap()
{
for(int i=currentSize/2;i>0;i--)
{
percolateDown(i);
}
}

/**
* @Description:  扩充数组
* @param newSize
*/
private void enlargeArray(int newSize)
{
if(newSize < arrayData.length )
{
//正好是一层叶子节点
newSize = arrayData.length*2;
}

arrayData = Arrays.copyOf(arrayData, newSize);
}

public String toString(){
StringBuilder sb = new StringBuilder();
for(int i=1;i<=currentSize;i++){
sb.append(arrayData[i]).append(";");
}
return sb.toString();
}
}


2.      用来过滤的工具

package Option.heapUse1;

import ADT.Heap.BinaryHeap;

public class MiniTopHeap<Integer extends Comparable<Integer>> extends BinaryHeap<Integer>{

public MiniTopHeap(int n){
super(n);
}

@Override
public void insert(Integer x) {
//数组不够则扩充
if(currentSize == arrayData.length - 1){
throw new RuntimeException();
}
super.insert(x);
}

/**
* @Description:  下虑操作
* @param x 新插入的数
*/
public void filter(Integer x){
if(currentSize != arrayData.length-1){
throw new RuntimeException("堆尚未存满");
}
if(x.compareTo(arrayData[1]) < 0){
return;
}else{
arrayData[1] = x;
percolateDown(1);
}

}

}


3.      测试代码

/**
* @Title:       HeapUse1.java
* @date:        2016-04-06下午3:38:37
* @version:     V1.0
*/
package Option.heapUse1;

import java.util.Random;

/**
* @Title:       HeapUse1
* @Description: 使用小顶堆来求解问题:给定几个数字数组,求这几个数组的最大的k个值
*               复杂度:Nlogk  (N为总数目,k为所求数目)
* @author: 	 Lijn
* @date:        2016-04-06下午3:38:37
* @version:     V1.0
*/
public class HeapUse1 {
public static void main(String[] args){
Integer[] array1 = new Integer[500];
Integer[] array2 = new Integer[500];
Random rd = new Random();
int k = 100;
MiniTopHeap<Integer> heap = new MiniTopHeap<Integer>(k);
for(int i =0;i<500;i++){
array1[i]= rd.nextInt(1000);
array2[i]= rd.nextInt(1000);
}

//平均   k 最坏 klogk
for(int j=0;j<k;j++){
heap.insert(array1[j]);
}

//平均  (1000-k)logk  最坏(1000-k)logk
for(int j=k;j<500;j++){
heap.filter(array1[j]);
}
for(int j=0;j<500;j++){
heap.filter(array2[j]);
}

System.out.println(heap.toString());
}
}


 

这样总的操作时间就是O(nlogk)

在k比n小的较多的情况下是比较合适的。

最后给个使用场景:比如某网站想实时查询当前最热门(搜索次数最多)的几个关键字,就可以用这种办法实现
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: