算法排序之堆排序
2010-12-08 23:06 MichaelYin 阅读(379) 评论(0) 编辑 收藏 举报堆排序的重点在于对堆的理解,首先堆是一种数组对象,同时,它也可以被视为一棵完全二叉树,树中的每个节点从上到下,从左到右和数组中的每个元素是一一对应的,二叉树的每一层都是填满的,除了最后一层以外。
比如数组中的第一个元素就是二叉树的根节点,第二个就是元素就是根节点的左边的子节点,而第三个节点就是根节点的右边的子节点,然后第四个节点就是根节点的左边的子节点的左边的子节点,这样以此类推。
通过给定的节点的数组下标,我们可以求出该节点的左边子节点的坐标和右边子节点的坐标。
public static int Left(int parent) { return (2 * parent) + 1; } public static int Right(int parent) { return (2 * parent); }
堆有两种,一种是最大堆,一种是最小堆,最大堆的性质就是每个母节点都比它的子节点大,最小堆的性质则相反。我们这里的排序就用最大堆来排序。
Heapify这个方法用来保持最大堆的母节点总是比子节点大这个特性,将数组坐标i 和数组输入,它从母节点i 处开始比较,如果发现子节点比母节点大,那么就会让母节点下降,下降的母节点还有可能比下面的节点大,所以又进行比较。方法执行完之后,可以保证以i 为根节点的子树成为最大堆。关于这个方法有两种实现方式,递归和循环调用,我都写出来了,大家可以都看一下。
/// <summary> /// 保证最大堆的性质,采用递归方法实现 /// </summary> /// <param name="arrayToSort"></param> /// <param name="index"></param> public static void HeapifyType1(int[] arrayToSort, int index) { int leftIndex = HeapSort.Left(index); int rightIndex = HeapSort.Right(index); //先假设父节点最大,然后进行比较 int largestIndex = index; if (leftIndex < HeapSort.HeapSize && arrayToSort[leftIndex] > arrayToSort[largestIndex]) largestIndex = leftIndex; if (rightIndex < HeapSort.HeapSize && arrayToSort[rightIndex] > arrayToSort[largestIndex]) largestIndex = rightIndex; //判断是否需要朝下继续生成堆 if (largestIndex != index) { //调换位置 Utils.Swap(ref arrayToSort[index], ref arrayToSort[largestIndex]); HeapSort.HeapifyType1(arrayToSort, largestIndex); } } /// <summary> /// 保证最大堆的性质,采用迭代方法实现,功能和前面一样 /// </summary> /// <param name="arrayToSort"></param> /// <param name="index"></param> public static void HeapifyType2(int[] arrayToSort, int index) { int outIndex = index; while (true) { int leftIndex = HeapSort.Left(outIndex); int rightIndex = HeapSort.Right(outIndex); //先假设父节点最大,然后进行比较 int largestIndex = outIndex; if (leftIndex < HeapSort.HeapSize && arrayToSort[leftIndex] > arrayToSort[largestIndex]) largestIndex = leftIndex; if (rightIndex < HeapSort.HeapSize && arrayToSort[rightIndex] > arrayToSort[largestIndex]) largestIndex = rightIndex; //判断是否需要朝下继续生成堆 if (largestIndex != outIndex) { //调换位置 Utils.Swap(ref arrayToSort[outIndex], ref arrayToSort[largestIndex]); //调整节点index outIndex = largestIndex; } else { break; } } }
在开始排序之前,我们需要先把最大堆建立起来,如何建立最大堆呢,我们从底层往上调用Hepify方法,由于底层确定了每一次选出来的是最大值,那么上一层的方法在这个基础上才会执行正确。因为一个a[n]的数组,其中后面一半肯定是叶子节点,这可以看成一个性质,叶子节点可以看成是只含一个元素的堆。知道了这些,就不难理解我们通过Heapify将最大堆建立起来的原理了。
public static void BuildHeap(int[] arrayToSort) { HeapSort.HeapSize = arrayToSort.Length; for (int i = (arrayToSort.Length) / 2; i > -1; i--) HeapifyType1(arrayToSort, i); }
另外需要说明的一点是,HeapSize这个变量表示的是Heap的大小,因为我们维护的堆在开始和数组的长度是相等的,但后后来会小于数组的长度,
public static int HeapSize;
下面就是排序的过程了,我们通过前面提到的BuildHeap方法将最大堆建立起来后,显而易见根节点就是最大值,然后将根节点和后面的元素进行互换,并减小HeapSize,这样就不会考虑那些已经剔除的最大值,和最大值互换的叶子节点的值显然不一定比后来的子节点大,所以调用Heapify重新选出最大值。然后循环这个过程,最终完成数组的排序。
public static void Sort(int[] arrayToSort) { HeapSort.BuildHeap(arrayToSort); for (int i = arrayToSort.Length - 1; i > 0; i--) { Utils.Swap(ref arrayToSort[i], ref arrayToSort[0]); HeapSort.HeapSize--; HeapSort.HeapifyType1(arrayToSort, 0); } }
堆排序其实主要就是一个维护堆的过程,始终保持每一个母节点比子节点大这个性质。优先级队列的实现用堆是比较合适的。