为什么不能使用for循环遍历Queue

ε祈祈猫儿з 提交于 2021-02-01 15:07:54

为什么不能使用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

img

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写于九江,加油!

公众号

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!