为什么不能使用for循环遍历Queue
昨天有个朋友问我一道算法题,给出了他和答案的两个版本,这道题我看过,并查集问题,左看右看就是没发现它有问题,于是进行
debug
,发现数据读取没有问题,于是继续判断合并是否有误,发现也没有问题,最后发现他使用的PriorityQueue
他使用的for
循环进行遍历,好家伙,我当场好家伙!
1. 遍历
public static void main(String[] args) {
// 默认是小顶堆
PriorityQueue<Integer> queue = new PriorityQueue<>(Arrays.asList(1, 5, 3, 7, 8, 6, 9, 2));
for (Integer num : queue) {
System.out.print(num+" ");
}
}
上面的程序有不少人会以为输出的结果为1 2 3 5 6 7 8 9
,但是实际上呢,输出结果为:1 2 3 5 8 6 9 7
2. 原因
原因的话,学过堆排序的朋友就应该知道,在PriorityQueue
中实际上内部是维持着一棵二叉排序树,利用堆排序的规则实现的,具体的可以看PriorityQueue
的源码,实际上树是一种虚拟出来的数据结构,底层都是基于数组实现的。只是对于左右节点的访问采用了不同的下标规则。
当我们使用for
循环直接遍历PriorityQueue
的时候,此时是直接将底层的数组直接遍历出来了,但问题是队列是排序队列,数据是排序数据,但数组并非排序数组,所以使用PriorityQueue
的时候需要使用add()、poll()
等方法添加或者移除元素,这些方法在内部都会去维持二叉树的有序性,所以通过迭代器的方式可以获取到具体的排序数值。
堆排序
// 左孩子:2*i + 1
// 右孩子:2*i + 2
// 一个节点的父节点: (i-1)/2
public static void heapSort(int[] nums) {
if (nums == null || nums.length < 2) return;
for (int i = 0; i < nums.length; i++) {
heapInsert(nums, i);
}
int size = nums.length;
// 由于是大根堆,最大的数肯定是在第一个
// 交换第一个数
swap(nums, 0, --size);
// 大根堆的环境被破坏了,此时需要调整
while (size > 0) {
// 调整完以后,此时又恢复大根堆了,此时最大值又在0位置,除去排好的size那个位置
heapify(nums, 0, size);
// 交换,破坏大根堆
swap(nums, 0, --size);
}
}
private static void heapify(int[] nums, int index, int size) {
// 找到自己的左孩子
int left = 2 * index + 1;
// 循环遍历调整,确保不能越界,这里已经确保在size-1的地方了
while (left < size) {
// 寻找最大的下标 left+1表示右孩子节点,确保不能越界
int largest = ((left + 1) < size && nums[left + 1] > nums[left]) ? left + 1 : left;
// 如果自己就是最大的,停止循环
largest = nums[largest] > nums[index] ? largest : index;
if (largest == index) {
break;
}
// 否则进行一个调整,进行一个交换
swap(nums, largest, index);
index = largest;// 将index移动到这个最大值的地方,因为不能确保下面的值是否还有比自己大的
left = 2 * index + 1;// 利用新的index重新计算左孩子的坐标
}
}
private static void heapInsert(int[] nums, int index) {
// 查找到自己的父节点
// 要一直查找到比自己父节点小为止,如果大于自己的父节点,就需要交换
while (nums[index] > nums[(index - 1) / 2]) {
swap(nums, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
public static void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
3. 小结
这个
for
循环遍历queue
的问题就到这里了,自己也花了点时间去看代码的bug
,有些小的问题自己可能很难发现,因为代码写出来确实是符合一切的逻辑。有些时候逻辑正确,代码正确,但写代码的方式错误了,也会引发不小的问题。
Keep thinking, keep coding! 2021年2月1日13:09:39写于九江,加油!
来源:oschina
链接:https://my.oschina.net/u/4124720/blog/4940184