经典白话算法之堆排序

前序:

(二叉)堆数据结构是一种数组对象,它可以被视为一棵完全二叉树。树中每个节点与数组中存放该节点值的那个元素对应。

树的每一层都是填满的,最后一层除外。

树的根为a[1] (在这里是从1开始的,也可以从0开始),给定了某个节点的下标i,其父节点为i/2,左二子为2*i,右儿子为2*i+1。

二叉堆满足二个特性:

1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。

2.每个结点的左子树和右子树都是一个二叉堆(最大堆或最小堆)。

当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。

当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。

保持堆的性质:

MaxHeap是对最大堆进行操作的最重要的子程序。

以i为根的子树:

在算法每一步中,从a[i], a[Left(i)], a[Right(i)]找出最大值,并将其下标存在LargestIndex中。如果a[i]是最大的,则以i为根的子树已是最大堆,程序结束。

否则i的某个子结点中有最大元素则交换a[i],a[LargetIndex],从而使i及子女满足堆性质。下标为LargestIndex的结点在交换后的值为a[i],以该结点为根的子树又有可能违反最大堆的性质,因而又要对该子树递归调用MaxHeap,重新使子树平衡。

[cpp] view
plain
copy

  1. //调整以index为根的子树
  2. //n:堆中元素个数
  3. int MaxHeap(int a[],int index,int n){
  4. int LargestIndex = index;
  5. //左子节点
  6. int LeftIndex = 2*index;
  7. //右子节点
  8. int RightIndex = 2*index+1;
  9. if(LeftIndex <= n && a[LeftIndex] > a[LargestIndex]){
  10. LargestIndex = LeftIndex;
  11. }
  12. if(RightIndex <= n && a[RightIndex] > a[LargestIndex]){
  13. LargestIndex = RightIndex;
  14. }
  15. //如果a[index]是最大的,则以index为根的子树已是最大堆否则index的子节点有最大元素
  16. //则交换a[index],a[LargetIndex],从而使index及子女满足堆性质
  17. int temp;
  18. if(LargestIndex != index){
  19. //交换a[index],a[LargetIndex]
  20. temp = a[index];
  21. a[index] = a[LargestIndex];
  22. a[LargestIndex] = temp;
  23. //重新调整以LargestIndex为根的子树
  24. MaxHeap(a,LargestIndex,n);
  25. }
  26. return 0;
  27. }

建堆:

我们可以自底向上的用MaxHeap来将一个数组a[1-n]变成一个最大堆,子数组a[n/2+1,........n]中的元素是树中的叶子,因此每个都可以看做只含一个元素的堆,满足最大堆的要求,不用调整。所以只需调整以a[n/2........1]中元素为根的子树使之成为最大堆。

[cpp] view
plain
copy

  1. //建堆:将一个数组a[1-n]变成一个最大堆
  2. int BuildMaxHeap(int a[],int n){
  3. int i;
  4. //子数组a[(n/2+1,n/2+2......n)]中的元素都是树中的叶子
  5. for(i = n/2;i >= 1;i--){
  6. //调整以i为根节点的树使之成为最大堆
  7. MaxHeap(a,i,n);
  8. }
  9. return 0;
  10. }

a数组

16 7 3 20 17 8

初始堆:

自底向上从最后一个非叶节点开始调整:

(a)                                                    (b)                                                
(c)                                                   (d)

每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。

堆排序:

开始时,堆排序先用BuildMaxHeap将输入数组a[1-n]构造成一个最大堆。又因为数组中最大元素在根a[1],则可以通过它与a[n]交换来达到最终的正确位置。

现在,如果从堆中”去掉“结点n(不是真的删除,而是通过修改堆的元素个数n),可以很容易的将a[1-(n-1)]建成最大堆。原来根的子女依旧是最大堆,二新交换的根元素很有可能违背最大堆的性质。这时调用MaxHeap重新调整一下。在a[1-(n-1)]中构造出最大堆。堆排序不断重复这一过程,堆的大小由n-1一直降到2.从而完成排序的功能

