[算法系列之一]堆排序

前序:

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

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

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

[算法系列之一]堆排序的相关文章

排序算法系列之堆排序

   堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法.堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点. 1 堆排序定义     n个关键字序列Kl,K2,-,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):    (1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤  )    若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完

算法系列

算法对程序员来说是熟悉的陌生人,编过大量代码后突然被哪个问到算法是什么也有时不知从何说起,简单来说是没有好好总结过仔细分析过.大学里面导师整天苦口婆心的教导算法有多么多么重要,但哪个能真正听得进去,即使认真的学了出了社会过个两三个月就忘到九霄云外了,记得算法的排序有几种就算不错了的.说到底还是没有真正的理解,而理解是建立在应用之上,用多了亲历了也就知道其中的道理,学好了也能锻炼自己的抽象能力,因此平时没事就多拿出来多练哈,没准哪天突然开窍了也说不定. 自己也是意识到学好算法的重要性,因此一直也在

缓存淘汰算法系列之3——FIFO类

缓存淘汰算法系列之3--FIFO类 1 FIFO 1.1. 原理 按照"先进先出(First In,First Out)"的原理淘汰数据. 1.2. 实现 FIFO队列,具体实现如下: 1. 新访问的数据插入FIFO队列尾部,数据在FIFO队列中顺序移动: 2. 淘汰FIFO队列头部的数据: 1.3. 分析 l 命中率 命中率很低,因为命中率太低,实际应用中基本上不会采用. l 复杂度 简单 l 代价 实现代价很小. 2. Second Chance 2.1. 原理 FIFO算法的改进

算法系列(二十) 计算中国农历(二)

所谓的"天文算法",就是利用经典力学定律推导行星运转轨道,对任意时刻的行星位置进行精确计 算,从而获得某种天文现象发生时的时间,比如日月合朔这一天文现象就是太阳和月亮的地心黄经(视黄 经)差为0的那一瞬间.能够计算任意时刻行星位置的一套理论就被称为星历表,比较著名的星历表有美 国国家航空航天局下属的喷气推进实验室发布的DE系列星历表,还有瑞士天文台在DE406基础上拓展的瑞 士星历表等等.根据行星运行轨道直接计算行星位置通常不是很方便,更何况大多数民用天文计算用不上 那么多精确的轨道参

算法系列(十九) 用天文方法计算日月合朔(新月)

中国农历的朔望月是农历历法的基础,而朔望月又是严格以日月合朔发生的那一天作为月首,因此日 月合朔时间的计算是制定农历历法的关键.本文将介绍ELP-2000/82月球运行理论,以及如何用ELP- 2000/82月球运行理论计算日月合朔时间. 要计算日月合朔时间, 首先要对日月合朔这一天文现象进行数学定义.朔望月是在地球上观察到的月相周期,平均长度约等于 29.53059日,而恒星月(天文月)是月亮绕地球公转一周的时间,长度约27.32166日.月相周期长度比恒 星月长大约两天,这是因为在月球绕地球

算法系列(十四) 狼、羊、菜和农夫过河问题

题目描述:农夫需要把狼.羊.菜和自己运到河对岸去,只有农夫能够划船,而且船比较小,除农 夫之外每次只能运一种东西,还有一个棘手问题,就是如果没有农夫看着,羊会偷吃菜,狼会吃羊. 请考虑一种方法,让农夫能够安全地安排这些东西和他自己过河. 这个题目考察人的快速逻辑运算和短期记忆力.分析一下,在狼->羊->菜这个食物链条中 ,"羊"处在关键位置,解决问题的指导思想就是将"羊"与"狼"和"菜"始终处于隔离状态,也 就是说

数据结构与算法系列(1)时间测试

前言 好久都把数据结构和算法的东西忘完了,最近想重温下这些知识.因此就写了<<数据结构与 算法系列>的文章,希望能和大家共同学习,共同探讨这方面的知识.希望大家多多指异. 1.时间测试 由于本部分内容采用了一种实用的方法来分析数据结构与算法检测,所以在这里避开了使用大O分 析法,而采用运行简单基准测试的方法来代替.这种测试将会说明运行一段代码需要多少秒数(或者 无论什么时间单位). 基准法测试是使用时间测试的方法来衡量运行完整算法所花费的时间长度.如同科学一样,基准测 试也像是一门艺术,

[算法系列之二十四]后缀树(Suffix Tree)

之前有篇文章([算法系列之二十]字典树(Trie))我们详细的介绍了字典树.有了这些基础我们就能更好的理解后缀树了. 一 引言 模式匹配问题 给定一个文本text[0-n-1], 和一个模式串 pattern[0-m-1],写一个函数 search(char pattern[], char text[]), 打印出pattern在text中出现的所有位置(n > m). 这个问题已经有两个经典的算法:KMP算法 ,有限自动机,前者是对模式串pattern做预处理,后者是对待查证文本text做预处

[算法系列之十九]最长公共子序列

题目 最长公共子序列 分析 有两个字符串S1和S2,求一个最长公共子串,即求字符串S3,它们同时是S1和S2的子串,且要求它们的长度最长,并确定这个长度.这个问题我们称之为最长公共子序列问题. 与求最长递增子序列一样,我们首先将原问题分割成一些子问题,我们用dp[i][j]表示S1中前i个字符和S2中前j个字符分别组成的两个前缀字符串的最长公共子串长度.显然的,当i,j较小时我们可以直接给出答案,如dp[0][j] 必等于0.那么,假设我们已经求的dp[i][j](0 <= i < x,0 &