堆/堆排序(C/C++)

news2025/1/20 3:33:15

        本篇文章将会较为全面的介绍堆的概念以及实现堆两个重要算法:向上调整算法和向下调整算法。接着实现了堆排序。

        若想查看对应位置,可直接按照以下目录进行查看:

        目录

1.堆的概念及结构

2.堆的实现

2.1 堆的向上调整算法

2.2 堆的向下调整算法

2.3 堆的插入

2.4 堆的删除 

2.5 堆的实现

3.堆排序

1.堆的概念及结构

        概念:若一个关键码的集合K=\left\{ k_0,k_1,k_2,k_3,...,k_{n-1} \right\},把它的所有元素按照完全二叉树的顺序存储方式存储在一个一维数组中,并满足:sss且xxx(xxx且zzzz),则称为小堆(大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

        性质:1.堆中的某个节点的值总是不大于或不小于其父节点的值;

                   2.堆总是一颗完全二叉树;

                   3.堆中父节点的关系与孩子节点的关系为:leftchild_k=parent_{2\times i+1},rightchild_k=parent_{2\times i+2}\,\,

        堆的结构如下:

        堆的存储结构:在数据结构中,对于二叉树的存储结构一般可以分为链式存储、顺序表存储;同样,对于堆的存储我们也可以选择链式存储和顺序表存储,但我们需要从中选出最优的存储结构。对于满二叉树和完全的二叉树的顺序表存储结构对于整个顺序表都可以将其存储满,并且下标的变化便于我们在堆中添加数据调整数据。所以我们使用顺序表来存储堆。如下:

2.堆的实现

        堆的实现主要的两个点为堆的建立和堆的调整,其中的主要思想为堆的向上调整算法向下调整算法,具体的实现将会在下面实现。

2.1 堆的向上调整算法

        堆的向上调整发算法是实现堆的插入核心算法。

        对于堆的建立,假设我们将一组数据一个一个的加入到堆中,那么我们需要在每一个元素入堆时都要进行调整,我们将加入的元素存入到顺序表的最后,然后利用双亲结点和孩子结点之间的关系来判断是否需要调整,知道调整到根节点为止。对于此算法思想既可以使用递归算法,也可以使用循环算法,在这里我们使用循环算法,如下:

        具体的实现算法如下:

void Swap(HPDataType* p1, HPDataType* p2) {
	HPDataType* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

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;
		}
	}
}

2.2 堆的向下调整算法

        向下调整算法是堆删除堆顶元素的核心算法。

        向下调整算法的主要作用为:当我们需要将堆顶的元素弹出时,将整个堆进行维护,建立一个新的堆。其主要思路是将当前元素的孩子结点与双亲结点进行比较,但是此时双亲节点的孩子节点有两个,我们需要找出最合适的那一个,比如:建小堆则找出最小的孩子结点,建大堆则找出最大的孩子结点。然后将孩子结点与双亲结点进行交换,以此迭代,直到迭代到叶子结点。

        图示如下:

        以建小堆的算法为例,算法如下:

//建小堆的向下调整算法
void AdjustDown(HPDataType* a, int size, int parent) {
    //左孩子结点与双亲结点的关系
	int child = parent * 2 + 1;
    //假若右孩子比左孩子更小,则将child变量加一。child + 1 <size 用于保证不会越界
	//若建大堆,改为if ((a[child + 1] > a[child]) && (child + 1) < size)
    if ((a[child + 1] < a[child]) && (child + 1) < size) {
		++child;
	}
    //当child>=size时表示已经到达叶子结点跳出循环
	while (child<size) {
        //双亲结点大于孩子结点交换
        //若建大堆,改为if (a[parent] < a[child])
		if (a[parent] > a[child]) {
			Swap(&a[parent], &a[child]);
            //进行迭代
			parent = child;
			child = parent * 2 + 1;
			if ((a[child + 1] < a[child]) && (child + 1) < size) {
				++child;
			}
		}
        //若双亲结点不在大于孩子结点,说明已经迭代结束,不在需要继续进行迭代
		else {
			break;
		}
	}
}

2.3 堆的插入

        堆的插入就是在堆中插入元素,直接在堆的顺序存储的最后一个位置插入元素,然后调用向上调整算法,对堆进行维护。具体算法如下:

