1. 名詞解釋:
二叉堆。什麼是二叉?顯而易見,即爲兩個分支。那麼剩下的就是堆這個詞,這個詞比較難以解釋,可以說成是一些數據的堆積,但是這些數據是有一定的規律的。連起來就是具有一定規律的二叉結構的數據堆積。
所謂一定的規律就是數據中最大的或者最小的數排在堆的頂部或者底部(這依賴於你的規則),接着就是這個數的左分支和右分支,且左分支和右分支必須都小於或者大於它的父母(這依賴於你的規則),然後是左分支的左分支和右分支,右分支的左分支和右分支,以此類推。。。,總結起來就是:父節點的值必須大於或者小於(這依賴與你的規則)2個直接子節點(左子節點和右子節點)的值,父節點與孫,重孫,外孫,女兒,外孫女節點都沒關係,只要直接的關係即可。看下面這個圖:
10是堆頂元素,可以看出10是所有的數中最小的,這裏可以稱之爲最小二叉堆,10的直接孩子有30和20,且都大於10;然後再來看20,它的孩子有30和24,且都大於直接父節點20,所有這裏給出了所有的直接孩子節點都必須大於它的父節。然後再來看20的右孩子24和10的左孩子30,顯而易見,30大於24,某種意義上來說,30的輩分要比24大,按照最小二叉堆的規則,這個是成立的,因爲他們倆沒有直接的關係,所以沒有必要比較他們的大小,這裏給出瞭解釋就是堆中的間接關係不用去管。
2. 存儲格式及規律:
這篇文章中實現的代碼所用的二叉堆是用數組格式存儲的,一維數組。假設數組名稱爲A,那麼,堆頂的數據存儲在數組A[1]中(位置1,而不是位置0,位置0在我們的堆實現中沒有用到),接着,他的2個孩子分別在位置A[1*2]和A[1*2 + 1],即A[2]和A[3],A[2]的2個孩子分別是A[2*2]和A[2*2 + 1],即A[4]和A[5]。總結一下:父節點位於(P),那麼他的孩子節點的位置則爲(2*P)和(2*P + 1)。
3. 添加元素:
如何按照這個存儲格式及規律添加元素?下面給出答案,這裏還是以最小二叉堆爲例,很簡單,我們始終往數組的尾部添加新元素newItem。newItem來了,把他放在數組最後位置,然後拿newItem與他的父節點比較,父節點的位置爲(newItem的位置/2),如果newItem的值小於父節點,那麼我們就交換這2個節點。然後比較newItem和他的新的父節點,新的父節點的位置爲(newItem的新位置/2),如果newItem的值比新的父節點還小,繼續交換,以此類推。
我們可以舉一個例子,假設我們已經有了一個最小二叉堆(其中有7個元素),現在我們要插入一個元素17到這個二叉堆中去,那麼插入後二叉堆大小就會加一變成8。看下面這個圖:
10 30 20 34 38 30 24 17
現在17位下劃線的那個,我們已經將他放在數組的最後位置了,然後我們比較他與他的父節點(位置爲8/2 = 4),在位置4(從1開始數)的爲34,由於17小於34,所以我們交換彼此,得到下圖:
10 30 20 17 38 30 24 34
現在17跑到位置4中去了,然後我們比較17和他的新的父節點(位置爲4/2 = 2),在位置2(從1開始數)的爲30,由於17還是小於30,所以交換彼此,得到下圖:
10 17 20 30 38 30 24 34
現在17跑到第二位了,繼續比較,父節點位置爲2/2 = 1,由於17不小於10,所以這裏就沒有必要比較了,走到這一步我們的新元素17添加完成。現在的堆已經添加了元素17,堆中有8位按照最小二叉堆規則排序的元素。
4. 刪除元素:
刪除元素則是按照添加元素相反的方向來做,我們需要刪除的元素是堆頂,在這裏是最小的那一個元素10,接着我們將最後一位元素放到堆頂的位置(之前的元素已經刪除了,所以這裏空了一個位置),得到下圖:
34 17 20 30 38 30 24
現在我們將比較堆頂的新元素與他的子節點,如果堆頂新元素比他的子節點都要大,則將新的堆頂元素與子節點中較小的元素交換。在這裏堆頂元素爲34,他的子節點分別在位置(1*2 = 2)和(1*2 + 1 = 3),即爲17和20,34要大於17和20,所以我們將34與17(子節點中較小的一個)交換,得到下圖:
17 34 20 30 38 30 24
接着我們得到34當前所在位置爲2(從1開始數),那麼他的子節點位置分別爲(2*2 = 4)和(2*2 + 1 = 5),即爲30和38,由於34還是大於他的所有子節點,交換34與20(子節點中較小的),得到下圖:
17 30 20 34 38 30 24
當前34所在位置爲4,子節點位置(4*2 = 8)和(4*2 + 1 = 9),由於當前堆的最大長度爲7,小於8和9,沒有元素與之對應,所以到這裏元素刪除完畢,最終結果如上圖所示。
終上所述,我們知道了二叉堆的原理,我們就可以拿來排序了,我們發現刪除操作要麼是從小到大刪除(最小二叉堆),要麼是從大到小刪除(最大二叉堆),所以我們只要將元素從堆中一個個的刪除然後保存到另一個數組中去,那麼這個數組就是已經排好序的數組了,好了,我們編寫代碼。
完整代碼:
using System;
using System.Collections.Generic;
namespace BinaryHeap
{
/// <summary>
/// The heap's order
/// </summary>
public enum Order
{
ASC = 0,
DESC = 1
}
/// <summary>
/// The BinaryHeap
/// </summary>
/// <typeparam name="T">The T represent the type of the heap's value</typeparam>
public class BinaryHeap<T> where T : IComparable<T>
{
/// <summary>
/// The size of the heap
/// </summary>
public int Size { get; set; }
private int length;
/// <summary>
/// The length of the heap
/// </summary>
public int Length
{
get
{
return length;
}
private set { length = value; }
}
private T[] Items { get; set; }
private Order Order = Order.ASC;
/// <summary>
/// The Cons of the heap
/// </summary>
/// <param name="size">The default size of the heap</param>
/// <param name="order">The order of the heap</param>
public BinaryHeap(int size, Order order)
{
if (size < 1)
{
throw new Exception("The size should be greater or equal than 1.");
}
this.Size = size;
this.Order = order;
// We don't need the Items[0], so the actually size is (this.Size + 1),
// and we just use the the value of Items[1], Items[2], Items[3]... and so on
this.Size++;
Items = new T[this.Size];
// Set to 0 represent the heap's length is empty
this.length = 0;
}
/// <summary>
/// Add new item to the heap
/// </summary>
/// <param name="item"></param>
public void Add(T item)
{
if (this.length == 0)
{
Items[1] = item;
}
else
{
int len = this.length;
if (len >= this.Size)
{
throw new Exception("The heap is fulfilled, can't add item anymore.");
}
// Set the new item at the end of this heap
int endPos = len + 1;
Items[endPos] = item;
// Calculate the new item's parent position
int parentPos = endPos / 2;
bool isContinue = true;
while (parentPos != 0 && isContinue)
{
// Compare the new added item and its parent, swap each other if needed
if (Order == BinaryHeap.Order.ASC)
{
if (Items[endPos].CompareTo(Items[parentPos]) < 0)
{
Swap(ref Items[endPos], ref Items[parentPos]);
endPos = parentPos;
parentPos = endPos / 2;
}
else
{
isContinue = false;
}
}
else
{
if (Items[endPos].CompareTo(Items[parentPos]) > 0)
{
Swap(ref Items[endPos], ref Items[parentPos]);
endPos = parentPos;
parentPos = endPos / 2;
}
else
{
isContinue = false;
}
}
}
}
// After the new item added, set the heap's length added by one
this.length++;
}
/// <summary>
/// Remove the top item from the heap
/// </summary>
/// <returns>if the order is ASC, return the smallest one, if DESC, return the largest one.</returns>
public T Remove()
{
if (this.length == 0)
{
throw new Exception("The heap is empty");
}
// Remove the first item and move the last item to the first
T removedItem = Items[1];
int len = this.length;
Items[1] = Items[len];
// After the top item removed, set the heap's length reduced by one
this.length--;
// Get the removing item's childrens's position
int currentPos = 1;
int leftChildPos = currentPos * 2;
int rightChildPos = currentPos * 2 + 1;
// Set the while loop continue or not flag
bool isContinue = true;
while ((leftChildPos <= len || rightChildPos <= len) && isContinue)
{
// Compare the removing item to its childrens, swap each other if needed
if (Order == BinaryHeap.Order.ASC)
{
#region Order == BinaryHeap.Order.ASC
if (leftChildPos <= len && rightChildPos <= len)
{
if (Items[leftChildPos].CompareTo(Items[rightChildPos]) < 0 && Items[currentPos].CompareTo(Items[leftChildPos]) >= 0)
{
Swap(ref Items[currentPos], ref Items[leftChildPos]);
currentPos = leftChildPos;
}
else if (Items[leftChildPos].CompareTo(Items[rightChildPos]) >= 0 && Items[currentPos].CompareTo(Items[rightChildPos]) >= 0)
{
Swap(ref Items[currentPos], ref Items[rightChildPos]);
currentPos = rightChildPos;
}
else
{
isContinue = false;
}
}
else if (leftChildPos <= len)
{
if (Items[currentPos].CompareTo(Items[leftChildPos]) >= 0)
{
Swap(ref Items[currentPos], ref Items[leftChildPos]);
currentPos = leftChildPos;
}
else
{
isContinue = false;
}
}
else if (rightChildPos <= len)
{
if (Items[currentPos].CompareTo(Items[rightChildPos]) >= 0)
{
Swap(ref Items[currentPos], ref Items[rightChildPos]);
currentPos = rightChildPos;
}
else
{
isContinue = false;
}
}
else
{
isContinue = false;
}
#endregion
leftChildPos = currentPos * 2;
rightChildPos = currentPos * 2 + 1;
}
else
{
#region Order == BinaryHeap.Order.DESC
if (leftChildPos <= len && rightChildPos <= len)
{
if (Items[leftChildPos].CompareTo(Items[rightChildPos]) > 0 && Items[currentPos].CompareTo(Items[leftChildPos]) <= 0)
{
Swap(ref Items[currentPos], ref Items[leftChildPos]);
currentPos = leftChildPos;
}
else if (Items[leftChildPos].CompareTo(Items[rightChildPos]) <= 0 && Items[currentPos].CompareTo(Items[rightChildPos]) <= 0)
{
Swap(ref Items[currentPos], ref Items[rightChildPos]);
currentPos = rightChildPos;
}
else
{
isContinue = false;
}
}
else if (leftChildPos <= len)
{
if (Items[currentPos].CompareTo(Items[leftChildPos]) <= 0)
{
Swap(ref Items[currentPos], ref Items[leftChildPos]);
currentPos = leftChildPos;
}
else
{
isContinue = false;
}
}
else if (rightChildPos <= len)
{
if (Items[currentPos].CompareTo(Items[rightChildPos]) <= 0)
{
Swap(ref Items[currentPos], ref Items[rightChildPos]);
currentPos = rightChildPos;
}
else
{
isContinue = false;
}
}
else
{
isContinue = false;
}
#endregion
leftChildPos = currentPos * 2;
rightChildPos = currentPos * 2 + 1;
}
}
return removedItem;
}
/// <summary>
/// Sort the heap
/// </summary>
/// <returns>Return the sorted heap array</returns>
public IEnumerable<T> Sort()
{
if (this.length == 0)
{
throw new Exception("The heap is empty");
}
while (this.length > 0)
{
yield return Remove();
}
}
#region Private method
/// <summary>
/// Swap each other
/// </summary>
/// <param name="t1">The first one</param>
/// <param name="t2">The second one</param>
private void Swap(ref T t1, ref T t2)
{
T temp = t1;
t1 = t2;
t2 = temp;
}
#endregion
}
}