[cpp] view
plain
copy

  1. //堆排序
  2. int HeapSort(int a[],int n){
  3. int temp;
  4. //BulidMaxHeap将输入数组构造一个最大堆
  5. BuildMaxHeap(a,n);
  6. //数组中最大元素在根a[1],则可以通过它与a[n]交换来达到最终的正确位置
  7. for(int i = n;i >= 2;i--){
  8. //交换
  9. temp = a[i];
  10. a[i] = a[1];
  11. a[1] = temp;
  12. //a[i]已达到正确位置,从堆中去掉
  13. n--;
  14. //重新调整,保持最大堆的性质
  15. MaxHeap(a,1,n);
  16. }
  17. return 0;
  18. }

(a)                                                   (b)                                              
(c)                                                (d)

(e)                                                    (f)                                                
   (g)

(h)                                              (i)                                                
 (j)                                                (k)

红色为排序后的结果;

代码:

[cpp] view
plain
copy

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. //调整堆
  4. int MaxHeap(int a[],int index,int n){
  5. int LargestIndex = index;
  6. //左子节点
  7. int LeftIndex = 2*index;
  8. //右子节点
  9. int RightIndex = 2*index+1;
  10. if(LeftIndex <= n && a[LeftIndex] > a[LargestIndex]){
  11. LargestIndex = LeftIndex;
  12. }
  13. if(RightIndex <= n && a[RightIndex] > a[LargestIndex]){
  14. LargestIndex = RightIndex;
  15. }
  16. //如果a[index]是最大的,则以index为根的子树已是最大堆否则index的子节点有最大元素
  17. //则交换a[index],a[LargetIndex],从而使index及子女满足堆性质
  18. int temp;
  19. if(LargestIndex != index){
  20. //交换a[index],a[LargetIndex]
  21. temp = a[index];
  22. a[index] = a[LargestIndex];
  23. a[LargestIndex] = temp;
  24. //重新调整以LargestIndex为根的子树
  25. MaxHeap(a,LargestIndex,n);
  26. }
  27. return 0;
  28. }
  29. //建堆:将一个数组a[1-n]变成一个最大堆
  30. int BuildMaxHeap(int a[],int n){
  31. int i;
  32. //子数组a[(n/2+1,n/2+2......n)]中的元素都是树中的叶子
  33. for(i = n/2;i >= 1;i--){
  34. //调整以i为根节点的树使之成为最大堆
  35. MaxHeap(a,i,n);
  36. }
  37. return 0;
  38. }
  39. //堆排序
  40. int HeapSort(int a[],int n){
  41. int temp;
  42. //BulidMaxHeap将输入数组构造一个最大堆
  43. BuildMaxHeap(a,n);
  44. //数组中最大元素在根a[1],则可以通过它与a[n]交换来达到最终的正确位置
  45. for(int i = n;i >= 2;i--){
  46. //交换
  47. temp = a[i];
  48. a[i] = a[1];
  49. a[1] = temp;
  50. //a[i]已达到正确位置,从堆中去掉
  51. n--;
  52. //重新调整,保持最大堆的性质
  53. MaxHeap(a,1,n);
  54. }
  55. return 0;
  56. }
  57. int main(){
  58. int n = 6;
  59. //a[0]不用,堆的根结点是从1开始的
  60. int a[] = {0,3,17,8,7,16,20};
  61. HeapSort(a,n);
  62. for(int i = 1;i <= n;i++){
  63. printf("%d ",a[i]);
  64. }
  65. return 0;
  66. }
时间: 2024-10-13 18:05:09

经典白话算法之堆排序的相关文章

经典白话算法之归并排序

