英文:
If the comparator uses some other variables, when is the variable consumed by the comparator?
问题
我下面有一个代码示例,基本上,我想要获取数组中值从小到大的索引。
它按预期工作,但当我开始添加一些逻辑来改变input
数组的值时,poll()
方法并不总是返回正确的索引。
但我不知道为什么会发生这种情况。
public static void main(String[] args) {
Solution s = new Solution();
int[][] input = new int[][]{
{8,6,8,1,4,1},
{10,3,1,8,9,10},
{1,5,6,9,8,5},
{10,4,6,7,3,3},
{6,6,9,1,3,3},
{3,1,10,3,4,1},
{6,1,6,10,7,10}
};
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(o -> input[o[0]][o[1]]));
for(int i = 0; i < input.length; i++) {
for (int j = 0; j < input[0].length; j++) {
pq.add(new int[]{i, j});
}
}
while(!pq.isEmpty()) {
int[] h = pq.poll();
// 如果我添加一些改变input数组的操作,poll()方法不总是返回最小值的索引,为什么?
System.out.println(h[0] + " " + h[1] + " " + input[h[0]][h[1]]);
}
}
英文:
I have a code sample below, basically, I want to get the index of the array whose value is from low to high in the input
It works as expected but when I start to add some logic to change the value of input
, the poll()
method not always returns me the right index.
But I have no idea how this happens
public static void main(String[] args) {
Solution s = new Solution();
int[][] input = new int[][]{
{8,6,8,1,4,1},
{10,3,1,8,9,10},
{1,5,6,9,8,5},
{10,4,6,7,3,3},
{6,6,9,1,3,3},
{3,1,10,3,4,1},
{6,1,6,10,7,10}
};
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(o -> input[o[0]][o[1]]));
for(int i = 0; i < input.length; i++) {
for (int j = 0; j < input[0].length; j++) {
pq.add(new int[]{i, j});
}
}
while(!pq.isEmpty()) {
int[] h = pq.poll();
// if I add something to change the input, the poll() method not always returns the index of min val, why?
System.out.println(h[0] + " " + h[1] + " " + input[h[0]][h[1]]);
}
}
答案1
得分: 2
比较器在向 PriorityQueue 添加元素以及从中移除元素之后被调用。比较器用于对支撑队列的最小堆进行排序。
具体来说,当你添加元素后会调用 PriorityQueue#siftUp
,而在移除元素时会调用 PriorityQueue#siftDown
,但仅在移除队列的第一个元素后才会调用。这两种方法都使用了比较器。
考虑这个简单的例子:
public static void main(String[] args) {
int[] a = {1};
int[] b = {2};
int[] c = {3};
int[] d = {4};
int[][] input = new int[][] {a,b,c,d};
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
pq.addAll(Arrays.asList(input));
d[0] = -1;
while (!pq.isEmpty()) {
int[] h = pq.poll();
System.out.println(Arrays.toString(h));
}
}
最初添加 a、b、c、d 时,队列经过向上筛选操作部分有序,以至少保证队列头为 {1}
。然后将元素 d
改变为 -1。
毫无疑问,在进行变异时不会调用比较器。
在循环内,当你第一次调用 poll 时,会移除头元素 a = 1
,在移除堆的头部后,队列会使用比较器重新排序。
接下来要移除的元素是 d = -1
。因此,在我简化的示例中,移除的打印顺序是 1,-1,2,3
,这证明了比较器在向队列添加元素后以及从队列中移除元素后被调用,仅此而已。
我强调一下“后面”这个词,因为如果在从队列中移除之前调用比较器,那么在上述示例中我们会得到不同的结果(-1,1,2,3,而不是1,-1,2,3)。
毫无疑问,我在这里给出的示例是为什么在向 PriorityQueue 中添加元素后不应该对其进行变异的一个很好的理由。
英文:
The comparator is called after you add and after you remove from a PriorityQueue. Comparisons are made to sort the min-heap that backs the queue.
More specifically, PriorityQueue#siftUp
is called when after you add, and PriorityQueue#siftDown
is called when you remove, but only after the 0-th element of the queue is removed. Both of these methods use the comparator.
Consider this simple example:
public static void main(String[] args) {
int[] a = {1};
int[] b = {2};
int[] c = {3};
int[] d = {4};
int[][] input = new int[][] {a,b,c,d};
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
pq.addAll(Arrays.asList(input));
d[0] = -1;
while (!pq.isEmpty()) {
int[] h = pq.poll();
System.out.println(Arrays.toString(h));
}
}
Initially when a,b,c,d are added, the queue is partially ordered from the sift up operations so that at least the head of the queue is guaranteed to be {1}
. Then element d
is mutated to be -1.
It goes without saying that the comparator is not called during the mutation.
Inside the loop, when you call the poll for the first time, you remove the head element, a = 1
, and the queue is re-sorted after the head of the heap is removed, using the comparator.
The next element to be removed is d = -1
. Hence the print order of removals in my simplified example is 1,-1,2,3
, which demonstrates that the comparator is called after you add to the queue and after you remove from the queue, and that is all.
I place the emphasis on the word after because if it were called before you removed from the queue, we would have gotten a different result in the above example (-1,1,2,3 instead of 1,-1,2,3)
It probably also goes without saying that the example I'm giving here is a good reason why you should not mutate the elements in a PriorityQueue after they've been added.
答案2
得分: -1
总结一下,您基本上是在创建一个指向可变整数的已排序队列的 'addresses'(元组)。
一个 'address'(元组)将在队列方法中插入,但之后不会再插入。
如果您更改了引用的整数,没有任何东西会告诉已排序集合重新排序。实际上,您正在损坏集合,因为优先级队列的内部会因为通常希望其状态按顺序排序以正确插入而感到困惑。
PQ 使用 'siftup/siftdown',基本上是将项目向上或向下冒泡。如果由于后门变化而不再排序队列,这种筛选将过早结束,导致集合损坏。
英文:
To sum up, you are basically creating a sorted queue of 'addresses' (tuples) pointing to mutable ints.
One 'address' (tuple) will be inserted during queue methods but not afterwards.
If you change the referenced ints, nothing is telling the sorted collection to re-sort. You are effectively corrupting the collection, because the internals of the priority queue will get confused as it would normally expect its state to be sorted in order to insert in proper spot.
The PQ uses 'siftup/siftdown' which basically bubbles up or down the item. If the queue is no longer sorted because of backdoor mutations, this sifting will prematurely end and leave the collection corrupted.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论