在教室里.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相关问答推荐

避免函数和其他对象之间的相互递归的模式?

在 Rx 中,处理线程安全是否是消费者(IObserver)的责任?

为什么这个多态 C# 代码会打印它的功能?

在生产中使用实体框架(代码优先)迁移

.NET 的 Visual Studio 调试器提示和技巧

如何摆脱 VS2008 中的目标程序集不包含服务类型错误消息?

为什么 .Contains 慢?通过主键获取多个实体的最有效方法?

File.ReadAllLines() 和 File.ReadAllText() 有什么区别?

如何以编程方式 Select ListView 中的项目?

如何在 .NET 中将字符串转换为字节数组?

如何等到远程 .NET 调试器附加

如何从字符串中删除所有字母字符?

C# 相当于 Java 的 <?在泛型中扩展 Base>

如何授予所有用户对我的应用程序创建的文件的完全权限?

清除 .NET 的 StringBuilder 内容的最佳方法

在不使用while循环的情况下找到最里面的异常?

如何从 HashSet 中检索实际项目?

WPF中的依赖属性和附加属性有什么区别?

.NET Remoting 真的被弃用了吗?

嵌套捕获组如何在正则表达式中编号?