C#數(shù)據(jù)結(jié)構(gòu)之最小堆的實現(xiàn)方法
最小堆
基本思想:堆對應一棵完全二叉樹,且所有非葉結(jié)點的值均不大于(或不小于)其子女的值,根結(jié)點(堆頂元素)的值是最小(或最大)的,每次都取堆頂?shù)脑?,將其放在序列最后面,然后將剩余的元素重新調(diào)整為最?。ù螅┒?,依次類推,最終得到排序的序列。
堆排序分為大頂堆和小頂堆排序。大頂堆:堆對應一棵完全二叉樹,且所有非葉結(jié)點的值均不小于其子女的值,根結(jié)點(堆頂元素)的值是最大的。而小頂堆正好相反,小頂堆:堆對應一棵完全二叉樹,且所有非葉結(jié)點的值均不大于其子女的值,根結(jié)點(堆頂元素)的值是最小的。
舉個例子:
(a)大頂堆序列:(96, 83,27,38,11,09)
(b)小頂堆序列:(12,36,24,85,47,30,53,91)

實現(xiàn)堆排序需解決兩個問題:
1. 如何將n 個待排序的數(shù)建成堆?
2. 輸出堆頂元素后,怎樣調(diào)整剩余n-1 個元素,使其成為一個新堆?
首先討論第二個問題:輸出堆頂元素后,怎樣對剩余n-1元素重新建成堆?
調(diào)整小頂堆的方法:
1)設(shè)有m 個元素的堆,輸出堆頂元素后,剩下m-1 個元素。將堆底元素送入堆頂((最后一個元素與堆頂進行交換),堆被破壞,其原因僅是根結(jié)點不滿足堆的性質(zhì)。
2)將根結(jié)點與左、右子樹中較小元素的進行交換。
3)若與左子樹交換:如果左子樹堆被破壞,即左子樹的根結(jié)點不滿足堆的性質(zhì),則重復方法 (2).
4)若與右子樹交換,如果右子樹堆被破壞,即右子樹的根結(jié)點不滿足堆的性質(zhì)。則重復方法 (2).
5)繼續(xù)對不滿足堆性質(zhì)的子樹進行上述交換操作,直到葉子結(jié)點,堆被建成。
稱這個自根結(jié)點到葉子結(jié)點的調(diào)整過程為篩選。如圖:

再討論第一個問題,如何將n 個待排序元素初始建堆?
建堆方法:對初始序列建堆的過程,就是一個反復進行篩選的過程。
1)n 個結(jié)點的完全二叉樹,則最后一個結(jié)點是第n/2個結(jié)點的子樹。
2)篩選從第n/2個結(jié)點為根的子樹開始,該子樹成為堆。
3)之后向前依次對各結(jié)點為根的子樹進行篩選,使之成為堆,直到根結(jié)點。
如圖建堆初始過程:無序序列:(49,38,65,97,76,13,27,49)


