在最小產生樹Prim演算法中,可以利用最小優先順序隊列來改善時間複雜度,同時在單源最短路徑Dijkstra演算法中也同樣可以利用這種最小優先順序隊列來改善演算法時間複雜度。實現最小優先順序隊列可以有很多種方式,比如基於二叉最小堆,或者斐波那契堆等。這裡是二叉最小堆的C#實現,原理是根據書上的虛擬碼來的,但有些地方我做了改進,比如書key值改變,原來書上只能變大,這裡取掉了這個限制。同時還提供了根據衛星值來選擇元素的功能,下面是代碼:
/// <summary> /// 隊列元素封裝類 /// </summary> /// <typeparam name="T">實際元素類型</typeparam> public class QueueElement<T> { /// <summary> /// Key值 /// </summary> public int KeyValue { get; internal set; } /// <summary> /// 實際對象 /// </summary> public T Element { get; private set; } public QueueElement(T Item, int KeyVal) { KeyValue = KeyVal; Element = Item; } } /// <summary> /// 最小優先順序隊列 /// </summary> /// <typeparam name="T"></typeparam> public class MinHeapQueue<T> { /// <summary> /// 隊列元素存放,採用List實現. /// </summary> private List<QueueElement<T>> _queueValues = new List<QueueElement<T>>(); /// <summary> /// 隊列元素數目 /// </summary> public int Count { get { return _queueValues.Count; } } /// <summary> /// 擷取隊列Key值最小的元素 /// </summary> /// <returns></returns> public T GetMinimum() { return _queueValues[0].Element; } /// <summary> /// 從隊列中取出Key值最小的元素 /// </summary> /// <returns></returns> public T ExtractMin() { if (_queueValues.Count <= 0) { throw new Exception("隊列為空白"); } T theMin = _queueValues[0].Element; int theTail = Count - 1; _queueValues[0] = _queueValues[theTail]; _queueValues.RemoveAt(theTail); MinHeapify(0); return theMin; } /// <summary> /// 整理堆元素,保持最小堆特性,這個函數跟DownAdjust功能相同 /// </summary> /// <param name="i"></param> public void MinHeapify(int i) { int HeapSize = Count; int theL = HeapL(i); int theR = HeapR(i); int theLeast = i; if (theL < HeapSize && _queueValues[theL].KeyValue < _queueValues[theLeast].KeyValue) { theLeast = theL; } if (theR < HeapSize && _queueValues[theR].KeyValue < _queueValues[theLeast].KeyValue) { theLeast = theR; } if (theLeast != i) { SwapElement(i, theLeast); MinHeapify(theLeast); } } /// <summary> /// 改變元素key值 /// </summary> /// <param name="SelectFunc"></param> /// <param name="NewKey"></param> public void ChangeKey(Func<T, bool> SelectFunc, int NewKey) { int theIndex = -1; for (int i = 0; i < Count; i++) { if (SelectFunc(_queueValues[i].Element) == true) { theIndex = i; break; } } if (theIndex < 0) { return; } if (_queueValues[theIndex].KeyValue < NewKey) { _queueValues[theIndex].KeyValue = NewKey; DownAdjust(theIndex); return; } if (_queueValues[theIndex].KeyValue > NewKey) { _queueValues[theIndex].KeyValue = NewKey; UpAdjust(theIndex); return; } } /// <summary> /// 沿樹根方向整理元素,保持最小堆特性 /// </summary> /// <param name="i"></param> private void UpAdjust(int i) { int theIndex = i; int thePIndex = HeapP(theIndex); while (thePIndex >= 0 && _queueValues[theIndex].KeyValue < _queueValues[thePIndex].KeyValue) { SwapElement(thePIndex, theIndex); theIndex = thePIndex; thePIndex = HeapP(theIndex); } } /// <summary> /// 沿樹葉方向整理元素,保持最小堆特性 /// </summary> /// <param name="i"></param> private void DownAdjust(int i) { int HeapSize = Count; int theL = HeapL(i); int theR = HeapR(i); int theLeast = i; if (theL < HeapSize && _queueValues[theL].KeyValue < _queueValues[theLeast].KeyValue) { theLeast = theL; } if (theR < HeapSize && _queueValues[theR].KeyValue < _queueValues[theLeast].KeyValue) { theLeast = theR; } if (theLeast != i) { SwapElement(i, theLeast); DownAdjust(theLeast); } } /// <summary> /// 改變元素key值 /// </summary> /// <param name="i"></param> /// <param name="NewKey"></param> public void ChangeKey(int i, int NewKey) { int theIndex = i; if (_queueValues[theIndex].KeyValue > NewKey) { _queueValues[theIndex].KeyValue = NewKey; UpAdjust(theIndex); return; } if (_queueValues[theIndex].KeyValue < NewKey) { _queueValues[theIndex].KeyValue = NewKey; DownAdjust(theIndex); return; } } /// <summary> /// 刪除隊列元素 /// </summary> /// <param name="SelectFunc"></param> public void HeapDelete(Func<T, bool> SelectFunc) { int theIndex = -1; for (int i = 0; i < Count; i++) { if (SelectFunc(_queueValues[i].Element) == true) { theIndex = i; break; } } if (theIndex < 0) { return; } SwapElement(theIndex, Count - 1); _queueValues.RemoveAt(Count - 1); if (theIndex < Count) { int theP = HeapP(theIndex); bool theUp = false; if (theP >= 0) { if (_queueValues[theIndex].KeyValue < _queueValues[theP].KeyValue) { UpAdjust(theIndex); theUp = true; } } if (theUp == false) { MinHeapify(theIndex); } } } /// <summary> /// 隊列元素交換位置 /// </summary> /// <param name="i"></param> /// <param name="j"></param> private void SwapElement(int i, int j) { QueueElement<T> theTmp = _queueValues[i]; _queueValues[i] = _queueValues[j]; _queueValues[j] = theTmp; } /// <summary> /// 將元素插入隊列 /// </summary> /// <param name="Element"></param> /// <param name="Key"></param> public void HeapInsert(T Element, int Key) { _queueValues.Add(new QueueElement<T>(Element, int.MinValue)); ChangeKey(Count - 1, Key); } /// <summary> /// 取節點的左孩子節點 /// </summary> /// <param name="i"></param> /// <returns></returns> private int HeapL(int i) { return i * 2 + 1; } /// <summary> /// 取節點的右孩子節點 /// </summary> /// <param name="i"></param> /// <returns></returns> private int HeapR(int i) { return i * 2 + 2; } /// <summary> /// 取節點的父節點 /// </summary> /// <param name="i"></param> /// <returns></returns> private int HeapP(int i) { return (i + 1) / 2 - 1; } }
需要注意的,我前面有篇基於二叉最大堆的優先順序隊列演算法跟這篇很類似,但上篇演算法中有小錯誤,而這篇演算法中的優先順序隊列已通過測試,沒問題。大家可對比一下,看錯誤在哪裡。
Ps:既然到首頁,還是加點注釋.