原问题是:给定两个数组,求两个数组中最大的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小的较多的情况下是比较合适的。
最后给个使用场景:比如某网站想实时查询当前最热门(搜索次数最多)的几个关键字,就可以用这种办法实现