排序算法:堆排序算法实现及分析

article/2025/10/29 18:18:33

堆排序介绍

堆排序(Heap Sort)就来利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如何反复执行,便能得到一个有序序列了。

定义看懂没有?没看懂没关系,下面看图解。

堆排序图解

我们先来说说堆,我们这来看大顶堆,说大顶堆前,我先看 一个数组如何模拟成树的。请看下图,将数组元素从下标0开始,看做完全二叉树结点,请以层序遍历的角度看待它


我先来构造大顶堆,在构造堆之前,我们先看下堆的定义堆是具有下列性质的完全二叉树每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆我们从后往前将每个结点构成一个大顶堆不就得了?想想不是呢?因为最下层的结点已经满足堆条件,然后调整上层结点,使其满足大顶堆条件,那么整个完全二叉树不就成了大顶堆了么。我们应该从后面的那个结点开始呢?当然是从非叶子结点 9 开始呀,因为叶子结点没有孩子结点呗。将结点9看做一棵树将其调整问堆,然后将3位根结点的树调整为堆,然后将2为根结点的树调整为堆........,整棵完全二叉树就调整好了,成为了一个大顶堆。请看下图的代码和说明。



将堆的初始化看懂了,就已经成功了一大半了。还有一小部分就是 将最值(下标为0)放到序列的尾部,然后将剩下的元素继续进行堆调整,堆顶(下标为0)的元素 又是次最值,将其放到尾部,这样一直循环 直到将每个元素放到堆顶,则堆排序完毕。

堆排序代码

下面请堆排序代码,然后再进行解说。

//堆调整 大堆顶,将最大值放在根结点
void BigHeadAdjust(int *arr,int index,int length)
{int lchild = 2 * index + 1;int rchild = 2 * index + 2;int max = index;if (lchild<length&&arr[lchild]>arr[max]){max = lchild;}if (rchild<length&&arr[rchild]>arr[max]){max = rchild;}if (max != index){Swap(&arr[max], &arr[index]);BigHeadAdjust(arr, max, length);}return;
}//堆排序,采用大顶堆 升序
void HeapSort_Up(int *arr, int length)
{//初始化堆,将每个非叶子结点倒叙进行大顶堆调整。//循环完毕 初始大顶堆(每个根结点都比它孩子结点值大)形成for (int i = length / 2 - 1; i >= 0; i--){BigHeadAdjust(arr, i, length);}printf("大堆顶初始化顺序:");PrintArr(arr, length);//将堆顶值放到数组尾部,然后又进行大堆顶调整,一次堆调整最值就到堆顶了。 for (int i = length - 1; i >= 0; i--){Swap(&arr[0], &arr[i]);BigHeadAdjust(arr, 0, i);}return;
}

细心的朋友,会发现初始化化堆 是从非叶子结点从后往前进行调整为堆,而后序的堆调整 都是从下标为0处开始调整为堆因为起初的完全二叉树是完全无效的,所有只能从后往前调整。在初始化完堆之后,尽管将最值移动到尾部,打乱了堆,因为原来的堆结构已经基本形成,只需要递归调用BigHeadAdjust即可

堆排序复杂度分析

堆排序,它的运行时间主要是消耗在构建堆和在重建堆时的反复筛选上。在构建堆的过程,因为我们是从完全二叉树最下层的非叶子结点开始构建的,将它与其孩子结点进行比较和有必要的互换,对于每个非叶子结点来说,其实最多2次比较和互换,故初始化堆的时间复杂度为O(n)。在正式排序的时候,第i次取堆顶记录和重建堆需要O(logi)的时间(完全二叉树的某个结点到根结点的距离为log2i+1),并且需要取n-1次堆顶记录,因此重建堆的时间复杂度为O(nlogn)。所以总的来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对元素记录的排序状态不敏感,因此它无论最好,最坏,和平均时间复杂度均为O(nlogn)

