在教室里.NET框架在PresentationCore中.dll中,有一个泛型PriorityQueue<T>类,其代码可以在here中找到.

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

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

存在排序错误,如果样本量增加,排序错误的数量会按一定比例增加.

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

推荐答案

可以使用初始化向量[0, 1, 2, 4, 5, 3]再现该行为.结果是:

[0,1,2,4,3,5]

(我们可以看到3放错了)

Push算法是正确的.它以一种简单的方式构建最小堆:

  • 从右下角开始
  • 如果该值大于父 node ,则插入它并返回
  • 否则,将父对象放在右下角,然后try 在父对象位置插入值(并继续向上交换树,直到找到正确的位置)

生成的树是:

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

问题在于Pop方法.首先,将顶部 node 视为要填补的"空白"(因为我们弹出了它):

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

为了填充它,它搜索最低的直接子对象(在本例中为:1).然后,它向上移动值以填充缺口(现在,子级就是新的缺口):

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

然后它会对新的间隙做完全相同的事情,所以间隙再次向下移动:

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

当差距达到底部时,算法...获取树的最右下角的值,并使用它来填补空白:

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

现在间隙位于最右下角的 node ,它将递减_count以从树中移除间隙:

                 1
               /   \
              /     \
             4       2
           /  \     
          3    5   

我们最终...破堆.

说实话,我不明白作者想做什么,所以我无法修复现有的代码.最多,我可以用一个工作版本(无耻地抄袭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);
    }
}

该代码的主要问题是递归实现,如果元素数量过多,递归实现就会中断.我强烈建议使用优化的第三方库.


编辑:我想我找到了缺失的东西.在获取最右下角的 node 后,作者忘记了重新平衡堆:

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--;
}

.net相关问答推荐

为什么在WinForm应用程序中创建组件类椭圆会在www.example.com中没有响应

是否有内置方法将 nuget 包引用为 csproj 中的文件?

.net Maui 在单击按钮时访问 CollectionView 中的数据项

如何判断属性设置器是否公开

如何为多种文件类型设置 FileSystemWatcher 过滤器?

图像 UriSource 和数据绑定

如何获得友好的操作系统版本名称?

如何以编程方式判断类型是 struct 还是类?

托管和非托管代码、内存和大小有什么区别?

获取 .NET Framework 目录路径

如何在不丢失数据的情况下重命名 Entity Framework 5 Code First 迁移中的数据库列?

在 C# 中与块等效?

如何异步 Files.ReadAllLines 并等待结果?

返回 IList 是否比返回 T[] 或 List 更糟糕?

如何允许程序集(单元测试)访问另一个程序集的内部属性?

dotnet 恢复警告 NU1701

等效于 C# 在 powershell 中的使用关键字?

为什么会出现编译错误使用未分配的局部变量?

C# - 在 WPF 应用程序中保存用户设置的方法?

.NET 图形库?