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


原问题是:给定两个数组,求两个数组中最大的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小的较多的情况下是比较合适的。


最后给个使用场景:比如某网站想实时查询当前最热门(搜索次数最多)的几个关键字,就可以用这种办法实现

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值