继上一章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,请在评论区指出。