java-PriorityQueue中poll()的疑惑和小结

详细PriorityQueue信息请看这篇博文:csdn-JAVA中PRIORITYQUEUE详解,本文只描述我在使用时遇到的对poll()的疑惑和小结

一.问题来源

 java库函数中有个PriorityQueue是使用动态数组来实现最小堆的(默认最小堆,也可以在初始化时传入自己制定的Comparator来把它变成最大堆)。
 实现堆排序的算法中,有一种算法需要用到堆的"删除"方法.前面说了PriorityQueue是使用动态数组来实现最小堆的,那删除堆顶就表示堆变小了,那是不是动态数组也变小了?
 众所周知数组一旦初始化了,那它的长度是不可改变的。只有新建一个数组然后把原元素复制到一个新数组,才能实现"数组长度的改变"。 如果动态数组会变小,就表示每次删除根节点,就要新建一个数组,算法的时间复杂度和空间复杂度会大大上升
 所以我很好奇PriorityQueue是怎么实现的,查了下API文档,有了以下的总结。如果出错请在评论区留言讨论,谢谢



二、初始化

我们先来看PriorityQueue的构造函数


//一个常量,当你调用构造函数时没有传入数组的长度,则这个就是新建数组的默认长度
 private static final int DEFAULT_INITIAL_CAPACITY = 11;

//PriorityQueue中就是维护这个数组,用来存放最小堆
 transient Object[] queue;

//当前堆中的元素个数,初始值为0;这个有什么作用呢?后面就会知道
private int size = 0;

//这其实就是传入一个数字,数字作为上面那个queue数组的初始长度
//再传入一个比较器,排序时就按照这个比较器中写的来排序
//如果比较器为null,将使用元素的自然排序(数字的自然排序就是从小到大)
 public PriorityQueue(int initialCapacity,Comparator<?super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

//无参构造函数,会传入默认的数组数值11(看上面的第一个属性,它默认是11)
//就是说什么都不传入,初始维护的数组大小为11
public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }


三、删除堆顶poll()

//把E看成是Integer就行了(当然他们并不相同,只不过好理解点)
//E是数组内的元素的数据类型——还记得吗,PriorityQueue维护了一个数组来存放堆
public E poll() {
		//size是当前堆的元素个数,上面定义了
		//size=0表示当前堆没元素,就没有堆顶
        if (size == 0)
            return null;

		//删除堆顶元素,那整个堆的元素数量就减1了
		//其实这里好像都没必要新建个整数存新的size了,
		//因为size是个全局变量,一改全改。我猜可能是s短一点,好写
        int s = --size;

		//modCount是堆的修改次数(数组的修改次数),不用管,这是和线程安全有关的属性,这里不用管
        modCount++;

		//数组的第一个元素是堆的堆顶,poll()要返回堆顶的,所以我们要取出来
        E result = (E) queue[0];

		//这里涉及堆的删除知识,不懂可以去查查
		//假如一个数组的长度为10,
		//那这个数组的最后一个元素的下标是什么?是9;是(10-1)
		//所以可以理解queue[s]是当前堆(还没开始删除)的最后一个元素
		//因为s = size-1;
        E x = (E) queue[s];
        //把最后一个元素置为null
        queue[s] = null;
        
        //把最后一个元素(我们在置为空之前把它取出来了)放到数组的第一个位置
        //进行堆的排序,堆的排序只在这个数组内进行,不会额外添加新的数组
        if (s != 0)
            siftDown(0, x);
        return result;
    }




1.假如有个初始数组

01234
13526

2.进行堆排序(最小堆)后

01234
12536

3.poll()掉堆顶
(1)首先:把堆的最后一个元素拿出来临时存放,然后把它置为空

01234
1253null
在数组外开辟个临时空间存放它的值
6

(2)然后,把最后那个元素的值赋给数组的第一个元素(堆顶元素)

01234
6253null

(3)最后,调整数组元素来满足堆的定义

01234
2356null



四、代码验证

public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
		//我们给一个初始数组,等下加进去
		int[] array = {1,3,5,2,6,2,7,9,10,11,1,2,34,5,6,7};
		
		//新建一个PriorityQueue,没输入数组长度,默认11
		PriorityQueue<Integer> heap = new PriorityQueue<Integer>();
		
		//通过反射获取queue数组
		Class c = heap.getClass();
		Field f = c.getDeclaredField("queue");
		f.setAccessible(true);
		Object[] len =  (Object[]) f.get(heap);
		System.out.println("没添加元素时,初始数组长度: " + len.length);
		
		
		//把我们给出的数组的元素加进去,我们的数组中有16个元素
		//不用管数组增长算法,感兴趣的话可以自己到api文档去看
		for(int e : array){
			heap.add(e);
		}
		len =  (Object[]) f.get(heap); //要重新取数组的值
		System.out.println("添加16个元素后,数组长度: " + len.length);
		
		//删除堆顶
		heap.poll();
		len =  (Object[]) f.get(heap); //要重新取数组的值
		System.out.println("删除1个元素后,数组长度: " + len.length);
		
		//多次删除堆顶
		int i = 6;
		while(i-- > 0) heap.poll();
		len =  (Object[]) f.get(heap); //要重新取数组的值
		System.out.println("再删除6个元素后,数组长度: " + len.length);
		
		
	}

在这里插入图片描述



五、结论:

1.可以看到删除栈顶元素后,原数组并不会变小,而是把堆的末尾元素赋给数组的第一个元素(堆顶),然后末尾元素置为null,剩余元素再调整顺序来满足堆的定义
2.所以全局变量size就有大作用了,size表示堆的元素数量,就可以用来
定位堆的最后一个元素在数组中的位置,来确定堆在数组中的位置


3.突然又发现PriorityQueue不允许插入null,但是它本身是可以存null甚至里面许多空间存的就是null

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值