C#算法實現(xiàn):
using System;
using System.Collections.Generic;
namespace StructScript
{
/// <summary>
/// 最小堆實現(xiàn)
/// </summary>
/// <typeparam name="T"></typeparam>
public class BinaryHeap<T>
{
//默認容量為6
private const int DEFAULT_CAPACITY = 6;
private int mCount;
private T[] mItems;
private Comparer<T> mComparer;
public BinaryHeap() : this(DEFAULT_CAPACITY) { }
public BinaryHeap(int capacity)
{
if (capacity < 0)
{
throw new IndexOutOfRangeException();
}
mItems = new T[capacity];
mComparer = Comparer<T>.Default;
}
/// <summary>
/// 增加元素到堆,并從后往前依次對各結(jié)點為根的子樹進行篩選,使之成為堆,直到根結(jié)點
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public bool Enqueue(T value)
{
if (mCount == mItems.Length)
{
ResizeItemStore(mItems.Length * 2);
}
mItems[mCount++] = value;
int position = BubbleUp(mCount - 1);
return (position == 0);
}
/// <summary>
/// 取出堆的最小值
/// </summary>
/// <returns></returns>
public T Dequeue()
{
return Dequeue(true);
}
private T Dequeue(bool shrink)
{
if (mCount == 0)
{
throw new InvalidOperationException();
}
T result = mItems[0];
if (mCount == 1)
{
mCount = 0;
mItems[0] = default(T);
}
else
{
--mCount;
//取序列最后的元素放在堆頂
mItems[0] = mItems[mCount];
mItems[mCount] = default(T);
// 維護堆的結(jié)構(gòu)
BubbleDown();
}
if (shrink)
{
ShrinkStore();
}
return result;
}
private void ShrinkStore()
{
// 如果容量不足一半以上,默認容量會下降。
if (mItems.Length > DEFAULT_CAPACITY && mCount < (mItems.Length >> 1))
{
int newSize = Math.Max(
DEFAULT_CAPACITY, (((mCount / DEFAULT_CAPACITY) + 1) * DEFAULT_CAPACITY));
ResizeItemStore(newSize);
}
}
private void ResizeItemStore(int newSize)
{
if (mCount < newSize || DEFAULT_CAPACITY <= newSize)
{
return;
}
T[] temp = new T[newSize];
Array.Copy(mItems, 0, temp, 0, mCount);
mItems = temp;
}
public void Clear()
{
mCount = 0;
mItems = new T[DEFAULT_CAPACITY];
}
/// <summary>
/// 從前往后依次對各結(jié)點為根的子樹進行篩選,使之成為堆,直到序列最后的節(jié)點
/// </summary>
private void BubbleDown()
{
int parent = 0;
int leftChild = (parent * 2) + 1;
while (leftChild < mCount)
{
// 找到子節(jié)點中較小的那個
int rightChild = leftChild + 1;
int bestChild = (rightChild < mCount && mComparer.Compare(mItems[rightChild], mItems[leftChild]) < 0) ?
rightChild : leftChild;
if (mComparer.Compare(mItems[bestChild], mItems[parent]) < 0)
{
// 如果子節(jié)點小于父節(jié)點, 交換子節(jié)點和父節(jié)點
T temp = mItems[parent];
mItems[parent] = mItems[bestChild];
mItems[bestChild] = temp;
parent = bestChild;
leftChild = (parent * 2) + 1;
}
else
{
break;
}
}
}
/// <summary>
/// 從后往前依次對各結(jié)點為根的子樹進行篩選,使之成為堆,直到根結(jié)點
/// </summary>
/// <param name="startIndex"></param>
/// <returns></returns>
private int BubbleUp(int startIndex)
{
while (startIndex > 0)
{
int parent = (startIndex - 1) / 2;
//如果子節(jié)點小于父節(jié)點,交換子節(jié)點和父節(jié)點
if (mComparer.Compare(mItems[startIndex], mItems[parent]) < 0)
{
T temp = mItems[startIndex];
mItems[startIndex] = mItems[parent];
mItems[parent] = temp;
}
else
{
break;
}
startIndex = parent;
}
return startIndex;
}
}
}
附上,測試用例:
using System;
namespace StructScript
{
public class TestBinaryHeap
{
static void Main(string[] args)
{
BinaryHeap<int> heap = new BinaryHeap<int>();
heap.Enqueue(8);
heap.Enqueue(2);
heap.Enqueue(3);
heap.Enqueue(1);
heap.Enqueue(5);
Console.WriteLine(heap.Dequeue());
Console.WriteLine(heap.Dequeue());
Console.ReadLine();
}
}
}
測試用例,執(zhí)行結(jié)果依次輸出1,2。
總結(jié)
到此這篇關(guān)于C#數(shù)據(jù)結(jié)構(gòu)之最小堆實現(xiàn)的文章就介紹到這了,更多相關(guān)C#數(shù)據(jù)結(jié)構(gòu)最小堆實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用序列化實現(xiàn)List<T> 實例的深復制(推薦)
下面小編就為大家?guī)硪黄眯蛄谢瘜崿F(xiàn)List<T> 實例的深復制(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02
c# Newtonsoft.Json 常用方法總結(jié)
這篇文章主要介紹了c# Newtonsoft.Json 常用方法的相關(guān)資料,幫助大家更好的理解和學習使用c#,感興趣的朋友可以了解下2021-02-02
c# DateTime常用操作實例(datetime計算時間差)
字符串操作DateTime操作,datetime計算時間差,取當前時間,更多方法看下面代碼2013-12-12

