PriorityQueue(堆)续集

1. PriorityQueue中插入对象

优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够
进行比较

复制代码
class Card {
	public int rank; // 数值
	public String suit; // 花色
	public Card(int rank, String suit) {
		this.rank = rank;
		this.suit = suit;
	}
}
public class TestPriorityQueue {
	public static void TestPriorityQueue()
	{
	PriorityQueue<Card> p = new PriorityQueue<>();
	p.offer(new Card(1, "♠"));
	p.offer(new Card(2, "♠"));
	}
	public static void main(String[] args) {
	TestPriorityQueue();
	}
}

这个 ClassCastException 报错的意思是:

Card 类不能被强制转换为 Comparable 接口
PriorityQueue(优先队列 / 堆)在插入元素时,需要知道如何比较两个元素的大小,才能维护堆的结构。

  • 如果往 PriorityQueue 里放自定义对象 (如 Card),Java 默认会尝试把它强转为 Comparable 接口,调用 compareTo() 方法来排序。
  • 但是Card没有实现 Comparable 接口 ,也没有给 PriorityQueue 传入自定义比较器 Comparator,所以就抛出了这个类型转换异常。

2. 元素的比较

2.1 基本类型的比较

在Java中,基本类型的对象可以直接比较大小。

复制代码
public class test {  
    public static void main(String[] args) {  
        int a = 10;  
        int b = 20;  
        System.out.println(a > b);      // flase  
        System.out.println(a < b);      // true  
        System.out.println(a == b);     // flase  
        System.out.println("============================");  
        char c1 = 'A';  
        char c2 = 'B';  
        System.out.println(c1 > c2);       // flase  
        System.out.println(c1 < c2);        // true  
        System.out.println(c1 == c2);       // flase  
        System.out.println("============================");  
        boolean b1 = true;  
        boolean b2 = false;  
        System.out.println(b1 == b2);       // flase  
        System.out.println(b1 != b2);       // true  
    }  
}

2.2 对象比较的问题

复制代码
class Card {  
    public int rank; // 数值  
    public String suit; // 花色  
    public Card(int rank, String suit) {  
        this.rank = rank;  
        this.suit = suit;  
    }  
}  
public class test {  
    public static void main(String[] args) {  
        Card c1 = new Card(1, "♠");  
        Card c2 = new Card(2, "♠");  
        Card c3 = c1;  
        System.out.println(c1 > c2); // 编译报错  
        System.out.println(c1 == c2); // 编译成功 ----> 打印false,因为c1和c2指向的是不同对象  
        System.out.println(c1 < c2); // 编译报错  
        System.out.println(c1 == c3); // 编译成功 ----> 打印true,因为c1和c3指向的是同一个对象  
    }  
}

解释

1.为什么这里会报错?System.out.println(c1 > c2);

①Java 里不允许直接用 >< 比较自定义对象 (比如写的 Card 类),只有基本数据类型int/double/char 等)才能用这些运算符比较大小。

Card 是引用类型,Java 不知道你想比较什么,所以直接编译报错。

2.== 为什么不报错?

== 比较的是引用地址(两个变量是否指向同一个对象),不是对象内容,所以对所有引用类型都可用:

  • c1 == c2false,因为它们是两个不同的 new 出来的对象
  • c1 == c3true,因为 c3 = c1,指向同一个对象

3. 对象的比较

3.1 覆写equals

复制代码
class Card {  
    public int rank; // 数值  
    public String suit; // 花色  
    public Card(int rank, String suit) {  
        this.rank = rank;  
        this.suit = suit;  
    }  
  
