为什么优先级队列中的密钥是不可变的?

时间:2014-04-09 14:27:02

标签: java priority-queue

我正在研究Robert Sedgewick在coursea.org上的算法。他提到密钥对于优先级队列应该是不可变的?为什么呢?

3 个答案:

答案 0 :(得分:2)

这是简单的例子

public static void main(String[] args)
    {
        Comparator<AtomicInteger> comparator = new AtomicIntegerComparater();
        PriorityQueue<AtomicInteger> queue =
                new PriorityQueue<AtomicInteger>(10, comparator);
        AtomicInteger lessInteger = new AtomicInteger(10);
        AtomicInteger middleInteger = new AtomicInteger(20);
        AtomicInteger maxInteger = new AtomicInteger(30);
        queue.add(lessInteger);
        queue.add(middleInteger);
        queue.add(maxInteger);
        while (queue.size() != 0)
        {
            System.out.println(queue.remove());
        }



        queue.add(lessInteger);
        queue.add(middleInteger);
        queue.add(maxInteger);

        lessInteger.addAndGet(30);
        while (queue.size() != 0)
        {
            System.out.println(queue.remove());
        }
    }
    }



class AtomicIntegerComparater implements Comparator<AtomicInteger>
{
    @Override
    public int compare(AtomicInteger x, AtomicInteger y)
    {

        if (x.get() < y.get())
        {
            return -1;
        }
        if (x.get() > y.get())
        {
            return 1;
        }
        return 0;
    }
}

您将获得类似

的输出
10
20
30


40
20
30

注意在第二次删除时,它首先删除40。但期望是它应该删除最后。因为它添加时它有10个,这被认为是第一个元素。

但是,如果您将另一个元素添加到同一个队列,它将正确地重新排序。

queue.add(lessInteger);
        queue.add(middleInteger);
        queue.add(maxInteger);
        lessInteger.addAndGet(30);
        queue.add(new AtomicInteger(5));
        while (queue.size() != 0)
        {
            System.out.println(queue.remove());
        }

将导致

5
20
30
40

检查PriortyQueue的siftUpUsingComparator方法。

private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }

适用于其他收藏品吗?

那取决于收集,实施。 例如:TreeSet属于同一类别。它只是保留/使用比较器而不插入迭代。

TreeSet<AtomicInteger> treeSets = new TreeSet<AtomicInteger>(comparator);
        lessInteger.set(10);
        treeSets.add(middleInteger);
        treeSets.add(lessInteger);
        treeSets.add(maxInteger);
        lessInteger.addAndGet(30);
        for (Iterator<AtomicInteger> iterator = treeSets.iterator(); iterator.hasNext();) {
            AtomicInteger atomicInteger = iterator.next();
            System.out.println(atomicInteger);
        }

会导致

40
20
30

除此之外没有。

答案 1 :(得分:1)

PriorityQueue的键(条目)应该是不可变的原因是PriorityQueue无法检测到这些键的更改。例如,当您插入具有特定优先级的密钥时,它将被放置在队列中的某个位置。 (实际上,在后台实现中,它更像是一个“树”,但这并不重要)。现在修改此对象时,其优先级可能会更改。但是它不会改变它在队列中的位置,因为队列不会知道该对象被修改了。然后,此对象在队列中的放置可能只是错误的,并且队列将变得不一致(即,以错误的顺序返回对象)。

请注意,对象并非严格必须完全不可变。重要的是,可能不会修改影响其优先级的对象。修改优先级计算中涉及的 not 对象的字段是完全可行的。但必须小心,因为更改是否影响优先级可能会或可能不会在相应条目的类中明确指定。

答案 2 :(得分:1)

这是一个简单的示例,显示它是如何中断的 - 第一个队列按预期顺序打印数字,但第二个队列没有,因为其中一个数字在添加到之后已经突变队列中。

public static void main(String[] args) throws Exception {
    PriorityQueue<Integer> ok = new PriorityQueue<>(Arrays.asList(1, 2, 3));
    Integer i = null;
    while ((i = ok.poll()) != null) System.out.println(i); //1,2,3

    PriorityQueue<AtomicInteger> notOk = new PriorityQueue<>(Comparator.comparing(AtomicInteger::intValue));
    AtomicInteger one = new AtomicInteger(1);
    notOk.add(one);
    notOk.add(new AtomicInteger(2));
    notOk.add(new AtomicInteger(3));
    one.set(7);
    AtomicInteger ai = null;
    while ((ai = notOk.poll()) != null) System.out.println(ai); //7,2,3
}