优先级队列TOPK问题

一、java中元素的比较

(1)在Java中比较两个元素相等用equals方法

(2)比叫自定义对象的大小关系时,类重写Comparable接口,实现compareTo方法

          若一个类Student implents Comparable,则这个Student类具备了可比较的能力

           public comparareTo(Object o){    }  ,比较当前对象和传入对象的大小关系

            ①当返回值>0时,当前元素"大于"传入对象o

            ②当返回值<0时,当前元素"小于"传入对象o

            ③当返回值=0时,当前元素"等于"传入对象o

package priority_queue.compare;

import java.util.Arrays;

public class Student implements Comparable {
    private int age;
    private  String name;

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public int compareTo(Student o) {
      return this.age-o.age;
    }

    public static void main(String[] args) {
        Student[] arr=new Student[]{
                new Student(19,"张三"),
                new Student(20,"李四"),
                new Student(18,"王麻子")
        };
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

优先级队列TOPK问题_第1张图片

(3)实现java.until.comparator 接口=>比较器接口

 优先级队列TOPK问题_第2张图片

 此时进行排序就会到的一个升序的数组,但如果此时要得到一个降序的数组就要改为" o.age-this.age",在程序设计模式中提到,一段程序应该对扩展开放,对修改关闭,就是说当出现新的要求时,尽量不要去影响已经写好的代码,而是用新的代码去完成新要求

java.until.Comparator接口:

一个类如果实现了这个接口,就表示这个类天生就是为别的类的大小关系服务的

优先级队列TOPK问题_第3张图片

StudentSec这个类天生就是为了Student对象的排序而存在的,并且重写了cpmparr方法

传入两个值o1和o2比较他们的大小关系 

①当返回值大于0时,说明o1>o2

②当返回值小于0时,说明o1

③当返回值等于0时,说明o1=o2

package priority_queue.compare;

import java.util.Arrays;
import java.util.Comparator;


class StudentSec implements Comparator{

    @Override
    public int compare(Student o1, Student o2) {
        return o1.getAge()-o2.getAge();
    }
}

class StudentDesc implements Comparator{

    @Override
    public int compare(Student o1, Student o2) {
        return o2.getAge()-o1.getAge();
    }
}

public class Student  {
    private int age;
    private  String name;

    public int getAge() {
        return age;
    }

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public static void main(String[] args) {
        Student[] arr=new Student[]{
                new Student(19,"张三"),
                new Student(20,"李四"),
                new Student(18,"王麻子")
        };
        //此时Student不具备比较的能力,但是可以通过比较器去比较
        Arrays.sort(arr,new StudentSec());
        System.out.println(Arrays.toString(arr));
        Arrays.sort(arr,new StudentDesc());
        System.out.println(Arrays.toString(arr));
    }
}

优先级队列TOPK问题_第4张图片

 可以看出来此时就不需要在原来的代码进行修改,需要什么样的比较方法,就产生一个新的类专门去实现,极大程度上实现了java的开闭原则

二、基于堆的优先级队列的实现(TOPK) 

1、优先级队列的实现

package priority_queue;

import queue.Queue;

public class PriorityQueue  implements Queue {

    private MaxHeap heap;

    public PriorityQueue(){
        heap=new MaxHeap();
    }

    @Override
    public void offer(Integer val) {
        heap.add(val);
    }

    @Override
    public Integer poll() {
        return heap.extractMax();
    }

    @Override
    public Integer peek() {
        return heap.peekMax();
    }

    @Override
    public boolean isEmptty() {
        return heap.isEmpty();
    }
}

2、例题引入TOPK问题

leetcode面试题17.14

优先级队列TOPK问题_第5张图片

看到题的第一种想法是使用排序然后取出前k个值就可以,但是此时的时间复杂度0(nlogn),假如此时要求时间复杂优于O(nlogn),那么排序就行不通了,就要用到优先级队列

 TOPK问题都可以用优先级队列去解决,若需要取出前k个最大元素就构造最小堆,若需要取出前k个最小元素就构造最大堆

 思路:取出最小的k个元素,构造一个大小为k的最大堆

①若此时堆中的元素

②若此时堆中的元素=k

    a.新来的元素val>=堆顶元素,一定大于此时堆中的所有元素,则val一定不是需要的元素

    b.新来的元素val<堆顶元素,将堆顶元素出队,将新元素添加到堆中

    c.重复上述操作,直到没有新元素为止,此时堆中就是前k个最小值

package priority_queue.leetcode;

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

/**
 * 设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可
 */

class IntegerReverse implements Comparator{

    @Override
    public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
}

public class Num17_14 {
    public int[] smallestK(int[] arr, int k) {
        int[] ret=new int[k];
        if (arr.length==0||k==0){
            return ret;
        }
        //JDK是默认使用最小堆,所以我们要将其改造为最大堆,通过比较器实现
        Queue queue=new PriorityQueue<>(new IntegerReverse());
        //遍历集合,堆中只保存k个元素
        for (int i = 0; i < arr.length; i++) {
            if (queue.size()

3、前 K 个高频元素 

优先级队列TOPK问题_第6张图片

 优先级队列要保存的元素是键值对时,当现有的类型无法解决问题时,就自定义一个类

package priority_queue.leetcode;

import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;

/**
 * 前 K 个高频元素
 */
//自定义一个类,此时类中保存了数组中的元素以及出现的频次
class kind implements Comparable{
    //数组中出现的元素
    int key;
    //元素出现的频次
    int frequency;

    public kind(int key, int frequency) {
        this.key = key;
        this.frequency = frequency;
    }

    @Override
    public int compareTo(kind o) {
        return this.frequency-o.frequency;
    }
}

public class Num347 {
    public int[] topKFrequent(int[] nums, int k) {
          int[] ret=new int[k];
          //先遍历数组,将出现的元素次数以及频次保存到Map集合中
        Map map=new HashMap<>();
        for (int i:nums){
            if (map.containsKey(i)){
                //此时i元素已经出现过,就将频次++
                int times=map.get(i);
                map.put(i,times+1);
            }else{
                //此时i元素第一次出现,就将该元素保存到map集合中
                map.put(i,1);
            }
        }
        //经过集合的遍历之后,将出现频次最高的前k个元素添加到优先级队列中
        Queue queue=new PriorityQueue<>();
        for (Map.Entryentry : map.entrySet()){
            if (queue.size()kind.frequency){
                    queue.poll();
                    queue.offer(new kind(entry.getKey(), entry.getValue()));
                }
            }
        }
        //此时堆中就保存了前k个出现频次最大的键值对,遍历堆,将key取出
        int i=0;
        while (!queue.isEmpty()){
            ret[i]=queue.poll().key;
            i++;
        }
        return ret;
    }
}

4、查找和最小的 K 对数字

优先级队列TOPK问题_第7张图片

 利用和上一个题相同的思路,首先明确优先级队列中该保存什么?

优先级队列中应该保存两个元素v1和v2,其中v1来自第一个数组,v2来自第二个数组,此时优先级中的"大小"关系就应该有v1和v2的加和大小判断,因为题目要求就是找到和最小的k个数对

注意:双循环的终止条件,从题目中可以知道两个数组都是排序好的升序数组,所以当要求去找出和最小的k对数组时,当k大于数组长度时,也就是示例3来说,就是将数组中的所有数都返回,当k小于等于数组长度时,只返回前k对数就行,总结循环的条件就是数组长度和k的最小值

三、相关代码

 https://gitee.com/ren-xiaoxiong/rocket_class_ds/tree/master/src/priority_queue/icon-default.png?t=M4ADhttps://gitee.com/ren-xiaoxiong/rocket_class_ds/tree/master/src/priority_queue/

你可能感兴趣的:(优先级队列,java,数据结构,intellij-idea)