    public boolean equals(Object o) {  
        // 1. 自己和自己比较(同一个对象,直接返回true)  
        if (this == o) {  
            return true;  
        }  
  
        // 2. 如果o是null,或者不是Card类/子类,直接返回false  
        if (o == null || !(o instanceof Card)) {  
            return false;  
        }  
  
        // 3. 把o强转为Card类型,才能访问Card的成员变量  
        Card c = (Card) o;  
  
        // 4. 比较核心属性:  
        //    - rank是int,直接用==比较  
        //    - suit是String,引用类型,必须用equals()比较内容  
        return rank == c.rank && suit.equals(c.suit);  
    }  
}
public class test {  
        public static void main(String[] args) {  
            Card c1 = new Card(1, "♠");  
            Card c2 = new Card(1, "♠");  
            System.out.println(c1 == c2);    // false(不同对象)  
            System.out.println(c1.equals(c2)); // true(内容相同)  
        }  
    }

💡 补充:为什么要重写 equals()

  • 默认 equals()(来自 Object 类)和 == 效果一样,只比较引用地址
  • 重写后,我们可以自定义:只要两个 Cardranksuit 都相同,就认为它们是 "相等" 的
  • :equal只能按照相等进行比较,不能按照大于、小于的方式进行比较。

3.2 基于Comparble接口类的比较

Comparble是JDK提供的泛型的比较接口类。

复制代码
public interface Comparable<E> {
	// 返回值:
	// < 0: 表示 this 指向的对象小于 o 指向的对象
	// == 0: 表示 this 指向的对象等于 o 指向的对象
	// > 0: 表示 this 指向的对象大于 o 指向的对象
	int compareTo(E o);
}

如果要想按照大小与方式进行比较时:在定义类时,实现Comparble接口即可,然后在类
中重写compareTo方法。

复制代码
class Card implements Comparable<Card> {  
    public int rank; // 数值  
    public String suit; // 花色  
    public Card(int rank, String suit) {  
        this.rank = rank;  
        this.suit = suit;  
    }  
    // 根据数值比较,不管花色  
    // 这里我们认为 null 是最小的  
    @Override  
    public int compareTo(Card o) {  
        if (o == null) {  
            return 1;  
        }  
        return rank - o.rank;  
    }  
}  
    public class test {  
        public static void main(String[] args) {  
            Card p = new Card(1, "♠");  
            Card q = new Card(2, "♠");  
            Card o = new Card(1, "♠");  
            System.out.println(p.compareTo(o)); // == 0,表示牌相等  
            System.out.println(p.compareTo(q)); // < 0,表示 p 比较小  
            System.out.println(q.compareTo(p)); // > 0,表示 q 比较大  
        }  
    }

Compareble是java.lang中的接口类,可以直接使用。

3.3 基于比较器比较

用户自定义比较器类,实现Comparator接口

复制代码
public interface Comparator<T> {
	// 返回值:
	// < 0: 表示 o1 指向的对象小于 o2 指向的对象
	// == 0: 表示 o1 指向的对象等于 o2 指向的对象
	// > 0: 表示 o1 指向的对象等于 o2 指向的对象
	int compare(T o1, T o2);
}

注意:区分Comparable和Comparator。

复制代码
import java.util.Comparator;  
//Comparator是java.util 包中的泛型接口类,使用时必须导入对应的包

  
class Card {  
    public int rank; // 数值  
    public String suit; // 花色  
    public Card(int rank, String suit) {  
        this.rank = rank;  
        this.suit = suit;  
    }  
}  
class CardComparator implements Comparator<Card> {  
    // 根据数值比较,不管花色  
    // 这里我们认为 null 是最小的  
  
    public int compare(Card o1, Card o2) {  
        if (o1 == o2) {  
            return 0;  
        }  
        if (o1 == null) {  
            return -1;  
        }  
        if (o2 == null) {  
            return 1;  
        }  
        return o1.rank - o2.rank;  
    }  
    public static void main(String[] args){  
        Card p = new Card(1, "♠");  
        Card q = new Card(2, "♠");  
        Card o = new Card(1, "♠");  
        // 定义比较器对象  
        CardComparator cmptor = new CardComparator();  
        // 使用比较器对象进行比较  
        System.out.println(cmptor.compare(p, o)); // == 0,表示牌相等  
        System.out.println(cmptor.compare(p, q)); // < 0,表示 p 比较小  
        System.out.println(cmptor.compare(q, p)); // > 0,表示 q 比较大  
    }  
}

