需要高效稳定的PriorityQueue实现

时间:2013-03-18 09:37:00

标签: c# .net algorithm priority-queue

对于最初使用SortedDictionary Eric Lippert's PriorityQueue implementation的棋盘游戏,我有A- *的实现,但我的棋盘尺寸的性能并不令人满意。

使用PriorityQueue的MinHeap实现,我在长对角线路径上获得了* 2加速(5-6秒到2-3秒);但后来我意识到它提供了一种不稳定的排序。对于这个应用程序,我需要稳定的排序。

是否有任何众所周知的PriorityQueue实现结合了MinHeap的效率,但是以SortedDictionary的方式提供稳定的排序?

更新:以下评论的其他详细信息:

  • 要求PriorityQueue操作:Enqueue(),Dequeue()&计数;
  • 在A- *的每次迭代中:IsEMpty& Dequeue()每次调用一次,Enqueue()调用6次;

解决:

虽然SortedDictionary<uint,Queue<TValue>>MinListHeap<KeyValuePair<TPriority,TValue>>的复杂性相同,但维护插入顺序的额外代码复杂性似乎是一个恒定的惩罚。这可能不是确定的,但我正在寻找在这个细化阶段的低调果实。

此外,当我更多地思考这个问题时,我意识到稳定只需要在短路径上,而不是在长路径上;因此,可以根据从开始到目标的距离的初始启发式估计,在进入FindPath()时选择所需的PriorityQueue实现。

1 个答案:

答案 0 :(得分:1)

这应该是一个评论,但它太长了。所以这里:

我找不到优先级队列的实现作为您建议的链接后的排序字典,但谷歌搜索表明此优先级队列是使用二叉搜索树实现的。 如果就是这种情况,看起来这些不是自平衡搜索树,因为我不明白如何用堆替换BST可以产生2倍的改进。自平衡BST可在insert()find()上为您提供有保证的log(n)性能。对于deque()(= removeMin())也是如此:它也应该是保证日志(n):你只需要离开,直到没有剩下的孩子。

好的,我们对二叉搜索树(BST)的每个操作都有log(n)。堆怎么样?好吧,对于您正在查看的操作,它是相同的:removeMin()(= dequeue())返回具有最高/最低键的节点(这是O(1)),但随后放置初始点中的最后一个节点,通过递归地将其与子节点进行比较,将其下沉。这需要在每个级别进行2次比较,并且通常涉及log(n)级别。所以 removeMin()是O(log(n))操作。那么insert()(= enqueue())怎么样?在这里,在通常的实现中,我们将新项目放在最后,然后通过与其父项进行比较将其浮动。在通常情况下,这需要0.5 * log(n)次操作,即O(log(n))。这意味着BST和堆应该在enqueue()和dequeue()上具有log(n)性能。并且这两个数据结构在isEmpty()上应具有O(1)性能。

这种推理如果正确,则表明以下原因之一:

  • BST实施中的常数因素使minHeap优先级队列中的常数因素相形见绌
  • 使用的BST不是自平衡的。

有人可能会认为0.5*log(n)上的insert()堆可能比BST insert()更好,但是,实际上BST插入实际上通常也只是0.5*log(n) ,我想,这不可能。常数因素似乎是合理的:如果 minHeap被实现为自动调整大小的数组(或者更好的是非调整大小的数组),那么minHeap可能不需要在enqueue上分配额外的存储空间, BST实施必须做的事情。

BST也可能不是自平衡的。在这种情况下,在最坏的情况下,dequeue()enqueue()将花费O(n)时间,具体取决于项目的添加顺序。所以这将是值得关注的事情。

编辑1 :常规BST实现的稳定方式是采用一种约定,即如果我们插入具有相同键的节点,则新节点成为(比方说)正确的子节点相同键控节点。这样,具有相同密钥的第一个节点将出列。对于自平衡BST,这可能需要更多的工作,因为旋转可能违反这个期望的不变量。在这种情况下,我想我会做什么(想到red-black trees,而不是AVL trees)是在旋转两个节点时A和BI将检查它们的键是否实际上是相同的,如果是,在保持节点结构的同时切换节点数据。但是对于这个问题可能有更好的解决方案。

编辑2 根据this acticle,SortedDictionary实际上是基于红黑树。这反过来暗示了实施中的一个错误(可疑但不是闻所未闻)或作为罪魁祸首的常数因素。