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
{
    /// 
    ///     斐波那契堆(最大堆)
    /// 
    /// 
    /// 
    /// 
    ///     斐波那契堆数据结构有两种用途。第一种,它支持一系列操作,这些操作构成了所谓的“可合并堆”。
    ///     第二种,斐波那契堆的一些操作可以在常数摊还时间内完成,这使得这种数据结构非常适合于需要频繁
    ///     调用这些操作的应用。
    /// 
    [DebuggerDisplay("")]
    public class FibonacciHeap : IReadOnlyCollection>, ICollection
    {
        #region Node

        // 元素存储结点
        [DebuggerDisplay("")]
        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() != 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;
                node.m_right.Previous = this;
                FibonacciHeapHelper.Swap(ref m_right, ref node.m_right);
            }

            // 链接一系列结点作为自己的孩子
            public void LinkToChild(Node node, int count)
            {
                if (!HasChild)
                {
                    m_firstChild = node;
                    Degree = count;
                }
                else
                {
                    m_firstChild.LinkToSibling(node);
                    Degree += count;
                }
            }

            // 使当前结点退出兄弟链, 返回兄弟链中现有的一个元素
            public Node QuitFromSibling()
            {
                if (!HasSilbing)
                {
                    return null;
                }

                var next = m_right;
                m_right.Previous = Previous;
                Previous.m_right = m_right;
                m_right = Previous = this;
                return next;
            }

            // 移除所有子结点
            public Node ClearAllChild(out int count)
            {
                var child = m_firstChild;
                count = Degree;
                m_firstChild = null;
                Degree = 0;
                return child;
            }

            // 基于现有数据构建一条链表, 返回链表中的最优结点, 以及元素数量
            public static Node Create(IComparer comparer, IEnumerable> dataLists,
                out int count)
            {
                using (var dataSet = dataLists.GetEnumerator())
                {
                    if (!dataSet.MoveNext())
                    {
                        count = 0;
                        return null;
                    }

                    var cur = dataSet.Current;
                    Node pMax = new Node(cur.Key, cur.Value);
                    int cnt = 1;

                    while (dataSet.MoveNext())
                    {
                        cur = dataSet.Current;
                        var pNode = new Node(cur.Key, cur.Value);
                        pMax.LinkToSibling(pNode);
                        if (comparer.Compare(pNode.Key, pMax.Key) > 0)
                        {
                            pMax = pNode;
                        }

                        ++cnt;
                    }

                    count = cnt;
                    return pMax;
                }
            }

            #endregion
        }

        #endregion

        #region Enumerator

        /// 
        ///     此枚举器用于遍历斐波那契堆中的所有元素(非有序遍历)
        /// 
        public struct Enumerator : IEnumerator>
        {
            private readonly Queue m_queue;

            private readonly FibonacciHeap m_heap;

            /*
             * 在枚举器遍历的过程中, 我们不允许m_heap对象被外部更改,
             * 所以我们需要通过m_version来进行版本号校对
             */
            private readonly ulong m_version;
            private Node m_current;
            private Node m_startNode;

            public Enumerator(FibonacciHeap heap)
            {
                if (heap == null)
                {
                    throw new ArgumentNullException(nameof(heap));
                }

                m_version = heap.m_version;
                m_heap = heap;
                m_queue = new Queue();
                m_current = null;
                m_startNode = null;
                if (m_heap.m_maxNode != null)
                {
                    m_queue.Enqueue(m_heap.m_maxNode);
                }
            }

            private void CheckVersion()
            {
                if (m_heap.m_version != m_version)
                {
                    throw new InvalidOperationException("The heap has been changed.");
                }
            }

            public void Dispose()
            {
                m_current = m_startNode = null;
                m_queue.Clear();
            }

            public bool MoveNext()
            {
                CheckVersion();
                if (m_current == null)
                {
                    if (m_queue.Count == 0)
                    {
                        return false;
                    }

                    m_startNode = m_current = m_queue.Dequeue();
                }
                else
                {
                    if (m_current.HasChild)
                    {
                        m_queue.Enqueue(m_current.FirstChild);
                    }

                    m_current = m_current.Next;
                    if (m_current == m_startNode)
                    {
                        m_current = null;
                        if (m_queue.Count == 0)
                        {
                            return false;
                        }

                        m_startNode = m_current = m_queue.Dequeue();
                    }
                }

                return true;
            }

            public void Reset()
            {
                CheckVersion();
                m_current = null;
                m_startNode = null;
                m_queue.Clear();
                if (m_heap.m_maxNode != null)
                {
                    m_queue.Enqueue(m_heap.m_maxNode);
                }
            }

            public KeyValuePair Current => new KeyValuePair(m_current.Key, m_current.Value);

            object IEnumerator.Current => Current;
        }

        #endregion

        #region Fields

        /* 指向具有最大关键字的树的根结点.
         * - 如果不止一个根结点具有最大关键字,
         *   那么这些根结点中的任何一个都有可能成功最大结点.
         * - 如果堆是空的, 这m_maxNode = null;
         */
        private Node m_maxNode;

        // 当前堆中的结点数目
        private int m_count;

        // 当前堆算法使用的关键字比较器.
        private readonly IComparer m_comparer;

        // 内部算法使用的缓存数组
        private readonly Node[] m_dnBuffer;

        // 操作版本号, 每对此集合执行一次修改性操作, 将递增此版本号.
        // 此版本号主要使用来保证Enumerator的遍历结构正确性
        private ulong m_version;

        #endregion

        #region Constructors

        /// 
        ///     创建一个空的斐波那契堆, 使用默认的比较器
        /// 
        public FibonacciHeap()
            : this(Comparer.Default)
        {
        }

        /// 
        ///     创建一个空的斐波那契堆, 使用指定的比较器
        /// 
        /// 
        public FibonacciHeap(IComparer comparer)
        {
            m_comparer = comparer ?? Comparer.Default;
            m_maxNode = null;
            m_count = 0;
            m_dnBuffer = new Node[FibonacciHeapHelper._maxBufferSize];
        }

        /// 
        ///     创建一个具有初始值的斐波那契堆, 使用默认的比较器
        /// 
        /// 
        public FibonacciHeap(IEnumerable> initialData)
            : this(Comparer.Default, initialData)
        {
        }

        /// 
        ///     创建一个具有初始值的斐波那契堆, 使用指定的比较器
        /// 
        /// 
        /// 
        public FibonacciHeap(IComparer comparer, IEnumerable> initialData)
            : this(comparer)
        {
            if (initialData == null)
            {
                return;
            }

            m_maxNode = Node.Create(m_comparer, initialData, out m_count);
        }

        #endregion

        #region Private Methods

        /// 
        ///     向堆中的根链区插入新结点链
        /// 
        /// 结点链中的最优先结点
        /// 结点数量
        private void HeapInsert(Node node, int count = 1)
        {
            Contract.Assert(node != null);

            if (m_maxNode == null)
            {
                Debug.Assert(m_count == 0);
                m_maxNode = node;
            }
            else
            {
                m_maxNode.LinkToSibling(node);
                if (m_comparer.Compare(node.Key, m_maxNode.Key) > 0)
                {
                    m_maxNode = node;
                }
            }

            m_count += count;
        }

        /// 
        ///     合并一个堆到当前堆
        /// 
        /// 
        ///     该操作将同时清空的原有数据
        /// 
        private void HeapUnion(FibonacciHeap otherHeap)
        {
            Contract.Assert(otherHeap != null);
            Contract.Assert(m_comparer.Equals(otherHeap.m_comparer));

            if (otherHeap.m_maxNode == null)
            {
                return;
            }

            if (m_maxNode == null)
            {
                FibonacciHeapHelper.Swap(ref m_maxNode, ref otherHeap.m_maxNode);
                FibonacciHeapHelper.Swap(ref m_count, ref otherHeap.m_count);
            }
            else
            {
                m_maxNode.LinkToSibling(otherHeap.m_maxNode);
                m_count += otherHeap.m_count;
                otherHeap.m_maxNode = null;
                otherHeap.m_count = 0;
            }
        }

        /// 
        ///     从堆中移除最优先结点
        /// 
        private KeyValuePair ExtractMax()
        {
            Contract.Assert(m_maxNode != null);

            var max = m_maxNode;

            // 把最优堆的所有子结点移动到根链区
            var childs = m_maxNode.ClearAllChild(out int cnt);

            if (cnt > 0)
            {
                m_maxNode.LinkToSibling(childs);
            }

            // 此时最优堆已经没有任何子结点, 可以从根链区中移除
            m_maxNode = max.QuitFromSibling();
            --m_count;
            if (m_maxNode != null && m_maxNode.HasSilbing)
            {
                // 合并根链区且确认下一个最优堆
                Consolidate();
            }

            return new KeyValuePair(max.Key, max.Value);
        }

        /// 
        ///     调整堆中的根链区, 在根链区中合并度数相同的堆, 使得根链区的各个堆具有不同的degree.
        /// 
        private void Consolidate()
        {
            Contract.Assert(m_maxNode.HasSilbing);

            // buffer数组用作算法缓存, 其表达意义如下:
            // 若buffer[i] == Node, 则表示Node.Degree == i;
            var buffer = m_dnBuffer;
            
            var lastNode = m_maxNode.Previous;
            var currentNode = m_maxNode;
            bool bContinue;
            // 遍历根链区中的所有堆
            do
            {
                var nextNode = currentNode.Next;
                bContinue = currentNode != lastNode;

                var x = currentNode; // 当前正在处理的堆
                int d = x.Degree; // 堆的度数(即子结点数量)

                // 查找是否有缓存过与当前堆具有相同度数的堆
                while (buffer[d] != null)
                {
                    var y = buffer[d]; // 堆y与堆x具有相同的度数
                    buffer[d] = null;

                    // 谁大谁当父结点
                    if (m_comparer.Compare(y.Key, x.Key) > 0)
                    {
                        FibonacciHeapHelper.Swap(ref x, ref y);
                    }

                    // 合并X和Y
                    Link(x, y);

                    // 由于合并了Y, 所以此时X的度数必然加一
                    ++d;
                }

                /* 1. 堆X已经无法继续合并, 那么则进入缓存区, 等待被合并.
                 * 2. 把x赋值到m_maxNode并不是因为x真的是当前的最优堆, 而是因为在堆合并的过程中,
                 *    m_maxNode指向的堆有可能被移除合并为其他堆的子堆, 我们要保证m_maxNode始终
                 *    指向一个位于根链区的堆顶点.
                 */
                m_maxNode = buffer[d] = x;
                currentNode = nextNode;
            } while (bContinue);

            //Contract.Assert(buffer[m_maxNode.Degree] == m_maxNode);
            buffer[m_maxNode.Degree] = null;

            /*
             * 现在, 所有根链区中的堆皆已合并, 但m_maxNode指向的并不一定是最优堆, 所以,
             * 我们需要在根链区的结点中做一次遍历, 寻找最优的堆顶点.
             */
            if (!m_maxNode.HasSilbing)
            {
                // 如果根链区中只剩一个堆, 则无需再调整.
                return;
            }

            var pNode = m_maxNode.Next;
            var maxNode = m_maxNode;

            do
            {
                if (m_comparer.Compare(pNode.Key, maxNode.Key) > 0)
                {
                    maxNode = pNode;
                }
                //Contract.Assert(buffer[pNode.Degree] == pNode);
                buffer[pNode.Degree] = null;
                pNode = pNode.Next;
            } while (pNode != m_maxNode);

            m_maxNode = maxNode;
        }

        /// 
        ///     合并根链区中的两个兄弟堆, 使其变为父子堆
        /// 
        /// 
        /// 
        private void Link(Node asParent, Node asChild)
        {
            asChild.QuitFromSibling();
            asParent.LinkToChild(asChild, 1);
        }

        #endregion

        #region Properties

        /// 
        ///     获取一个值, 指示当前集合是否为空.
        /// 
        public bool IsEmpty => m_maxNode == null;

        /// 
        ///     获取集合中元素的数量
        /// 
        public int Count => m_count;

        // 隐藏
        object ICollection.SyncRoot => throw new NotSupportedException();

        // 隐藏
        bool ICollection.IsSynchronized => false;

        #endregion

        #region Public Methods

        /// 
        ///     向集合中添加新项
        /// 
        /// 
        /// 被添加的元素
        /// 算法复杂度: O(1)
        public void Push(TKey key, TValue value)
        {
            ++m_version;
            HeapInsert(new Node(key, value));
        }

        /// 
        ///     向集合中添加一组新项
        /// 
        /// 算法复杂度: O(N)
        public void PushRange(IEnumerable> data)
        {
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            var node = Node.Create(m_comparer, data, out var cnt);
            ++m_version;
            HeapInsert(node, cnt);
        }

        /// 
        ///     从集合中移除并返回最高优先的元素
        /// 
        /// 最高优先的元素
        /// 算法复杂度: O(logN)
        public KeyValuePair Pop()
        {
            if (m_maxNode == null)
            {
                throw new InvalidOperationException("The heap is empty.");
            }

            ++m_version;
            return ExtractMax();
        }

        /// 
        ///     从集合中提取但不移除最高优先的元素
        /// 
        /// 最高优先的元素
        /// 算法复杂度: O(1)
        public KeyValuePair Peek()
        {
            if (m_maxNode == null)
            {
                throw new InvalidOperationException("The heap is empty.");
            }

            return new KeyValuePair(m_maxNode.Key, m_maxNode.Value);
        }

        /// 
        ///     清空此集合
        /// 
        /// 算法复杂度: O(1)
        public void Clear()
        {
            ++m_version;
            m_maxNode = null;
            m_count = 0;
        }

        /// 
        ///     把当前集合中的元素复制到. 但并不保证其优先级顺序性.
        /// 
        /// 指定的目标数组
        /// 内的可用起始下标
        /// 算法复杂度: O(n)
        public void CopyTo(Array array, int index)
        {
            if (array == null)
            {
                throw new ArgumentNullException(nameof(array));
            }

            if (index < 0 || index >= array.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }

            if (array.Length - index < Count)
            {
                throw new ArgumentException("数组长度不足", nameof(array));
            }

            if (Count < 1)
            {
                return;
            }

            var queue = new Queue();
            queue.Enqueue(m_maxNode);

            do
            {
                var start = queue.Dequeue();
                var current = start;

                do
                {
                    if (current.HasChild)
                    {
                        queue.Enqueue(current.FirstChild);
                    }

                    array.SetValue(new KeyValuePair(current.Key, current.Value), index++);
                    current = current.Next;
                } while (current != start);
            } while (queue.Count > 0);
        }

        /// 
        ///     把当前堆中的元素合并到
        /// 
        /// 算法复杂度: O(1)
        /// 
        public void MoveTo(FibonacciHeap heap)
        {
            if (heap == null)
            {
                throw new ArgumentNullException(nameof(heap));
            }

            if (!heap.m_comparer.Equals(m_comparer))
            {
                throw new ArgumentException("The specified heap does not use same comparer as current heap.");
            }

            ++m_version;
            heap.HeapUnion(this);
        }

        /// 
        ///     获取枚举器
        /// 
        public IEnumerator> GetEnumerator()
        {
            return new Enumerator(this);
        }

        /// 
        ///     获取枚举器
        /// 
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }

    /// 
    ///     帮助类
    /// 
    internal static class FibonacciHeapHelper
    {
        public static readonly int _maxBufferSize = (int) Math.Ceiling(Math.Log(int.MaxValue, 1.618));

        /// 
        ///     交换俩元素
        /// 
        /// 
        /// 
        public static void Swap(ref T a, ref T b)
        {
            var c = a;
            a = b;
            b = c;
        }
    }
}
如果你发现或测出了什么Bug,请在评论区指出。

你可能感兴趣的:(C#,优先队列,斐波那契堆)