本文介绍了Microsoft内部的PriorityQueue< T>中的错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在PresentationCore.dll的.NET Framework中,有一个通用的 PriorityQueue< T> 类可以在此处.

In the .NET Framework in PresentationCore.dll, there is a generic PriorityQueue<T> class whose code can be found here.

我写了一个简短的程序来测试排序,结果并不理想:

I wrote a short program to test the sorting, and the results weren't great:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using MS.Internal;

namespace ConsoleTest {
    public static class ConsoleTest {
        public static void Main() {
            PriorityQueue<int> values = new PriorityQueue<int>(6, Comparer<int>.Default);
            Random random = new Random(88);
            for (int i = 0; i < 6; i++)
                values.Push(random.Next(0, 10000000));
            int lastValue = int.MinValue;
            int temp;
            while (values.Count != 0) {
                temp = values.Top;
                values.Pop();
                if (temp >= lastValue)
                    lastValue = temp;
                else
                    Console.WriteLine("found sorting error");
                Console.WriteLine(temp);
            }
            Console.ReadLine();
        }
    }
}

结果:

2789658
3411390
4618917
6996709
found sorting error
6381637
9367782

存在排序错误,如果样本数量增加,排序错误的数量就会成比例地增加.

There is a sorting error, and if the sample size is increased, the number of sorting errors increases somewhat proportionally.

我做错了什么吗?如果没有, PriorityQueue 类的代码中的错误到底在哪里?

Have I done something wrong? If not, where is the bug in the code of the PriorityQueue class located exactly?

推荐答案

可以使用初始化矢量 [0、1、2、4、5、3] 重现此行为.结果是:

The behavior can be reproduced using the initialization vector [0, 1, 2, 4, 5, 3]. The result is:

(我们可以看到3放置不正确)

(we can see that 3 is incorrectly placed)

Push 算法正确.它以一种直接的方式建立了最小堆:

The Push algorithm is correct. It builds a min-heap in a straightforward way:

  • 从右下角开始
  • 如果该值大于父节点,则将其插入并返回
  • 否则,将父项放到右下角的位置,然后尝试在父项位置插入值(并不断交换树直到找到正确的位置)

生成的树是:

                 0
               /   \
              /     \
             1       2
           /  \     /
          4    5   3

问题出在 Pop 方法上.首先将顶部节点视为要填充的间隙"(因为我们将其弹出):

The issue is with the Pop method. It starts by considering the top node as a "gap" to fill (since we popped it):

                 *
               /   \
              /     \
             1       2
           /  \     /
          4    5   3

要填充它,它将搜索最低的直接子对象(在这种情况下为1).然后,它将值上移以填补空白(现在孩子是新的空白):

To fill it, it searches for the lowest immediate child (in this case: 1). It then moves the value up to fill the gap (and the child is now the new gap):

                 1
               /   \
              /     \
             *       2
           /  \     /
          4    5   3

然后它对新的间隙执行完全相同的操作,因此间隙再次向下移动:

It then does the exact same thing with the new gap, so the gap moves down again:

                 1
               /   \
              /     \
             4       2
           /  \     /
          *    5   3

当间隙达到最低点时,算法...将树的最右下角的值用于填充间隙:

When the gap has reached the bottom, the algorithm... takes the bottom-rightmost value of the tree and uses it to fill the gap:

                 1
               /   \
              /     \
             4       2
           /  \     /
          3    5   *

现在,该间隙位于最右下角的节点上,它会减小 _count 以便从树中删除该间隙:

Now that the gap is at the bottom-rightmost node, it decrements _count to remove the gap from the tree:

                 1
               /   \
              /     \
             4       2
           /  \     
          3    5   

最后,我们得到了一个破碎的堆.

And we end up with... A broken heap.

老实说,我不了解作者要做什么,所以我无法修复现有代码.最多,我可以将其替换为工作版本(从 Wikipedia 中无耻复制):

To be perfectly honest, I don't understand what the author was trying to do, so I can't fix the existing code. At most, I can swap it with a working version (shamelessly copied from Wikipedia):

internal void Pop2()
{
    if (_count > 0)
    {
        _count--;
        _heap[0] = _heap[_count];

        Heapify(0);
    }
}

internal void Heapify(int i)
{
    int left = (2 * i) + 1;
    int right = left + 1;
    int smallest = i;

    if (left <= _count && _comparer.Compare(_heap[left], _heap[smallest]) < 0)
    {
        smallest = left;
    }

    if (right <= _count && _comparer.Compare(_heap[right], _heap[smallest]) < 0)
    {
        smallest = right;
    }

    if (smallest != i)
    {
        var pivot = _heap[i];
        _heap[i] = _heap[smallest];
        _heap[smallest] = pivot;

        Heapify(smallest);
    }
}

该代码的主要问题是递归实现,如果元素数量太大,该实现将中断.我强烈建议您改用经过优化的第三方库.

Main issue with that code is the recursive implementation, which will break if the number of elements is too large. I strongly recommend using an optimized thirdparty library instead.

我想我发现了所缺少的内容.在取得了最右下角的节点之后,作者只是忘记了重新平衡堆:

I think I found out what is missing. After taking the bottom-rightmost node, the author just forgot to rebalance the heap:

internal void Pop()
{
    Debug.Assert(_count != 0);

    if (_count > 1)
    {
        // Loop invariants:
        //
        //  1.  parent is the index of a gap in the logical tree
        //  2.  leftChild is
        //      (a) the index of parent's left child if it has one, or
        //      (b) a value >= _count if parent is a leaf node
        //
        int parent = 0;
        int leftChild = HeapLeftChild(parent);

        while (leftChild < _count)
        {
            int rightChild = HeapRightFromLeft(leftChild);
            int bestChild =
                (rightChild < _count && _comparer.Compare(_heap[rightChild], _heap[leftChild]) < 0) ?
                    rightChild : leftChild;

            // Promote bestChild to fill the gap left by parent.
            _heap[parent] = _heap[bestChild];

            // Restore invariants, i.e., let parent point to the gap.
            parent = bestChild;
            leftChild = HeapLeftChild(parent);
        }

        // Fill the last gap by moving the last (i.e., bottom-rightmost) node.
        _heap[parent] = _heap[_count - 1];

        // FIX: Rebalance the heap
        int index = parent;
        var value = _heap[parent];

        while (index > 0)
        {
            int parentIndex = HeapParent(index);
            if (_comparer.Compare(value, _heap[parentIndex]) < 0)
            {
                // value is a better match than the parent node so exchange
                // places to preserve the "heap" property.
                var pivot = _heap[index];
                _heap[index] = _heap[parentIndex];
                _heap[parentIndex] = pivot;
                index = parentIndex;
            }
            else
            {
                // Heap is balanced
                break;
            }
        }
    }

    _count--;
}

这篇关于Microsoft内部的PriorityQueue&lt; T&gt;中的错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-19 01:43