完整代码

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/timeb.h>
#define MAXSIZE 1000000
//交换值
void Swap(int* a, int* b)
{int temp = *a;*a = *b;*b = temp;
}
//打印数组元素
void PrintArr(int* arr, int length)
{for (int i = 0; i < length; i++){printf("%d ", arr[i]);}printf("\n");return;
}
long GetSysTime()
{struct timeb tb;ftime(&tb);return tb.time * 1000 + tb.millitm;
}//堆调整 大堆顶,将最大值放在根结点
void BigHeadAdjust(int *arr,int index,int length)
{int lchild = 2 * index + 1;int rchild = 2 * index + 2;int max = index;if (lchild<length&&arr[lchild]>arr[max]){max = lchild;}if (rchild<length&&arr[rchild]>arr[max]){max = rchild;}if (max != index){Swap(&arr[max], &arr[index]);BigHeadAdjust(arr, max, length);}return;
}//堆排序,采用大顶堆 升序
void HeapSort_Up(int *arr, int length)
{//初始化堆,将每个非叶子结点倒叙进行大顶堆调整。//循环完毕 初始大顶堆(每个根结点都比它孩子结点值大)形成for (int i = length / 2 - 1; i >= 0; i--){BigHeadAdjust(arr, i, length);}//printf("大堆顶初始化顺序:");//PrintArr(arr, length);//将堆顶值放到数组尾部,然后又进行大堆顶调整,一次堆调整最值就到堆顶了。 for (int i = length - 1; i >= 0; i--){Swap(&arr[0], &arr[i]);BigHeadAdjust(arr, 0, i);}return;
}//堆调整 小堆顶,将最小值放在根结点
void SmallHeadAdjust(int *arr, int index, int length)
{int lchild = 2 * index + 1;int rchild = 2 * index + 2;int min = index;if (lchild<length&&arr[lchild]<arr[min]){min = lchild;}if (rchild<length&&arr[rchild]<arr[min]){min = rchild;}if (min != index){Swap(&arr[min], &arr[index]);SmallHeadAdjust(arr, min, length);}return;
}//堆排序,采用小顶堆 降序
void HeapSort_Down(int *arr, int length)
{for (int i = length / 2 - 1; i >= 0; i--){SmallHeadAdjust(arr, i, length);}for (int i = length - 1; i >= 0; i--){Swap(&arr[0], &arr[i]);SmallHeadAdjust(arr, 0, i);}return;
}
//希尔排序 升序
//根据插入排序的原理,将原来的一个大组,采用间隔的形式分成很多小组,分别进行插入排序
//每一轮结束后 继续分成更小的组进行 插入排序,直到分成的小组长度为1,说明插入排序完毕
void ShellSort_Up(int* arr, int length)
{int increase = length;int i, j, k, temp;do{increase = increase / 3 + 1;//每个小组的长度//每个小组的第0个元素for (i = 0; i < increase; i++){//对每个小组进行插入排序,因为是间隔的形式分组,每个小组下一个元素为 j+=incresefor (j = i + increase; j < length; j += increase){temp = arr[j];//待插入元素for (k = j - increase; k >= 0 && temp < arr[k]; k -= increase){arr[k + increase] = arr[k];}arr[k + increase] = temp;}}} while (increase>1);
}int main(int argc, char *argv[])
{srand((size_t)time(NULL));//设置随机种子int arr[10] = { 6,8,2,3,9,7,4,1,5,10 };int *arr2 = (int*)malloc(sizeof(int)*MAXSIZE);int *arr3 = (int*)malloc(sizeof(int)*MAXSIZE);//给每个元素设置一个随机值for (int i = 0; i < MAXSIZE; i++){int num = rand() % MAXSIZE;arr2[i] = num;arr3[i] = num;}//printf("排序前:\n");//PrintArr(arr, 10);//printf("堆排序升序:\n");//HeapSort_Up(arr, 10);//PrintArr(arr, 10);//printf("堆排序降序:\n");//HeapSort_Down(arr, 10);//PrintArr(arr, 10);long start1 = GetSysTime();ShellSort_Up(arr2, MAXSIZE);long end1 = GetSysTime();printf("%d个元素 希尔排序耗费%d毫秒\n",MAXSIZE,end1-start1);long start2 = GetSysTime();HeapSort_Up(arr3, MAXSIZE);long end2 = GetSysTime();printf("%d个元素 堆排序耗费%d毫秒\n", MAXSIZE, end2 - start2);return 0;
}

运行结果检测

正确性验证


和希尔排序比较

排序100万个数据,比较下他们的效率。希尔排序居然比堆排序高,运行了好几遍都是这个结果。。。






http://chatgpt.dhexx.cn/article/5V5VTlC3.shtml

相关文章

堆排序算法 总结

最近面试&#xff0c;老是被问到堆排序算法。 回答时老是感觉思路不清楚&#xff0c;现在总结一下&#xff0c;把思路弄清楚的。 1.堆排序是利用堆的特性对记录序列进行排序的一种排序方法。 好的那么堆得特性是什么呢&#xff1f; 堆得定义&#xff1a; 堆是满足下列性质的数…

Java实现堆排序算法

堆排序是计算机编程中一种流行且高效的排序算法。学习如何编写堆排序算法需要了解两种类型的数据结构-数组和树。 我们要排序的初始数字集存储在数组中&#xff0c;例如[10, 3, 76, 34, 23, 32]&#xff0c;排序后&#xff0c;我们得到一个排序后的数组[3,10,23,32,34,76] 堆排…

精通八大排序算法系列:二、堆排序算法

精通八大排序算法系列&#xff1a;二、堆排序算法 作者:July 、二零一一年二月二十日本文参考&#xff1a;Introduction To Algorithms&#xff0c;second edition。------------------- 此精通排序算法系列&#xff0c;前一节&#xff0c;已讲过了一、快速排序算法&#xff0c…

堆排序算法详解

一、堆排序算法原理和动态图解 将待排序的序列构造成一个大顶堆。此时&#xff0c;整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换&#xff0c;此时末尾元素就是最大值)&#xff0c;然后将剩余的n-1个序列重新构造成一个堆&#xff0c;这样就…

【堆排序算法】(C语言实现)

堆排序 一.堆排序1.堆的概念及性质1.1堆的概念1.2堆的性质 二.向下调整和向上调整两大算法1. 向下调整算法2.1 向下调整算法基本思路 2. 向上调整算法2.2 向上调整的基本思路 三.堆的实现&#xff08;小堆&#xff09;1.头文件包含2. 接口实现 四.任意树调整为堆&#xff08;小…

堆排序算法

堆排序 堆排序&#xff08;Heapsort&#xff09;是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构&#xff0c;并同时满足堆积的性质&#xff1a;即子结点的键值或索引总是小于&#xff08;或者大于&#xff09;它的父节点。 基本思想 利用大顶堆…

3.图解排序算法(三)之堆排序

作者&#xff1a; dreamcatcher-cx 出处&#xff1a; http://www.cnblogs.com/chengxiao/ 本文版权归作者和博客园共有&#xff0c;欢迎转载&#xff0c;但未经作者同意必须保留此段声明&#xff0c;且在页面明显位置给出原文链接。 原文链接&#xff1a;https://www.cnblogs.…

排序算法:堆排序

相关博客&#xff1a; 排序算法&#xff1a;冒泡排序、插入排序、选择排序、希尔排序 排序算法&#xff1a;归并排序、快速排序 排序算法&#xff1a;桶排序、计数排序、基数排序 排序算法&#xff1a;堆排序 十大排序算法小结 一、堆&#xff1a; 1、什么是堆&#xff1…

排序算法 —— 堆排序(图文超详细)

文章目录 堆排序1. 排序思路2. 如何建成大根堆2.1. 将待排序的数据看成一个完全二叉树。2.2. 建堆思想2.3. 建堆步骤2.3.1.先将最后一棵子树建成大根堆2.3.2.将其余子树建成大根堆2.3.3. 代码分析 3 如何实现堆排序3.1 排序思路3.2 代码实现的思路3.3 代码分析3.4 排序过程 4. …

十大经典排序算法----堆排序(超详细)

目录 1. 堆排序的基础知识 1.1 大顶堆&&小顶堆 1.2 向下调整算法 1.3 物理结构与逻辑结构的关系 2. 堆排序详解 2.1 堆排序整体思路 2.2 思路详解 2.2.1 建堆 2.2.2 大堆or小堆 2.2.3 输出数据 3. 时间复杂度分析 4. 完整代码 5. 彩蛋 1. 堆排序的基础知识…

【转载】堆排序算法(图解详细流程)

堆排序的时间复杂度O(N*logN),额外空间复杂度O(1)&#xff0c;是一个不稳定性的排序 目录 一 准备知识 1.1 大根堆和小根堆 二 堆排序基本步骤 2.1 构造堆 2.2 固定最大值再构造堆 三 总结 四 代码 一 准备知识 堆的结构可以分为大根堆和小根堆&#xff0c;是一个完全…

堆排序算法(图解详细流程)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 堆排序基本介绍大顶堆举例说明小顶堆举例说明堆排序的基本思想堆排序步骤图解说明堆排序的基本思路总结 堆排序基本介绍 堆排序是利用堆这种数据结构而设计的一种排…

堆排序详细图解(通俗易懂)

什么是堆 堆是一种叫做完全二叉树的数据结构&#xff0c;可以分为大根堆&#xff0c;小根堆&#xff0c;而堆排序就是基于这种结构而产生的一种程序算法。 堆的分类 大根堆:每个节点的值都大于或者等于他的左右孩子节点的值 小根堆:每个结点的值都小于或等于其左孩子和右孩子…

PreferenceActivity(二)

看到很多书中都没有对PreferenceActivity做介绍&#xff0c;而我正好又在项目中用到&#xff0c;所以就把自己的使用的在这总结一下&#xff0c;也方便日后查找。 PerferenceActivity是什么&#xff0c;看下面的截图&#xff1a; Android系统截图&#xff08;左&#xff09; …

android设置页面之PreferenceActivity及Preference

离上篇博客刚好一周&#xff0c;希望后面会记录更多的内容&#xff0c;也算自己的Android笔记吧。 本篇主要记录一般的android设置页面PreferenceActivity的使用以及与之剪不断&#xff0c;理还乱的Preference。 &#xff08;一&#xff09;如何使用 Android系统自带的设置应用…

android中PreferencesActivity的使用(一)

在使用android手机的时候&#xff0c;尤其是在操作软件设置时&#xff0c;我们经常见到这样的界面&#xff1a; 这是怎么来实现的的呢&#xff1f;其实android已经提供了相应的类和方法&#xff0c;当进行简单数据存储时&#xff08;比如&#xff1a;软件配置参数&#xff09;a…

PreferenceActivity用法简介(二)

本测试主要是为了测试PreferenceActivity的使用&#xff0c;其中设置了播放背景音乐和开启wifi的设置&#xff0c;也就是本文要讲的 PreferenceActivity。 Android提供了放摆放的工具来定义所有的程序的首选项&#xff0c;并支持既不不许要编写代码的情况写显示这些首选项。可…

Android之PreferenceActivity

看到很多书中都没有对PreferenceActivity做介绍&#xff0c;而我在看Android Samples时无意中看见了&#xff0c;所以就稍微总结一下&#xff0c;也方便日后查找。 PerferenceActivity是什么&#xff0c;看下面的截图&#xff1a; 好了&#xff0c;我们看到Android系统本身就大…

Android PreferenceActivity使用

PreferenceActivity继承了ListActivity&#xff0c;定义Activity继承PreferenceActivity。在res目录下新建一个xml文件夹&#xff0c;接着在这个文件夹下新建一个取名为preferences.xml的File文件&#xff0c;xml中可以使用的标签(Tag)可以分为两类&#xff0c;一类是管理布局的…

PreferenceActivity定制

android很多设置界面都会使用PreferenceActivity来实现&#xff0c;但那个界面比较丑陋&#xff0c;显示开发总是满足不了要求。 可以自己实现一个&#xff0c;但是那样又会使Activity中的逻辑代码和xml布局文件过于复杂&#xff0c;远远不及PreferenceActivity来的方便快捷。…