3.4 三种方式对比

覆写的方法 说明
Object.equals 因为所有类都是继承自 Object 的,所以直接覆写即可,不过只能比较相等与否
Comparable.compareTo 需要手动实现接口,侵入性比较强,但一旦实现,每次用该类都有顺序,属于内部顺序
Comparator.compare 需要实现一个比较器对象,对待比较类的侵入性弱,但对算法代码实现侵入性强

💡:小技巧

  • compareTo自己 vs 别人
  • comparea vs b

4. 集合框架中PriorityQueue的比较方式

集合框架中的PriorityQueue底层使用堆结构,因此其内部的元素必须要能够比大小PriorityQueue采用了:Comparble和Comparator两种方式。

  1. Comparble是默认的内部比较方式,如果用户插入自定义类型对象时,该类对象必须要实现Comparble接口,并覆写compareTo方法
  2. 用户也可以选择使用比较器对象,如果用户插入自定义类型对象时,必须要提供一个比较器类,让该类实现Comparator接口并覆写compare方法。

5. 使用PriorityQueue创建大小堆,解决TOPK问题

复制代码
import java.util.Arrays;  
import java.util.Comparator;  
import java.util.PriorityQueue;  
  
//使用比较器创建小根堆  
class LessIntComp implements Comparator<Integer> {  
  
    public int compare(Integer o1, Integer o2) {  
        return o1 - o2;  
    }  
}  
//使用比较器创建大根堆  
class GreaterIntComp implements Comparator<Integer>{  
  
    public int compare(Integer o1, Integer o2) {  
        return o2 - o1;  
    }  
}  
public class test<E> {  
    //求最小的K个数,通过比较器创建大根堆  
    public static int[] smallestK(int[] array, int k) {  
        if(k <= 0) {  
            return new int[k];  
        }  
        GreaterIntComp greaterCmp = new GreaterIntComp();  
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(greaterCmp);  
        //先将前K个元素,创建大根堆  
        for(int i = 0; i < k; i++) {  
            maxHeap.offer(array[i]);  
        }  
        //从第K+1个元素开始,每次和堆顶元素比较  
        for (int i = k; i < array.length; i++) {  
            int top = maxHeap.peek();  
            if(array[i] < top) {  
                maxHeap.poll();  
                maxHeap.offer(array[i]);  
            }  
        }  
        //取出前K个  
        int[] ret = new int[k];  
        for (int i = 0; i < k; i++) {  
            int val = maxHeap.poll();  
            ret[i] = val;  
        }  
        return ret;  
    }  
    public static void main(String[] args) {  
        int[] array = {4,1,9,2,8,0,7,3,6,5};  
        int[] ret = smallestK(array,3);  
        System.out.println(Arrays.toString(ret));  
    }  
}

💡一句话记住 Top-K 万能规则

  • 求最小 K 个 → 用大根堆
  • 求最大 K 个 → 用小根堆
相关推荐
山川行2 小时前
关于《项目C语言》专栏的总结
c语言·开发语言·数据结构·vscode·python·算法·visual studio code
呜喵王阿尔萨斯2 小时前
C and C++ code
c语言·开发语言·c++
武超杰2 小时前
Spring Boot入门教程
java·spring boot·后端
左左右右左右摇晃2 小时前
JDK 1.7 ConcurrentHashMap——分段锁
java·开发语言·笔记
是小蟹呀^2 小时前
Java抽象类详解:从入门到精通
java·抽象类
xcLeigh2 小时前
Python入门:Python3基础练习题详解,从入门到熟练的 25 个实例(六)
开发语言·python·教程·python3·练习题
IT 行者2 小时前
Spring Boot 集成 JavaMail 163邮箱配置详解
java·spring boot·后端
lzhdim2 小时前
SQL 入门 7:SQL 聚合与分组:函数、GROUP BY 与 ROLLUP
java·服务器·数据库·sql·mysql
弹简特2 小时前
【JavaEE】Mybatis实现分页查询功能
java·java-ee·mybatis