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 } }