当前位置: 首页 > news >正文

[算法]——堆排序(C语言实现)

        简单的介绍一下用堆排序的算法对整形数据的数据进行排序。

一、堆的概念

        堆是具有下列性质的完全二叉树每个结点的值都大于或等于其左右孩子节点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

大顶堆和小顶堆的示意图:

二、堆排序的算法

        因为数组具有顺序结构,而我们的完全二叉树可以使用顺序结构来表示,所以我们可以用堆对数组进行排序。

(一)、算法思路

        这里介绍一下排升序的方法,等明白了思路后,排降序自然也会了。

        假设数组的元素个数为n,将待排序的数组构造成一个大顶堆。此时,整个数组的最大值就是堆顶的根节点。将它移走(将其与堆数组的末尾元素进行交换,此时末尾元素就是最大值),这样我们待排序的数组的最大值就排到了正确的位置上。然后我们把剩余的n-1个元素重新构成一个大顶堆,这样堆顶元素就是我们的次大值,将其移走,次大值的元素也就排好了。如此反复执行,就得到了一个升序的数组。如果我们要排降序的数组,则需要建小顶堆。

        于是我们就有了两个问题。一是:如何把一个无序的数组构建成大顶堆?在将堆顶元素移走后要怎么将剩余的数组元素重新调整为堆?

(二)、向上调整和向下调整

        向上调整针对的是当将一个新的元素插入到一个堆中时,将新插入的元素向上进行调整,将其二叉树保持原来的堆的结构。如果是使用堆来对一个数组进行排序的话,使用向下调整就足够了,所以我们先讲向下调整,当我们实现了对数组的堆排序后,再回来看向上调整。

1、向下调整

        向下调整的作用就是,当我们在一个堆的结构中,把堆顶元素给替换成别的值后,其堆顶处极可能已经不满足大顶堆和小顶堆的要求了,这个时候我们就需要把这个堆顶位置的元素向下调整到合适的位置,使其重新满足堆的要求。

例如下面这个例子:

        这是一个大顶堆,当我们用20替换其堆顶元素时,其不再满足大顶堆的结构,这时我们需要对堆顶的20进行向下调整。具体方法如如下:

具体代码:

//向下调整为大顶堆
void AdjustDown(int* arr,int left,int right)//传入数组和要向下调整的区间
{int pos = left;//记录向下调整的位置int temp = arr[pos];//保存向下调整的值for (int i = left * 2; i <= right; i *= 2)//遍历其要向下调整的结点的孩子{//找到其左孩子和右孩子的最大值 如果右孩子不存在,则最大值就算为左孩子if (i + 1 <= right && arr[i] < arr[i + 1])i++;//此时i指向右孩子if (temp < arr[i]) //如果要向下调整的值不如孩子大arr[pos] = arr[i];elsebreak;//更新要向下调整的值的下标位置pos = i;}//向下调整完毕,此时pos的位置就是要向下调整的值的最终位置arr[pos] = temp;
}

2、向上调整

        向上调整非常简单,只需要将插入的值的结点与其双亲结点相比较,如果比双亲大,那么就交换位置,一直重复该过程。

//向上调整为大堆顶
void AdjustUp(int* arr,int index)
{int pos = index;//记录向上调整的值的位置int temp = arr[index];//保存向上调整的值//遍历其双亲节点进行向上调整,如果向上调整的下标为0或比双亲小就结束for (int i = pos / 2; pos > 0; i /= 2){if (temp > arr[i])arr[pos] = arr[i];elsebreak;//更新pos的位置pos = i;}//向上调整完毕,此时pos位置就是向上调整的值的位置arr[pos] = temp;
}

 (三)、将无序数组转化为大顶堆

        实现方法:从最后一个叶子结点(就是数组的最后一个元素的下标的位置的结点)处的双亲结点开始,对该结点以及该结点之前的所有结点进行向下调整操作,这样就可以把无序数组转化为了大顶堆的结构。下面是将一个无序数组转化为大顶堆的示意图(标识为蓝色的值就是需要依次进行向下调整的结点。)

        

         就这样,我们就将一个无序的数组转化为了一个具有大顶堆结构的数组了。

        我们也可以从0开始遍历数组,每次执行一次向上调整,这样也可以建堆,但是其消耗比较大。因为需要对每个结点进行向上调整操作,而我们的向下调整建堆是不需要对最后一层的结点进行向下调整的,在一棵满二叉树中,最后一层的结点数就占了整棵树一半的结点数,这意味着向上调整建堆比向下调整建堆多用了很多时间。

