C#利用斐波那契堆实现优先队列

        继上一章C#利用二叉堆实现优先队列之后,我们继续来探究一下关于优先队列的另一个实现 —— 斐波那契堆(Fibonacci Heap)。

        相比起二叉堆中规中矩的实现而言,斐波那契堆的设计显得更为大胆而精妙。在这里,我们只讨论使用最频繁的两个操作:插入与移除,而不考虑优先级变更与关键字搜索等功能,因为这两个功能对我而言绝不常用,而且会使我们的结构变得复杂,所以我任性地作出了取舍。

        从算法复杂度的角度分析,斐波那契堆的插入操作只需花费O(1),而移除操作也能在O(logN)内完成。单从这点看来,斐波那契堆是绝对优于二叉堆的(插入与移除都需花费O(logN))。其实不然,在实际的环境中,宏观地看,斐波那契堆的实现更为复杂,因为二叉堆可以基于一片连续的数组内存实现,而斐波那契堆却对每个元素动态创建结点,所以说斐波那契堆的内存管理上会不如二叉堆高效;其次,对于移除操作,虽然双方的复杂度都标注了O(logN),但其底数却不一样,准确地讲,二叉堆的移除复杂度是以2为底的O(logN),而斐波那契堆的移除复杂度是以黄金分割率(约为1.618)为底的O(logN),同时,斐波那契堆的移除操作还需要额外为前面的每个插入操作分摊O(1)的复杂度,所以,斐波那契堆的移除算法销毁是要略高于二叉堆的。综合上述的优劣,在实际的测试中(10万次增减),我发现两者的耗时基本持平,严格地说,斐波那契堆的性能可能高出一点点,但确实拉不开差距。

        微观地看,在某种应用场景下,斐波那契堆要远优于二叉堆。举个例子,在C/S架构下,有一台服务器,它需要并发处理大量客户端的请求,具体的处理方式是把客户端的请求转化成带有优先级的任务数据,放入一个全局的优先队列中,等待后台的工作线程处理,同时向客户端返回“任务投递成功”的信息。等到后台的工作线程处理完该任务后,再通过某种机制通知客户端,并推送详细的任务结果。在这种模式中,服务器的前台线程要处理大量的客户端请求,所以对于性能延时是极为敏感的,而后台工作线程却没太大所谓,可以慢悠悠地一个个处理优先队列中的任务。那么,此时斐波那契堆能提供的O(1)内的任务项添加操作就显得极为重要,极大地提高了服务器的请求并发数,同时,后台工作线程使用的移除操作又不会太慢,完美契合了我们的需求。

        当然,说到服务器使用的数据结构,势必要谈及可并行操作数据结构,势必要涉及到细粒度锁和无锁的设计,这种编程复杂度已经超出了本文的范畴,本文不会详述,有兴趣的读者可自行百度。我自己本人也很热衷于研究多线程下的数据结构,但从行业发展来讲,目前并行数据结构领域还不成熟(尤其是基于CAS的无锁模式),资料较少,难度较高,更难以找到权威的教材。想要学习只能通过看别人的源码。

        言归正传,我们下面提供斐波那契堆的实现方案:

/*******************************************************************
* 版权所有: 深圳市震有科技有限公司
* 文件名称: FibonacciHeap.cs
* 作  者: 李垄华
* 创建日期: 2018-03-21 14:28:05
* 文件版本: 1.0.0.0
* 修改时间:             修改人:                修改内容:
*******************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;

namespace LWLCX.Common.Collections.Generics
{
    /// <summary>
    ///     斐波那契堆(最大堆)
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    /// <remarks>
    ///     斐波那契堆数据结构有两种用途。第一种,它支持一系列操作,这些操作构成了所谓的“可合并堆”。
    ///     第二种,斐波那契堆的一些操作可以在常数摊还时间内完成,这使得这种数据结构非常适合于需要频繁
    ///     调用这些操作的应用。
    /// </remarks>
    [DebuggerDisplay("<Count={" + nameof(Count) + "};>")]
    public class FibonacciHeap<TKey, TValue> : IReadOnlyCollection<KeyValuePair<TKey, TValue>>, ICollection
    {
        #region Node

        // 元素存储结点
        [DebuggerDisplay("<Key={Key}; Value={Value}; Degree={Degree}>")]
        private class Node
        {
            #region Constructors

            // 初始化一个结点
            public Node(TKey key, TValue value)
            {
                Key = key;
                Value = value;
                m_firstChild = null;
                Previous = m_right = this;
            }

            #endregion

            #region Fields

            // 指向某一个孩子.
            // Node的所有孩子被链接成一个环形的双向链表, 称为Node的孩子链表.
            private Node m_firstChild;

            // 分别指向当前Node的左右兄弟结点.
            // 如果当前Node是仅有的一个孩子结点, 则m_left = m_right = this;
            private Node m_right;

            // 孩子结点数量

            #endregion

            #region Properties

            public TKey Key { get; }

            public TValue Value { get; }

            // 下一个兄弟结点
            public Node Next => m_right;

            // 前一个兄弟结点
            public Node Previous { get; private set; }

            // 孩子结点
            public Node FirstChild
            {
                get
                {
                    Contract.Ensures(Contract.Result<Node>() != null);
                    return m_firstChild;
                }
            }

            // 孩子结点数量
            public int Degree { get; private set; }

            // 是否存在兄弟结点
            public bool HasSilbing => m_right != this;

            // 是否存在孩子结点
            public bool HasChild => m_firstChild != null;

            #endregion

            #region Methods

            // 链接一系列结点作为自己的兄弟
            public void LinkToSibling(Node node)
            {
                m_right.Previous = node;
          
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值