void Merge(int A[],int p,int q,int r){ int i,j,k; //计算子数组A[p..q]的元素个数 int n1 = q - p + 1; //计算子数组A[q+1..r]元素个数 int n2 = r - q; //创建子数组L,R int* L = (int*)malloc(sizeof(int)*(n1+1)); int* R = (int*)malloc(sizeof(int)*(n2+1)); //将子数组A[p..q]赋值到L数组 for(i

经典白话算法之快速排序

[分析] [伪代码] [运行过程] [代码] /********************************* * 日期:2014-04-01 * 作者:SJF0115 * 题目:快速排序 **********************************/ #include <iostream> #include <stdio.h> using namespace std; //对子数组array[p...r]就地重排 int Partition(int array[],i

经典白话算法之二叉树中序前序序列(或后序)求解树

这种题一般有二种形式,共同点是都已知中序序列.如果没有中序序列,是无法唯一确定一棵树的. <1>已知二叉树的前序序列和中序序列,求解树. 1.确定树的根节点.树根是当前树中所有元素在前序遍历中最先出现的元素. 2.求解树的子树.找出根节点在中序遍历中的位置,根左边的所有元素就是左子树,根右边的所有元素就是右子树.若根节点左边或右边为空,则该方向子树为空:若根节点 边和右边都为空,则根节点已经为叶子节点. 3.递归求解树.将左子树和右子树分别看成一棵二叉树,重复1.2.3步,直到所有的节点完成定

经典白话算法之优先级队列

<1>概念 优先级队列,顾名思义,就是一种根据一定优先级存储和取出数据的队列.它可以说是队列和排序的完美结合体,不仅可以存储数据,还可以将这些数据按照我们设定的规则进行排序.优先级队列是堆的一种常见应用.有最大优先级队列(最大堆)和最小优先级队列(最小堆).优先级队列是一种维护有一组元素构成的集合S的数据结构. <2>优先队列支持的基本运算 [cpp] view plaincopy //建立一个保存元素为int的优先级队列,其实是建了一个小顶堆 //但是请特别注意这样的建的堆默认是

经典白话算法之中缀表达式和后缀表达式

一.后缀表达式求值 后缀表达式也叫逆波兰表达式,其求值过程可以用到栈来辅助存储. 假定待求值的后缀表达式为:6  5  2  3  + 8 * + 3  +  *,则其求值过程如下: (1)遍历表达式,遇到的数字首先放入栈中,依次读入6 5 2 3 此时栈如下所示: (2)接着读到"+",则从栈中弹出3和2,执行3+2,计算结果等于5,并将5压入到栈中. (3)然后读到8(数字入栈),将其直接放入栈中. (4)读到"*",弹出8和5,执行8*5,并将结果40压入栈中

经典白话算法之二叉树各种遍历

树形结构是一类重要的非线性数据结构,其中以树和二叉树最为常用. 二叉树是每个结点最多有两个子树的有序树.通常子树的根被称作"左子树"(left subtree)和"右子树"(right subtree).二叉树常被用作二叉查找树和二叉堆或是二叉排序树.二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒.二叉树的第i层至多有2的 i -1次方个结点:深度为k的二叉树至多有2^(k) -1个结点:对任何一棵二叉树T,如果其终端

经典排序算法 - 堆排序Heap sort

经典排序算法 - 堆排序Heap sort 堆排序有点小复杂,分成三块 第一块,什么是堆,什么是最大堆 第二块,怎么将堆调整为最大堆,这部分是重点 第三块,堆排序介绍 第一块,什么是堆,什么是最大堆 什么是堆 这里的堆(二叉堆),指得不是堆栈的那个堆,而是一种数据结构. 堆可以视为一棵完全的二叉树,完全二叉树的一个"优秀"的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示,每一个结点对应数组中的一个元素. 数组与堆之间的关系 二叉堆一般分为两种:最大堆和最小堆. 什么

经典排序算法(Java实现)

以下程序均将数据封装于DataWrap数据包装类中,如下所示: 1 //数据包装类 2 class DataWrap implements Comparable<DataWrap> 3 { 4 int data; 5 String flag; 6 public DataWrap(int data,String flag) 7 { 8 this.data = data; 9 this.flag = flag; 10 } 11 //重写compareTo方法 12 public int compa

经典排序算法

经典排序算法(via  kkun) 经典排序算法,以下文章参考了大量网上的资料,大部分都给出了出处 这一系列重点在理解,所以例子什么的都是最简单的情况,难免失误之处,多指教 大多数排序算法都给出了每一步的状态,以方便初学者更容易理解,通俗易懂,部分难以理解的排序算法则给出了大量的图示,也算是一个特色吧 经典排序算法 - 快速排序Quick sort 经典排序算法 - 桶排序Bucket sort 经典排序算法 -  插入排序Insertion sort 经典排序算法 - 基数排序Radix so