(四)、堆排序的最终实现

        我们以一个大顶堆为例,看看将大顶堆转化成一个升序的数组的过程。

        来看下面这个大顶堆,是如何变升序的。

         此时90排好了。

 此时80就排好了。

如此往复...... 

具体代码:

//堆排序
void HeapSort(int* arr, int nums)
{//先从最后一个节点的双亲结点注逐一往前进行向下调整,将数组调整为大堆for (int i = (nums - 1) / 2; i >= 0; i--){AdjustDown(arr,i, nums - 1);}//将最大值放置到末尾然后将其与堆的联系解除,然后再对堆顶元素向下调整for (int i = nums - 1; i >= 1; i--){swap(arr[0], arr[i]);AdjustDown(arr, 0, i - 1);}
}

 三、堆排序的时间复杂度

        堆排序不需要额外开辟空间,所以空间复杂度为O(1)。

        时间消耗上主要在初始建堆和在反复重建堆的时间上。而我们对无序数组进行建堆所需要的时间复杂度为O(n),而我们在排序时,每次都需要对堆顶进行向下排序,其时间复杂度为O(nlogn)。所以堆排序的时间复杂度为O(nlogn)

相关文章:

  • MUNIK解读ISO26262--系统架构
  • 网络编程:UDP编程笔记
  • (一)Docker基本介绍
  • 【PYG】dataloader和densedataloader
  • 解决Linux环境Qt报“cannot find -lgl“问题
  • 2024.7.5
  • 【单链表】04 试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为0(1)。
  • VPN是什么?
  • 实验三 图像增强—灰度变换
  • Windows 11 安装 安卓子系统 (WSA)
  • Cesium与Three相机同步(3)
  • 安装局部的typeScript环境
  • 【C++】 解决 C++ 语言报错:Undefined Reference
  • window上部署sql server改动端口、和sqlserver的一些还原、批量插入存储过程的命令
  • Django 模版继承
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • Android Studio:GIT提交项目到远程仓库
  • JAVA SE 6 GC调优笔记
  • JavaScript实现分页效果
  • js中forEach回调同异步问题
  • Laravel 中的一个后期静态绑定
  • php的插入排序,通过双层for循环
  • sublime配置文件
  • vue.js框架原理浅析
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 动态规划入门(以爬楼梯为例)
  • 跨域
  • 前端面试之闭包
  • 前端相关框架总和
  • 区块链将重新定义世界
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 设计模式 开闭原则
  • 说说动画卡顿的解决方案
  • 责任链模式的两种实现
  • 栈实现走出迷宫(C++)
  • 06-01 点餐小程序前台界面搭建
  • 第二十章:异步和文件I/O.(二十三)
  • 移动端高清、多屏适配方案
  • ​2021半年盘点,不想你错过的重磅新书
  • ​520就是要宠粉,你的心头书我买单
  • ​埃文科技受邀出席2024 “数据要素×”生态大会​
  • ​人工智能书单(数学基础篇)
  • # C++之functional库用法整理
  • #pragma data_seg 共享数据区(转)
  • #stm32驱动外设模块总结w5500模块
  • #Z0458. 树的中心2
  • #数学建模# 线性规划问题的Matlab求解
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (论文阅读40-45)图像描述1
  • (实战)静默dbca安装创建数据库 --参数说明+举例
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (转)EXC_BREAKPOINT僵尸错误