Java 优先队列(PriorityQueue)总结

PriorityQueue 实现的是 Queue 接口 ,可以使用 Queue 提供的方法,以及自带的方法。
在这里插入图片描述

1、PriorityQueue概述

Java PriorityQueue 实现了 Queue 接口,不允许放入 null 元素;其通过堆实现,具体说是

通过完全二叉树(complete binary tree)实现的小顶堆(任意一个非叶子节点的权值,都不

大于其左右子节点的权值),也就意味着可以通过数组来作为PriorityQueue 的底层实现,

数组初始大小为11;也可以用一棵完全二叉树表示。

优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次

取最小元素,C++的优先队列每次取最大元素)。这里牵涉到了大小关系,元素大小的评判

可以通过元素本身的自然顺序(natural ordering),也可以通过构造时传入的比较

(Comparator,类似于C++的仿函数)。

2、常用方法总结

public boolean add(E e); //在队尾插入元素,插入失败时抛出异常,并调整堆结构
public boolean offer(E e); //在队尾插入元素,插入失败时抛出false,并调整堆结构

public E remove(); //获取队头元素并删除,并返回,失败时前者抛出异常,再调整堆结构
public E poll(); //获取队头元素并删除,并返回,失败时前者抛出null,再调整堆结构

public E element(); //返回队头元素(不删除),失败时前者抛出异常
public E peek();//返回队头元素(不删除),失败时前者抛出null

public boolean isEmpty(); //判断队列是否为空
public int size(); //获取队列中元素个数
public void clear(); //清空队列
public boolean contains(Object o); //判断队列中是否包含指定元素(从队头到队尾遍历)
public Iterator<E> iterator(); //迭代器

3、堆结构调整

每次插入或删除元素后,都对队列进行调整,使得队列始终构成最小堆(或最大堆)。

具体调整如下:

  • 插入元素后,从堆底到堆顶调整堆;

  • 删除元素后,将队尾元素复制到队头,并从堆顶到堆底调整堆。

小根堆结构调整

插入( add()和offer()方法 )元素后,向上调整堆:

//siftUp()
private void siftUp(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;//parentNo = (nodeNo-1)/2
        Object e = queue[parent];
        if (comparator.compare(x, (E) e) >= 0)//调用比较器的比较方法
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

删除( remove()和poll()方法 )元素后,向下调整堆:

//siftDown()
private void siftDown(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        //首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标
        int child = (k << 1) + 1;//leftNo = parentNo*2+1
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;//然后用c取代原来的值
        k = child;
    }
    queue[k] = x;
}

4、应用:topK问题

topK问题是指:从海量数据中寻找最大的前k个数据,比如从1亿个数据中,寻找最大的1万个数。

使用优先队列,能够很好的解决这个问题。先使用前1万个数构建最小优先队列,以后每取

一个数,都与队头元素进行比较,若大于队头元素,就将队头元素删除,并将该元素添加到

优先队列中;若小于队头元素,则将该元素丢弃掉。如此往复,直至所有元素都访问完。最

后优先队列中的1万个元素就是最大的1万个元素。

例1: 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。你需要找的是数组

排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 输出: 4

public class Main {
   public static void main(String[] args) {
       //int[] arr = new int[]{3,2,1,5,6,4};
       int[] arr = new int[]{3,2,3,1,2,4,5,5,6};
       int k = 4;
       test(arr,k);
   }


   /**
    * 方法三:PriorityQueue 优先队列思想
    * 返回数组第k个最大数据
    * @param nums
    * @param k
    */
   public static int findKthLargest3(int[] nums, int k){   //时间复杂度:O(NlogK),遍历数据 O(N),堆内元素调整 O(K),空间复杂度:O(K)
       int len = nums.length;
       // 使用一个含有 k 个元素的最小堆
       // k 堆的初始容量,(a,b) -> a -b 比较器
       PriorityQueue<Integer> minTop = new PriorityQueue<>(k,(a, b) -> a -b);
       for (int i = 0; i < k; i++){
           minTop.add(nums[i]);
       }
       for (int i = k; i < len; i++){
           Integer topEle = minTop.peek();  // 返回队头元素(不删除),失败时前者抛出null
           // 只要当前遍历的元素比堆顶元素大,堆顶弹出,遍历的元素进去
           if (nums[i] > topEle){
               minTop.poll();  // 获取队头元素并删除,并返回,失败时前者抛出null,再调整堆结构
               minTop.offer(nums[i]);  // 在队尾插入元素,插入失败时抛出false,并调整堆结构
           }
       }
       return minTop.peek();
   }

   public static void test(int[] input,int key){
       long begin = System.currentTimeMillis();

       int result = findKthLargest3(input,key);
       System.out.println("result = " + result);
       
       long end = System.currentTimeMillis();

       System.out.println();
       System.out.println("耗时:" + (end - begin) + "ms");
       System.out.println("--------------------");
   }
}

运行结果:
在这里插入图片描述
例2:
求 数组nums={6,14,71,21,9,18,1,4,23,0,45,66} 中最大的5个数

import java.util.PriorityQueue;
 
public class Main {
	static int[] nums={6,14,71,21,9,18,7,4,23,0,45,66};
	
	public static void main(String[] args) {
		topK();
	}
 
	public static void topK() {
		PriorityQueue<Integer> queue = new PriorityQueue<Integer>();
		for(int i=0; i<5; i++) {
			queue.add(nums[i]);
		}
		for(int i=5; i<12; i++) {
			if(nums[i] > queue.element()) {
				queue.remove();
				queue.add(nums[i]);
			}
		}
		while(!queue.isEmpty()) {
			int Ele=queue.remove();
			System.out.print(Ele+" ");
		}
	}
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值