《数据结构学习笔记---第十篇》--- 堆堆排序(超详细图解)

目录

1.堆是什么?

2.问题引入:当我们插入一个新的元素时,那么他还是堆吗。

3.堆的元素插入

4.问题引入:当我们删除一个堆顶元素时,我们又该如何调整呢?

5.堆顶元素删除

6.如何建堆?

6.1向上调整建堆:

6.2向下调整建堆:

6.3 两者区别:

7.堆排序的实现:


1.堆是什么?

  堆其实从逻辑上看是一棵完全二叉树物理结构上来看就是一个顺序的数组。满足:任何一个非叶节点的值都不大于(或不小于)其左右孩子结点的值。若父亲大,孩子小叫做大根堆,若父亲小,孩子大叫做小根堆。

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

 从这个树中我们可以看到,下标为奇数刚好为左孩子,下标为偶数刚好为右孩子。

由此我们可以得到孩子与父亲的关系

leftchild=parent*2+1;

rightchild=parent*2+2;

parent=(child-1)/2;

2.问题引入:当我们插入一个新的元素时,那么他还是堆吗。

  如果我们原来有一个堆,但我们又插入了新的数据,那么此时我们就需要进行向上调整,他是个大根堆,按照我们的顺序可以将他重新调整为大根堆。那么我们最容易误解的就是如果原本是个小根堆的话是不是就要向下调整呢?实际情况小根堆的新元素插入也是向上调整的,只不过就是元素的对比条件与大根堆不同,但代码逻辑都是向上比较的,都是一个爬升的过程。

 代码展示

void AdjustUp(HPDataType* a, int child)
{
    int parent = (child - 1) / 2;
    while (child>0)
    {
        if (a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }
}

我们知道了堆增加元素后如何使他变成新的堆,那么我们就可以进行堆的插入操作了。

3.堆的元素插入

代码展示

void HeapPush(HP* hp, HPDataType x)
{
    assert(hp);
    if (hp->size == hp->capacity)
    {
        int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;//申请扩容
        HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);//千万别忘了怎么扩容的
        if (tmp == NULL)
        {
            perror("realloc fail");
            exit(-1);
        }
        hp->capacity = newcapacity;
        hp->a = tmp;
    }
        hp->a[hp->size] = x;
        hp->size++;

        AdjustUp(hp->a , hp->size - 1);
}

考虑到我们的堆存储结构实际上是顺序结构,所以当我们插入元素可能会用到异地扩容。

4.问题引入:当我们删除一个堆顶元素时,我们又该如何调整呢?

如图,为了方便操作,我们把堆底的元素和堆顶的元素进行了调换,这样我们可以保证他的逻辑结构还是一颗完全二叉树,方便我们进行调整操作。

此时,我们进行的是向下调整算法,我们可以观察出我们向下调整算法的前提:左右子树必须是一个堆,才能调整。

代码展示:

void AdjustDown(HPDataType* a, int n, int parent)
{
    int child = parent * 2 + 1;
    while (child<n)//要分两个独立的判断 1.先判断孩子的大小,拿出一个最大的。2.然后拿出的最大的与根比较,最终决定是否交换位置
    {
        if (child+1 < n && a[child+1]> a[child])
        {
            child++;//指向大的孩子
        }
        if (a[child] > a[parent])
        {
            Swap(&a[parent], &a[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
  
}

我们知道了堆删除元素后如何使他变成新的堆,那么我们就可以进行堆的删除操作了。

5.堆顶元素删除

代码展示:

void HeapPop(HP* hp)
{
    assert(hp);
    assert(hp->size>0);
   Swap(&hp->a[0] ,&hp->a[hp->size-1]);//这里忘记减一了

    hp->size--;

    AdjustDown(hp->a, hp->size, 0);
    
}

6.如何建堆?

第一种方法就是,我们可以不断地插入新元素,然后再进行向上调整操作。实质上就是第二种方法中的向上调整建堆。

图解:

代码展示:

void HeapCreate(HP* hp, HPDataType* a, int n)
{
	assert(hp);
	HeapInit(hp);
	for (int i = 0; i < n; ++i)
	{
		HeapPush(hp, a[i]);
	}
}

第二种方法是我们先开辟出一个新的数组,就是堆已有的数组进行调整,但是到底是向上调整还是向下调整我们又分为了两种情况:

6.1向上调整建堆:

图解建堆

 代码展示:

void HeapCreate(HP* hp, HPDataType* a, int n)
{

     assert(hp);
   
      hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);//千万别忘了怎么扩容的
    if (hp->a == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
   
    memcpy(hp->a, a, sizeof(HPDataType) * n);
    hp->capacity = hp->size = n;
 
      for (int i = 1; i<n ; ++i)
    {
        AdjustUp(hp->a, i);
    }
}

6.2向下调整建堆:

图解建堆:

代码展示:

void HeapCreate(HP* hp, HPDataType* a, int n)
{

    assert(hp);
   
      hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);//千万别忘了怎么扩容的
    if (hp->a == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
   
    memcpy(hp->a, a, sizeof(HPDataType) * n);
    hp->capacity = hp->size = n;
    for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    {
        AdjustDown(hp->a, n, i);
    }
}

6.3 两者区别:

向下调整时:

向上调整时:

因此,当我们想要建堆的时候,我们优先选择向下调整建堆,他的时间代价更小。

问题引入:我们实现堆,是为了排序,那如果我们为了要升序序列,我们应该是怎样建堆?

7.堆排序的实现:

如果我们为了实现升序序列,我们应该建立大堆还是小堆?

应该是建立大堆

图解:

从图上我们可以看出,建立大堆的目的就是把最大的值,给放到数组的后面,也就是挑选出来,最后的数组里就是按升序排列的。当然我们要想实现降序序列,就是需要建立大根堆,将小的数挑选出来。

代码展示:

void HeapSort(int* a, int n)
{
    for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    {
        AdjustDown(a, n, i);
    }

    // O(N*logN)
    int end = n - 1;
    while (end > 0)
    {
        Swap(&a[0], &a[end]);
        AdjustDown(a, end, 0);
        --end;
    }
}

void Test3()
{
		int array[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
		HeapSort(array, sizeof(array) / sizeof(int));

		for (int i = 0; i < sizeof(array) / sizeof(int); ++i)
		{
			printf("%d ", array[i]);
		}
		printf("\n");

}

堆排序的性能分析:

1.空间效率:

仅使用常数个辅助单元,空间复杂度为O(1)。

2.时间效率:

建堆的时间O(n),之后又向下调整操作n-1次,而完全二叉树的高度为 logN,最后平均时间复杂度为 O(N*logN)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/517697.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

栈的应用——用栈实现算数混合运算表达式的计算

1、单目运算符双目运算符 算数运算符分为单目运算符和双目运算符等 单目运算符只需要一个操作数,双目运算符需要两个操作数 双目运算符最常见:常见的算术运算符:*/,比较运算符:<>=等等以下是一些单目运算符:正号 (+): 用于表示正数或给数值一个正号。例如:+5 仍然…

自动驾驶中各种坐标系辨析

坐标系辨析 0. 地球椭圆体1. 大地坐标系2. eci地心惯性坐标系3. 地心地固坐标系(ECEF坐标系&#xff0c;E系)4. 站心坐标系(ENU坐标系)5. UTM坐标系6. LTM坐标系7. IMU坐标系8. 代码部分8.1 LLA(大地坐标系坐标、经纬度海拔)坐标转LTM系(ENU系)下的三维笛卡尔坐标8.2 LLA坐标转…

Vue3(学自尚硅谷)

一、基础准备工作 &#xff08;一&#xff09;过程 环境要求&#xff1a;有node.js环境、npm。执行命令&#xff1a; npm create vuelatest 而后选择&#xff1a; ✔ 请输入项目名称&#xff1a; … me_vue3 ✔ 是否使用 TypeScript 语法&#xff1f; … 否 / 是 ✔ 是否启用…

Java 中 Spring Boot 框架下的 Email 开发

Email 开发 1. 核心依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId> </dependency><dependency><groupId>org.springframework.boot</groupId><…

9.set容器的使用

文章目录 set容器1.构造和赋值代码工程运行结果 2.大小和交换代码工程运行结果 4.插入和删除代码工程运行结果 5.查找和统计工程代码运行结果 6.multset代码工程运行结果 7.指定排序规则代码工程运行结果 8.自定义数据类型排序代码工程运行结果 set容器 所有元素都会在插入时&a…

ABAP SHIFT-字符串移位 和 CONDENSE去除空格

文章目录 SHIFT-字符串移位 和 CONDENSE去除空格SHIFT BY n PLACES RIGHT/LEFT运行结果 SHIFT ... UP TO ...运行结果 其他的-变量后面加括号和数字SHIFT c LEFT/RIGHT DELETING运行结果 SHIFT 去除0示例程序1运行结果示例程序2运行结果 CONDENSE示例程序运行结果 SHIFT-字符串…

电荷泵如何实现升压原来

电荷泵如何实现升压原来 某芯片自举栅极驱动内部原理图迪克森电荷泵 某芯片自举栅极驱动内部原理图 迪克森电荷泵 迪克森电荷泵&#xff08;Dickson Charge Pump&#xff09;是一种电压倍增器电路&#xff0c;可以将低电压升高到较高电压&#xff0c;相对于其他电压升压电路&a…

Vue3:组件间通信-各种通信方式的用法总结

Vue3组件通信和Vue2的区别&#xff1a; 移出事件总线&#xff0c;使用mitt代替。vuex换成了pinia。把.sync优化到了v-model里面了。把$listeners所有的东西&#xff0c;合并到$attrs中了。$children被砍掉了。

Java NIO是New IO还是Non-blocking IO

文章目录 前言NIO到底叫啥通过对比理解NIO传统IO网络编程NIO引入的新概念NIO网络编程两者区别NIO的事件驱动 总结 前言 很多小伙伴对Java NIO的一些概念和编程不是很理解&#xff0c;希望通过本文对Java NIO与传统IO的对比&#xff0c;可以帮助大家更好地理解和掌握Java NIO。…

10-用PySpark建立第一个Spark RDD

目录 RDD概念RDD特点建立RDD的方式不同工具建立RDD的方式使用PySpark Shell(交互环境)建立RDD使用VSCode编程建立RDD使用Jupyter Notebook建立RDD 总结 PySpark实战笔记系列第一篇 RDD概念 Apache Spark的核心组件的基础是RDD。所谓的RDD&#xff0c;即弹性分布式数据集&#…

力扣刷题 二叉树的迭代遍历

题干 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#xff1a;root [1] 输…

以太网布局指南

2层板 顶层走信号线以及地平面底层走信号线以及地平面信号走线应至少沿一条边被接地或接地走线包围如果使用地走线&#xff0c;应接本层接地平面&#xff0c;与上层接地平面解耦。 4层板 当信号走线被重新引用到功率平面时&#xff0c;在地平面和功率平面之间需要去耦电容器(0…

CSS - 你实现过0.5px的线吗

难度级别:中级及以上 提问概率:75% 我们知道在网页显示或是网页打印中,像素已经是最小单位了,但在很多时候,即便是最小的1像素,精度却不足以呈现所需的线条精度和细节。因此,为了在网页显示和网页打印中呈现更加细致的线条,为了在视觉…

回归预测 | Matlab基于CPO-GPR基于冠豪猪算法优化高斯过程回归的多输入单输出回归预测

回归预测 | Matlab基于CPO-GPR基于冠豪猪算法优化高斯过程回归的多输入单输出回归预测 目录 回归预测 | Matlab基于CPO-GPR基于冠豪猪算法优化高斯过程回归的多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab基于CPO-GPR基于冠豪猪算法优化高斯…

【C语言】猜数字小游戏(并讲解随机数相关知识)

前言 一、游戏菜单 二、游戏逻辑 1.用户选择 2.开始游戏 2.1 生成1~100的随机数 总结 前言 本文讲解使用C语言写一个猜数字小游戏(1~100)&#xff0c;涉及到的语法为&#xff1a;循环、分支、随机数、函数 一、游戏菜单 一个游戏的最开始&#xff0c;往往是一个菜单&…

从零开始实现一个RPC框架(一)

前言 在上一篇文章中我们先列举了大致的需求&#xff0c;定义了消息协议。这次我们着手搭建基本的RPC框架&#xff0c;首先实现基础的方法调用功能。 功能设计 RPC调用的第一步&#xff0c;就是在服务端定义要对外暴露的方法&#xff0c;在grpc或者是thrift中&#xff0c;这一…

如何删除 iPhone 上的 iCloud 激活锁

Apple 在 iPhone 上通过不同的安全屏障来保护您的数据。 iCloud 激活锁可阻止外部人员访问您的手机。您可以通过打开“查找我的 iPhone”功能来激活此锁。 使用安全协议似乎是无害的&#xff0c;直到你到达门的另一边。如果您购买了带有激活锁的二手 iPhone 或忘记了 iCloud 凭…

面试经典-Spring篇

1、解释Spring框架中bean的生命周期 2、单例Bean的优势

CEF的了解

(14 封私信 / 80 条消息) CEF和Electron的区别是什么&#xff1f; - 知乎 (zhihu.com) Electron面向的开发者&#xff1a;会用JavaScript,HTML,CSS&#xff0c;不会C CEF面向的开发者&#xff1a;会用JavaScript,HTML,CSS&#xff0c;会C (14 封私信 / 80 条消息) liulun - …

【文献分享】ALKEMIE:加速材料发现和设计的智能计算平台

题目&#xff1a;ALKEMIE: An intelligent computational platform for accelerating materials discovery and design 链接&#xff1a;DOI: 10.1016/j.commatsci.2020.110064 ALKEMIE&#xff1a;加速材料发现和设计的智能计算平台 摘要 通过传统的试错方式开发具有目标特性…