接上一篇,二叉堆实际的应用
一.java定时器对象Timer,里面就是维护着数组实现的二叉堆,最先要到期的任务放在数组中第一个位置,第一个位置出队后,剩下的数组再通过下沉上浮变形成新的二叉堆;依次下去成一个定时器。

1.问题:为什么不直接一下子排好顺序?
答案很简单,因为不需要,定时任务只要知道下一个要执行的任务就可以,没有必要浪费更高的时间复杂度去实现完全有序。
2.顺便记录一下,使用Timer对象,有一些隐形的坑:
1 public static void main(String[] args) { 2 //创建定时器对象 3 Timer timer = new Timer(); 4 5 //添加任务1,延迟1000ms执行 6 timer.schedule(new TimerTask() { 7 @Override 8 public void run() { 9 for (; ; ) { 10 System.out.println("---one Task---"); 11 try { 12 Thread.sleep(2000); 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } 16 } 17 //throw new RuntimeException("error "); 18 } 19 }, 1000); 20 21 //添加任务2,延迟1500ms执行 22 timer.schedule(new TimerTask() { 23 @Override 24 public void run() { 25 System.out.println("---two Task---"); 26 try { 27 Thread.sleep(1000); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 } 32 }, 1500); 33 }
1).上面例子中,永远只会打印"---one Task---",为什么呢?
因为task1和task2放进Timer对象内部数组的时候,用的是系统当前时间,task1延迟1000ms后执行,task2延迟1500ms后执行;而timer对象内部是单线程,不停的在死循环,去二叉堆中看第一个任务是不是可以到时间执行了,显然task1会先执行,串行顺序的执行,如果task1占着不放,task2将永远执行不了
2).同样因为单线程的串行顺序执行,如果有一个任务在执行过程中抛出异常,完了,整个后续链就over了
3).sched增加任务、cancel取消任务、purge清除任务、执行任务,都在操作同一个队列,Timer对象是怎样保证这个队列线程安全的呢?嗯,用悲观锁synchronized(queue)的方式,所以,你懂的,会出现竞争锁的问题
二.PriorityQueue优先队列
估计好多同学和我一样,之前只是听说过这个队列,尤其是在线程池相关的面试题中,哈哈,其实这个队列还是很牛的,在某些场景下;
1.leetcode 703:求数据流中的第K大元素,数据流中包含初始数组,每次调用add(int val)方法,增加新的元素val后再返回当前数据流中第K大的元素。当然解这道题时,可以不管三七二十一,整个数组全排列,然后在排好的数组中再找出第K大的元素,嗯,可以,时间复杂度呢?
1 int k; 2 3 public KthLargest703(int k, int[] nums) { 4 queue = new PriorityQueue<>(); 5 for(int i=0;i<nums.length;i++) { 6 queue.add(nums[i]); 7 } 8 this.k = k; 9 } 10 11 PriorityQueue<Integer> queue;//优先级队列:内容使用堆(默认二叉小顶堆)来实现的 12 13 public int add(int val) { 14 if(queue.size()<k){//队列中还没有k个元素 15 queue.offer(val);//直接放入队列 16 } else if (queue.peek() < val){//当前值大于堆顶 17 queue.poll();//堆顶扔出来 18 queue.offer(val);//当前值放入队列,内容自动调整上浮或下沉 19 } 20 return queue.peek();//返回堆顶,默认使用了小顶堆 21 }
2.leetcode 347:给定一个非空的整数数组,返回其中出现频率前 k 高的元素,仔细想想,其实和上面703的问题一样
1 public static List<Integer> topKFrequent(int[] nums, int k) { 2 //使用哈希存储元素与频率的映射 3 Map<Integer, Integer> map = new HashMap<>(); 4 for (int i : nums) { 5 Integer temp = map.get(i); 6 if(temp==null){ 7 map.put(i, 1); 8 } else { 9 temp++; 10 map.put(i, temp); 11 } 12 } 13 14 //使用小顶堆筛选前k个高频的元素,不能使用自然排序,需要重写比较的类函数 15 PriorityQueue<Integer> pq = new PriorityQueue<>(k, new Comparator<Integer>() { 16 public int compare(Integer a, Integer b){ 17 return map.get(a) - map.get(b); //堆中存放的是元素,但比较的应该是元素的频率 18 } 19 }); 20 int size = 0; 21 for (Integer j : map.keySet()) { 22 if(size<k){ 23 pq.offer(j);//前k个元素直接放入,自动排成小顶堆 24 size++; 25 }else if(map.get(j)>map.get(pq.peek())){ 26 pq.poll(); 27 pq.add(j); 28 } 29 } 30 31 //返回结果 32 List<Integer> ans = new ArrayList<>(); 33 while(!pq.isEmpty()){ 34 ans.add(pq.poll()); 35 } 36 return ans; 37 }
3.leetcode 239:给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字,滑动窗口每次只向右移动一位;返回滑动窗口中的最大值