void HeapPush(Heap* php, HPDataType x) {
	assert(php);
    //判断顺序表的容量,是否需要扩容
	if (php->capacity == php->size) {
		int newCapacity = php->capacity == 0 ? 4 : 2 * (php->capacity);
		HPDataType* tmp = (HPDataType*)realloc(php->a,newCapacity * sizeof(HPDataType));
		if (tmp == NULL) {
			perror("realloc failed:");
			exit(1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;
    //调用向上调整算法
	AdjustUp(php->a, php->size);
	php->size++;
}

2.4 堆的删除 

        堆的删除就是将堆顶的元素弹出,然后使用向下调整算法对堆进行维护。具体算法如下:

void HeapPop(Heap* php) {
	assert(php);
	assert(php->a > 0);
    //将最后一个元素调换在堆顶元素位置,便于进行向下调整算法
	php->a[0] = php->a[php->size - 1];
	php->size--;
    //调用向下调整算法
	AdjustDown(php->a, php->size, 0);
}

2.5 堆的实现

        以下为堆的所有代码:

        Heap.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef int HPDataType;

typedef struct Heap {
	HPDataType* a;
	int size;
	int capacity;
}Heap;

void HeapInit(Heap* php);
void HeapDestroy(Heap* php);
void HeapPush(Heap* php, HPDataType x);
void HeapPop(Heap* php);
HPDataType HeapTop(Heap* php);
int HeapSize(Heap* php);
bool HeapEmpty(Heap* php);

void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int size, int parent);
void Swap(HPDataType* p1, HPDataType* p2);

        Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

void HeapInit(Heap* php) {
	assert(php);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

void HeapDestroy(Heap* php) {
	assert(php);
	free(php->a);
	php->capacity = php->size = 0;
}

void Swap(HPDataType* p1, HPDataType* p2) {
	HPDataType* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
 
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;
		}
	}
}

void HeapPush(Heap* php, HPDataType x) {
	assert(php);
	if (php->capacity == php->size) {
		int newCapacity = php->capacity == 0 ? 4 : 2 * (php->capacity);
		HPDataType* tmp = (HPDataType*)realloc(php->a,newCapacity * sizeof(HPDataType));
		if (tmp == NULL) {
			perror("realloc failed:");
			exit(1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;
	AdjustUp(php->a, php->size);
	php->size++;
}

//建小堆的向下调整算法
void AdjustDown(HPDataType* a, int size, int parent) {
	//左孩子结点与双亲结点的关系
	int child = parent * 2 + 1;
	//假若右孩子比左孩子更小,则将child变量加一。
	if ((a[child + 1] < a[child]) && (child + 1) < size) {
		++child;
	}
	//当child>=size时表示已经到达叶子结点跳出循环
	while (child < size) {
		//双亲结点大于孩子结点交换
		if (a[parent] > a[child]) {
			Swap(&a[parent], &a[child]);
			//进行迭代
			parent = child;
			child = parent * 2 + 1;
			if ((a[child + 1] < a[child]) && (child + 1) < size) {
				++child;
			}
		}
		//若双亲结点不在大于孩子结点,说明已经迭代结束,不在需要继续进行迭代
		else {
			break;
		}
	}
}

void HeapPop(Heap* php) {
	assert(php);
	assert(php->a > 0);
	php->a[0] = php->a[php->size - 1];
	php->size--;
	AdjustDown(php->a, php->size, 0);
}

HPDataType HeapTop(Heap* php) {
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

int HeapSize(Heap* php) {
	assert(php);
	return php->size;
}

bool HeapEmpty(Heap* php) {
	assert(php);
	return php->size == 0;
}

        test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

int main() {
	int a[] = { 4,6,2,1,5,8,2,9 };
	Heap hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); ++i) {
		HeapPush(&hp, a[i]);
	}
	for (int i = 0; i < sizeof(a) / sizeof(int); ++i) {
		printf("%d ", hp.a[i]);
	}
    printf("\n");
	while (!HeapEmpty(&hp)) {
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	return 0;
}

        测试结果:

3.堆排序

        在上文中,已经较为介绍了堆的实现,现在实现堆的应用之一:堆排序。下午以降序排序为例进行介绍。

        对于堆的作用,我们在上文可以很清楚的得知,在小堆中,堆顶元素就是整个数据中最小的数据,但是对于后面的数据,并不一定呈现有序的方式排列,所以只能保证小堆的堆顶元素为最小值。所以若要将一组数据进行排序,其主要思路是先将其建堆,然后将堆顶元素放在数据最后,在将前n - 1个数据进行建堆,然后放到第n - 1个位置,以此循环递归,直至将数据排列完成。

        对于堆排序的排序原则为,降序排序为建大堆,升序排序为建小堆。以降序排序为例:当我们建立出小顶堆时,可以得出整列数据中最小的值,然后将最小值一致顺序表的最后位置,接着对前面的数据建堆,然后置换位置,以此迭代。若使用建大堆进行降序排列呢?每一次可以得到一个最大的值,然后将其放在第一个位置,对2 ~ n个位置的元素进行建堆。理论上可以,但是真正实行起来,对于第一个位置后的元素进行建堆这个步骤就会很麻烦,所以我们使用小堆进行降序排序。

        升序排序思路和上面基本一致,只有建小堆和大堆的区别。具体代码如下:

//降序排序
void HeapSort(HPDataType* a, int length) {
	// 建小堆
	for (int i = 1; i < length; i++) {
		AdjustUp(a, i);
	}
	//排序
	int end = length - 1;
	while (end > 0) {
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

int main() {
	int a[] = { 4,6,2,1,5,8,2,9 };
	HeapSort(a, sizeof(a) / sizeof(a[0]));
	for (int i = 0; i < sizeof(a) / sizeof(HPDataType); i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

        测试结果:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LNMP】云导航项目部署及环境搭建(复杂)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、项目介绍1.1项目环境架构LNMP1.2项目代码说明 二、项目环境搭建2.1 Nginx安装2.2 php安装2.3 nginx配置和php配置2.3.1 修改nginx文件2.3.2 修改vim /etc/p…

LemonSqueezy

信息收集 # nmap -sn 192.168.1.0/24 -oN live.nmap Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-08 11:22 CST Nmap scan report for 192.168.1.1 Host is up (0.00037s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap scan r…

微信小程序开发(实战案例):本地生活 - 列表页面开发(动态渲染处理)、节流防抖(节流阀应用)

文章目录 本地生活 - 列表页面开发一、将九宫格分类换成navigator组件二、动态设置商品列表页的 title三、动态渲染商品列表页面四、上拉触底加载数据五、添加Loading加载效果六、数据加载节流防抖处理 本地生活 - 列表页面开发 导入我们上次写的 本地生活 - 首页开发的项目 运…

2024年2月16日优雅草蜻蜓API大数据服务中心v1.1.1大更新-UI全新大改版采用最新设计ui·增加心率计算器·退休储蓄计算·贷款还款计算器等数接口

2024年2月16日优雅草蜻蜓API大数据服务中心v1.1.1大更新-UI全新大改版采用最新设计ui增加心率计算器退休储蓄计算贷款还款计算器等数接口 更新日志 前言&#xff1a;本次更新中途跨越了很多个版本&#xff0c;其次本次ui大改版-同步实时发布教程《带9.7k预算的实战项目layuiph…

Git笔记——4

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、操作标签 二、推送标签 三、多人协作一 完成准备工作 协作开发 将内容合并进master 四、多人协作二 协作开发 将内容合并进master 五、解决 git branch -a…

FreeRTOS任务创建过程详解

本篇文章及记录我在学习FreeRTOS中关于任务创建的详细过程的了解。希望我的分享能给你带来不一样的收获。 目录 一、任务创建的相关函数 二、任务初始化函数分析 三、任务堆栈初始化函数 四、添加任务到就绪列表 一、任务创建的相关函数 前面学了任务创建可以使用动态方法或…

C#学习(十四)——垃圾回收、析构与IDisposable

一、何为GC 数据是存储在内存中的&#xff0c;而内存又分为Stack栈内存和Heap堆内存 Stack栈内存Heap堆内存速度快、效率高结构复杂类型、大小有限制对象只能保存简单的数据引用数据类型基础数据类型、值类型- 举个例子 var c new Customer{id: 123,name: "Jack"…

自定义神经网络二之模型训练推理

文章目录 前言模型概念模型是什么&#xff1f;模型参数有哪些神经网络参数案例 为什么要生成模型模型的大小什么是大模型 模型的训练和推理模型训练训练概念训练过程训练过程中的一些概念 模型推理推理概念推理过程 总结 前言 自定义神经网络一之Tensor和神经网络 通过上一篇…

[深度学习]yolov9+deepsort+pyqt5实现目标追踪

【YOLOv9DeepSORTPyQt5追踪介绍】 随着人工智能技术的飞速发展&#xff0c;目标追踪在视频监控、自动驾驶等领域的应用日益广泛。其中&#xff0c;YOLOv9作为先进的目标检测算法&#xff0c;结合DeepSORT多目标追踪算法和PyQt5图形界面库&#xff0c;能够为用户提供高效、直观…

深度学习500问——Chapter01:数学基础

文章目录 前言 1.1 向量和矩阵 1.1.1 标量、向量、矩阵、张量之间的联系 1.1.2 张量与矩阵的区别 1.1.3 矩阵和向量相乘结果 1.1.4 向量和矩阵的范数归纳 1.1.5 如何判断一个矩阵为正定 1.2 导数和偏导数 1.2.1 导数偏导计算 1.2.2 导数和偏导数有什么区别 1.3 特征值和特征向量…

文献阅读:Transformers are Multi-State RNNs

文献阅读&#xff1a;Transformers are Multi-State RNNs 1. 内容简介2. 方法介绍 1. 基础回顾 1. RNN2. Transformer 2. Transformer解构 1. MSRNN2. Transformer 3. TOVA 1. 现有转换策略2. TOVA 3. 实验考察 & 结论 1. 实验设计2. 实验结果 1. LM2. 长文本理解3. 文本生…

(十三)【Jmeter】线程(Threads(Users))之tearDown 线程组

简述 操作路径如下: 作用:在正式测试结束后执行清理操作,如关闭连接、释放资源等。配置:设置清理操作的采样器、执行顺序等参数。使用场景:确保在测试结束后应用程序恢复到正常状态,避免资源泄漏或对其他测试的影响。优点:提供清理操作,确保测试环境的整洁和可重复性…

通天星CMSV6 车载视频监控平台信息泄露漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

元数据思想-打破传统的思维方式

本文已收录公众号(面汤放盐)&#xff1a;元数据思想-打破传统的思维方式 本文是一篇讲解元数据案例的技术文章; 同时也谈论如何对传统 CRUD 进行破局的文章。 元数据思想-打破传统的思维方式 打破传统的思维模式&#xff0c; 跳出固有的认知模型&#xff0c;从更高的视角去理…

社区分享|中华保险基于MeterSphere开展接口自动化测试

中华联合保险集团股份有限公司&#xff08;以下简称为“中华保险”&#xff09;始创于1986年&#xff0c;是全国唯一一家以“中华”冠名的国有控股保险公司。截至2022年12月底&#xff0c;中华保险总资产为1006.06亿元&#xff0c;在全国拥有超过2900个营业网点&#xff0c;员工…

Python入门必学:reverse()和reversed()的区别

Python入门必学&#xff1a;reverse()和reversed()的区别 &#x1f4c5;2024年02月25日 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程…

ABAP - Function ALV 05 添加选择框列、全选、取消全选

很多用户不习惯原生GRID的选择模式&#xff0c;所以业务需要用到自定义的选择框来进行数据的操作&#xff0c;显示效果如图所示&#xff0c;增加一条选择列&#xff0c;且配置全选和全选全选的按钮功能&#xff0c;如下图所示。 实现这种功能需要用到Fieldcat的参数控制以及GUI…

[02 git ] 清华大学电子系科协软件部2023暑期培训

本视频为清华大学电子系科协软件部2023年暑期培训内容的录屏&#xff0c;主要培训内容为游戏开发、网站建设中常用的软件工具&#xff0c;为未来一年软件部新部员维护科协网站、开发清华大学人工智能挑战赛&#xff08;THUAI&#xff09;作知识铺垫。本次培训还邀请到两位嘉宾讲…

Apache celeborn 安装及使用教程

1.下载安装包 https://celeborn.apache.org/download/ 测0.4.0时出现https://github.com/apache/incubator-celeborn/issues/835 2.解压 tar -xzvf apache-celeborn-0.3.2-incubating-bin.tgz 3.修改配置文件 cp celeborn-env.sh.template celeborn-env.shcp log4j2.xml.…

【PX4SimulinkGazebo联合仿真】在Simulink中使用ROS2控制无人机进入Offboard模式起飞悬停并在Gazebo中可视化

在Simulink中使用ROS2控制无人机进入Offboard模式起飞悬停并在Gazebo中可视化 系统架构Matlab官方例程Control a Simulated UAV Using ROS 2 and PX4 Bridge运行所需的环境配置PX4&Simulink&Gazebo联合仿真实现方法建立Simulink模型并完成基本配置整体框架各子系统实现…