【Java 数据结构】-优先级队列以及Java对象的比较

作者:学Java的冬瓜
博客主页:☀冬瓜的主页🌙
专栏【Java 数据结构】
分享:美妙人生的关键在于你能迷上什么东西。——《球状闪电》
主要内容优先级队列底层的堆,大堆的创建,插入,删除。堆的算法的时间复杂度。Java中的对象的比较。

在这里插入图片描述
在这里插入图片描述

一、模拟实现优先级队列

JDK1.8中,PriorityQueue(优先级队列)底层使用了堆的数据结构。

1、什么是堆?

堆的两个特性:

  • 结构性存储上用数组,逻辑上是一棵完全二叉树
  • 有序性:任意节点的关键字是其子树所有节点的最大值(或最小值)

分类:

  • 大顶堆(或最大堆):要求树中所有父亲节点都大于等于孩子节点,根最大
  • 小顶堆(或最小堆):要求树中所有父亲节点都小于等于孩子节点,根最小

2、代码

2.1、建堆(大堆示例)

建堆有两种方式:
第一种是从无到有(相当于第二种的插入元素,每次插入都调整为堆)
第二种是给一个数组,直接把它调整为堆。以下方法就是第二种。

public class MyHeap {
	// 1、堆的基本框架
    private int elem[];
    private int usedSize;
    private static final int DEFAULT_SIZE=11; // 注意:PriorityQueue源码中就是默认先开11个空间
    // 利用构造方法初始化
    public MyHeap() {
        elem = new int[DEFAULT_SIZE];
    }
    // 2、把数组放进堆数组
    public void initMyHeap(int[] arr){
        for(int i = 0; i < arr.length; i++){
            elem[i] = arr[i];
            usedSize++;
        }
    }
	// 3、建堆,以parent为根节点的树依次调整为大堆
    public void createHeap(){
        for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {  // 注意:数组下标到0
            shiftDown(parent, usedSize);
        }
    }

    // 向下调整算法
    private void shiftDown(int parent, int len){
        int child = parent*2+1;
        while(child < len){  // 注意:len-1为堆数组中最后一个元素的下标
            if(child+1 < len && elem[child] < elem[child+1]){// 左右孩子都满足下标在范围内。
                child++;// 找到以parent为根节点的左右孩子中大的
            }
			
            if(elem[child] > elem[parent]){
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
				
				// 满足交换条件,就交换,然后继续往下判断,是否符合条件
                parent = child;
                child = parent*2+1;
            }else{
            	// 不满足交换条件,就说明已经满足大堆的要求,直接break
                break;
            }
        }
    }

2.2、堆的插入

    public int offer(int data){
    	// 1、插入元素到堆中
        if(heapIsFull()){
            grow();
        }
        elem[usedSize] = data;
        usedSize++;
		
		// 2、把最后一个元素通过向上调整放在对应位置
        shiftUp(usedSize-1); // 注意:usedSize-1才是data元素的下标

        return data;
    }
    // 向上调整算法
    private void shiftUp(int child){
        int parent = (child-1)/2;
        while(parent >= 0){
        	// 把孩子节点和父亲节点比较,孩子大就交换
            if(elem[child] > elem[parent]){
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;

                child = parent;
                parent = (child-1)/2;
            }else {
                break;
            }
        }
    }
    // 判断堆满
    private boolean heapIsFull(){
        return this.usedSize == this.elem.length;
    }
    // 扩容
    private void grow(){
        elem = Arrays.copyOf(this.elem, this.usedSize*2);
    }

2.3、堆的删除

	// 删除队头元素
    public int poll(){
        if(heapIsEmpty()){
            return -1;
        }
		// 把堆顶和
        int tmp = elem[0];
        elem[0] = elem[usedSize-1];
        elem[usedSize-1] = tmp;
        usedSize--;

        shiftDown(0,usedSize);

        return elem[usedSize];
    }
    // 堆空
    private boolean heapIsEmpty(){
        return this.usedSize == 0;
    }

2.4、获取堆顶元素

// 获取队头(堆顶)元素
    public int peek(){
        return elem[0];
    }
}

3、小练习

在这里插入图片描述

4、堆的总结

  • 建堆,有两种方法:法一是不断插入数据,不断调整为堆,法二是根据一个数组,直接调整为堆。下面都为法二的总结。
  • 建大堆:因为要求每棵子树都为大堆,1> 所以可以从调整最后一个节点的父节点为根节点的树,开始往上调整其它树。2> 每棵子树的调整都用向下调整算法:即左右孩子中大的节点比根节点还大,就把两者交换。
  • 堆的插入:1> 把新增元素先放在堆的完全二叉树的最后。 2> 使用向上调整算法,把堆里最后一个元素调整到正确的位置。
  • 堆的删除:1> 把堆顶元素和最后一个元素交换,然后堆的有效元素个数-1,2> 把堆顶元素使用向下调整算法放到正确位置。
  • 向下调整算法:建堆、删除堆顶元素都需要用到。要判断左右孩子谁大,大的再和父亲节点交换。然后再parent = child,child = parent*2+1继续往子树判断是否满足大堆,不满足则一直交换,直到child=有效元素个数(越界)。
  • 向上调整算法:插入元素用到向上调整算法,只是插入的这个child和它的parent比较,不满足大堆交换,直到child>0结束(或者parent>=0结束),不需要左右孩子比较。

二、算法的复杂度分析

1、调整算法复杂度

log(n)
1> 从代码上来看,向上调整算法和向下调整算法,都是孩子节点和父亲节点之间的交换,所以复杂度应该是树的高度
2> 又因为堆是完全二叉树,所以调整算法的复杂度(最坏)为log(n)

2、建堆的时间复杂度(重点)

在这里插入图片描述
在这里插入图片描述

三、PriorityQueue关于比较的分析

1、关于PriorityQueue的分析

  • 当没有传入数组容量的时候,默认数组大小是11.
  • 扩容时,当oldCapacity<64,就约为2倍扩容,当oldCapacity>=64,就为1.5倍扩容。
  • 当没有传入比较器的时候,你放进PriorityQueue里面的必须是可比较的(比如整形,或者比如Student类实现Compareable接口,重写compareTo方法)
  • 在PriorityQueue中整形数默认是小堆排序,如果想要实现大堆,可以使用比较器。不能实现Compareable重写compareTo方法,因为我不能改变Integer类的源码。

2、关于比较的分析

法一:用equals方法

equals方法只能判断两个元素是否相等,或者地址是否相等

法二:实现Compareable接口

让类实现Compareable接口,然后重写compareTo方法,在比较时,程序自动调用compareTo方法

public class Student implements Comparable<Student>{
    private String name;
    private int age;
    public Student(){
        
    }

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

法三:比较器

<1> 制造比较器类
//构造:让构造器类实现Comparator接口,重写compare方法
class AgeCmp implements Comparator<Student>{
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age; // 为了方便,这里的Student类的age字段,我没有封装
    }
}

//使用(main中):
PriorityQueue<Student> priorityQueue = new PriorityQueue<>(new AgeCmp());
<2> 利用内部类完成比较器
		//在main中
		// 利用内部类给构造器进行构造
		PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });

法四:lambda表达式(JDK1.8开始出现)

PriorityQueue<Integer> priorityQueue = new PriorityQueue<>((x,y)->{return x-y;});
//或者
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>((x,y)->x.compareTo(y));
// 或者
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Integer::compareTo);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学Java的冬瓜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值