堆排序(C语言版)

一.堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
升序:建大堆
降序:建小堆
2. 利用堆删除思想来进行排序

1.1.利用上下调整法实现堆排序

第一步:建堆

好了,每次建堆都要问自己下,要建的是什么堆?大堆还是小堆呢?

我们这里就一一来实现,先建大堆

void Print(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void Swap(int* p1, int* p2)
{
	int* tmp = *p1;
	* p1=*p2;
	*p2 = tmp;
}
void Adjustup(int* arr2, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr2[child]>arr2[parent])//注意我们是建的大堆
		{
			Swap(&arr2[child], &arr2[parent]);//传地址才能改变值
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void Heapsort(int* arr, int n)
{
	//建立堆
	//问题是:你是建大堆还是小堆?
	//我们这里要建大堆
	for (int i = 1; i < n; i++)
	{
		Adjustup(arr, i);//利用向上调整法
	}
	Print(arr,n);
}

如果你实现过堆的代码相信上面的代码对你来说绝对小菜一碟

由于我们直接调用了打印函数,那么我们来看看结果吧。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void Print(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void Swap(int* p1, int* p2)
{
	int* tmp = *p1;
	* p1=*p2;
	*p2 = tmp;
}
void Adjustup(int* arr2, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr2[child]>arr2[parent])//注意我们是建的大堆
		{
			Swap(&arr2[child], &arr2[parent]);//传地址才能改变值
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void Heapsort(int* arr, int n)
{
	//建立堆
	//问题是:你是建大堆还是小堆?
	//我们这里要建大堆
	for (int i = 1; i < n; i++)
	{
		Adjustup(arr, i);//利用向上调整法
	}
	Print(arr,n);
}
int main()
{
	int arr[] = { 4,6,2,1,5,8,2,9 };
	int sz = sizeof(arr) / sizeof(int);
	Heapsort(arr, sz);
	return 0;
}

结果:

这个是不是就满足了大堆

第二步:如何实现排序呢?(别看上面是从大到小排好的,这只是一个巧合,我们要学会正确的排序法)

如果你对此不清楚,那么我要开始表演了。

如果你想,我们是大堆,这说明最大的数即是堆顶,如果我交换数组首尾位置,然后这是不是不再是一颗完全二叉树了,那么如果我再通过向下调整法来排好,重复操作,是不是就会得到一个从小到大的数组,那么不就排好序了,想到这,相信你肯定联想到了堆的删除操作,下面就让我们利用堆的删除来实现它吧!


void Swap(int* p1, int* p2)
{
	int* tmp = *p1;
	* p1=*p2;
	*p2 = tmp;
}
void Adjustdown(int* arr3, int n, int parent)
{
	int child = parent * 2 + 1;//假设左孩子大
	//注意我们建的是大堆
	while (child < n)
	{
		if ((child + 1) < n && (arr3[child] < arr3[child + 1]))
		{
			child++;
		}
		if (arr3[parent] < arr3[child])
		{
			Swap(&arr3[parent], &arr3[child]);//交换父子位置
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}


//堆建好了,现在实现第二步:堆删除
int end = n - 1;
while (end > 0)
{
	//堆删除分以下几步:
	//第一步:首尾元素互换
	Swap(&arr[0], &arr[end]);
	//第二步:向下调整法调整树根
	Adjustdown(arr, end, 0);
	//第三步:删除堆尾
	end--;
}

这对于学过实现堆的你来说依然so easy。

那么就让我们整体检查代码的正确性:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void Print(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void Swap(int* p1, int* p2)
{
	int* tmp = *p1;
	* p1=*p2;
	*p2 = tmp;
}
void Adjustup(int* arr2, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr2[child]>arr2[parent])//注意我们是建的大堆
		{
			Swap(&arr2[child], &arr2[parent]);//传地址才能改变值
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void Adjustdown(int* arr3, int n, int parent)
{
	int child = parent * 2 + 1;//假设左孩子大
	//注意我们建的是大堆
	while (child < n)
	{
		if ((child + 1) < n && (arr3[child] < arr3[child + 1]))
		{
			child++;
		}
		if (arr3[parent] < arr3[child])
		{
			Swap(&arr3[parent], &arr3[child]);//交换父子位置
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void Heapsort(int* arr, int n)
{
	//建立堆
	//问题是:你是建大堆还是小堆?
	//我们这里要建大堆
	for (int i = 1; i < n; i++)
	{
		Adjustup(arr, i);//利用向上调整法
	}
	Print(arr,n);
	//堆建好了,现在实现第二步:堆删除
	int end = n - 1;
	while (end > 0)
	{
		//堆删除分以下几步:
		//第一步:首尾元素互换
		Swap(&arr[0], &arr[end]);
		//第二步:向下调整法调整树根
		Adjustdown(arr, end, 0);
		//第三步:删除堆尾
		end--;
	}
	Print(arr, n);
}
int main()
{
	int arr[] = { 4,6,2,1,5,8,2,9 };
	int sz = sizeof(arr) / sizeof(int);
	Heapsort(arr, sz);
	return 0;
}

结果:

如果你是要从大到小排序,操作如下:

建小堆-》堆的尾删

整体而言有三处改动:

1.在void Adjustup(int* arr2, int child)函数中这个语句要改变符号,因为是建小堆了。
        if (arr2[child]>arr2[parent])//
2.在void Adjustdown(int* arr3, int n, int parent)这个函数中这两处符号也要改变,原因是因为你现在是小堆,向下调整法肯定要调整
        if (arr3[child] < arr3[child + 1]))
        if (arr3[parent] < arr3[child])
 

整体如下:

//表示原来的语句

//arr2[child]>arr2[parent]
arr2[child]<arr2[parent]

//if ((child + 1) < n && (arr3[child] < arr3[child + 1]))
if ((child + 1) < n && (arr3[child] > arr3[child + 1]))


//if (arr3[parent] < arr3[child])
if (arr3[parent] > arr3[child])

改完之后结果如下:

现在我们就要对这个算法进行分析:

时间复杂度:建堆为O(NlogN)+选数O(N-1logN)

得出结果:O(NlogN)(非常牛逼的算法)

1.2.只利用向下调整法实现堆排序

大家看上面的代码是不是感觉一个排序要写这么麻烦好不方便啊,是的,我们其实可以只通过一个向下调整就可以实现堆排序,下面看看我的表演吧!

步骤还是和上面一样,其实就改变了建堆的过程,我们现在是通过向下调整法建堆。

看代码:

for (int i = ; i ; i)
{
	Adjustdown(arr,,);
}

我们就是要把上面的空缺填好,那么该如何写呢?

我要告诉你一个概念:向下调整建堆是从第一个非叶子节点开始调整,我们肯定要调整到0

所以我们可以这样写:

for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
	Adjustdown(arr,n,i);
}

其他部分不用改变,所以整体代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void Print(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void Swap(int* p1, int* p2)
{
	int* tmp = *p1;
	* p1=*p2;
	*p2 = tmp;
}
void Adjustdown(int* arr3, int n, int parent)
{
	int child = parent * 2 + 1;//假设左孩子大
	//注意我们建的是大堆
	while (child < n)
	{
		if ((child + 1) < n && (arr3[child] < arr3[child + 1]))
		{
			child++;
		}
		if (arr3[parent] < arr3[child])
		{
			Swap(&arr3[parent], &arr3[child]);//交换父子位置
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void Heapsort(int* arr, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		Adjustdown(arr,n,i);
	}
	Print(arr, n);
	//堆建好了,现在实现第二步:堆删除
	int end = n - 1;
	while (end > 0)
	{
		//堆删除分以下几步:
		//第一步:首尾元素互换
		Swap(&arr[0], &arr[end]);
		//第二步:向下调整法调整树根
		Adjustdown(arr, end, 0);
		//第三步:删除堆尾
		end--;
	}
	Print(arr, n);
}
int main()
{
	int arr[] = { 4,6,2,1,5,8,2,9 };
	int sz = sizeof(arr) / sizeof(int);
	Heapsort(arr, sz);
	return 0;
}

结果:

来我们该讨论该算法时间复杂度情况了

建堆O(N)+选数O(NlogN)

时间复杂度:O(N*logN)

注意:该写法不仅简单(比上面那种),而且效率也比上面的高。

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

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

相关文章

Jmeter的安装与快速使用(做并发测试)

1、了解 JMeter是一款开源的性能测试工具&#xff0c;它主要用于模拟多种负载条件下的应用程序或服务器的性能和功能。JMeter可以发送不同类型的请求&#xff0c;如HTTP、HTTPS、FTP、SOAP、REST等&#xff0c;并且可以模拟多种负载类型&#xff0c;例如并发用户、线程组、定时…

【Linux常用指令】用户管理

文章目录 Linux系统目录结构Linux用户和用户组用户管理概述用户账号和用户组用户概念用户组概念 Linux用户和组的关系 Linux用户管理添加用户 useradd选项修改用户 usermod用户账号口令管理passwd删除用户 userdel Linux用户组管理添加新组groupadd修改群组groupmod删除群组gro…

传统企业转型需要怎么做?

传统企业面临着巨大的压力和挑战。在这个变革的时代&#xff0c;转型成为了传统企业持续发展的必由之路。本文将探讨传统企业如何成功转型&#xff0c;从困境中找到新的机遇&#xff0c;实现蜕变。 一、明晰转型目标 在转型的初期&#xff0c;传统企业需要明确自己的转型目标。…

RabbitMQ(七)ACK 消息确认机制

目录 一、简介1.1 背景1.2 定义1.3 如何查看确认/未确认的消息数&#xff1f; 二、消息确认机制的分类2.1 消息发送确认1&#xff09;ConfirmCallback方法2&#xff09;ReturnCallback方法3&#xff09;代码实现方式一&#xff1a;统一配置a.配置类a.生产者c.消费者d.测试结果 …

如何保护企业数据安全

数据安全是通过采用一系列 IT 安全策略和程序来保护数字信息免遭泄露、盗窃和破坏的做法&#xff0c;这些策略和程序可以包括系统安全、设备管理、访问控制、审计、主动威胁搜寻、事件响应等。 组织存储和处理大量数据&#xff0c;例如财务记录、知识产权和客户的个人数据&…

免费邮件系统hMailServer本地部署并实现远程发送邮件

文章目录 前言1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 前言 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpola…

python基础-01

文章目录 前言一、python中的注释二、变量的数据类型1.Number&#xff08;数字&#xff09;2.Boolean&#xff08;布尔类型&#xff09;—— True 和 False3.String&#xff08;字符串&#xff09;4.List&#xff08;列表&#xff09;5.Tuple&#xff08;元组&#xff09;6.Dic…

wordpress在界面将站点地址直接修改为https导致上不去问题的解决办法

wordpress在界面将站点地址直接修改为https导致上不去问题的解决办法 #修改数据库yz_options

从零实现一套低代码(保姆级教程) --- 【12】实现左侧层级树并支持查看JSON

摘要 目前&#xff0c;我们还有最后一个小模块没有实现&#xff0c;那就是左侧的数据。 我们希望它能够展示整个页面的相关协议&#xff0c;其实也就是我们redux中管理的数据。我们希望能够通过可视化的方式看到它。 因为有时候我们想知道一个组件的具体信息&#xff0c;就可…

Golang http包实战:构建RESTful API

Golang http包实战&#xff1a;构建RESTful API 引言简介目的 Go语言http包简介功能概述基本组件 搭建基础Web服务器步骤指导代码示例创建简单的HTTP文件服务器步骤说明代码示例 设计RESTful API结构设计原则路由设计 实现RESTful API处理请求代码示例 中间件应用代码示例 错误…

从零开始配置pwn环境:CTF PWN 做题环境

前期在kali2023环境安装的pwndocker使用发现不好用&#xff0c;so找了网上配置好pwn环境的虚拟机。 GitHub - giantbranch/pwn-env-init: CTF PWN 做题环境一键搭建脚本 可以直接下载我配置好的Ubuntu 16.04&#xff0c;为VMware导出的ovf格式 链接&#xff1a;百度网盘 请输…

小兔鲜儿 uniapp - SKU 模块

目录 存货单位&#xff08;SKU&#xff09;​ 插件市场​ 下载 SKU 插件​ 使用 SKU 插件​ 插件类型问题​ 核心业务​ 渲染商品规格​ 打开弹窗交互​ 渲染被选中的值​ 存货单位&#xff08;SKU&#xff09;​ SKU 概念 存货单位&#xff08;Stock Keeping Unit&a…

55寸oled透明显示屏售价,受哪些因素影响

55寸OLED透明显示屏的售价受到多个因素的影响&#xff0c;包括以下几个方面&#xff1a; 尺寸和分辨率&#xff1a;OLED透明显示屏的尺寸和分辨率是决定价格的重要因素。较大的尺寸和较高的分辨率会增加制造成本和售价。 技术水平和制造工艺&#xff1a;OLED透明显示屏的技术水…

@Autowired和@Resource的区别是什么

引言 当涉及到Spring框架中的依赖注入时&#xff0c;Autowired和Resource是两个常用的注解。它们都可以用来注入Bean&#xff0c;并且在实际开发中经常被使用。然而&#xff0c;Autowired和Resource之间存在一些重要的区别&#xff0c;包括适用范围、注入规则和注解来源等方面…

电源模块电阻测试:万用表如何测量电源的电阻?

电阻是电路中常用的电子元件&#xff0c;它可以调节电压、限制电流&#xff0c;从而保护电路。电阻测试是电源模块的常规测试项目之一&#xff0c;常见的电阻测试方法是通过万用表来测量电阻阻值&#xff0c;具体如下&#xff1a; 一、两线法 适用于测量较大的电阻值&#xff0…

众和策略:沪指震荡跌0.21%,煤炭、电力等板块拉升,核电概念活跃

2日早盘&#xff0c;三大股指盘中震荡走低&#xff0c;创业板指跌逾1%&#xff0c;北证50指数逆市拉升&#xff1b;北向资金大幅流出。 到午间收盘&#xff0c;沪指跌0.21%报2968.7点&#xff0c;深成指跌0.91%&#xff0c;创业板指跌1.38%&#xff0c;北证50指数涨1.33%&…

成功安装Milvus!零基础Ubuntu部署安装Milvus教程

Milvus源码编译安装 Milvus源码编译安装Golang和C开发环境安装源码安装编译基础依赖&#xff1a;OpenBLAS安装Rust安装前置依赖下载源码更改安装脚本开始编译测试Milvus是否安装成功 遇到的问题问题1&#xff1a;问题2&#xff1a;问题3&#xff1a;问题4&#xff1a;问题5&…

java常用数据结构

List&#xff1a;ArrayList 和 LinkedList 1、ArrayList 和 LinkedList都是非线程安全 2、ArrayList 可以直接根据下表定位元素&#xff0c;查找速度快&#xff0c;但是修改元素慢&#xff1b;LinkedList 查找元素必须从第一个开始逐个查找&#xff0c;查找速度慢&#xf…

开关电源输入输出电压测试方法:如何用开关电源智能测试系统测试输入输出电压?

一、用万用表测量输入输出电压 1. 连接万用表到电路中 2. 将万用表调到直流电压挡&#xff0c;连接红表笔到开关电源正极&#xff0c;连接黑表笔到开关电源负极。 3. 打开电源&#xff0c;读取万用表显示的电压值。 二、用示波器测量输入输出电压 1. 连接示波器到电路中 2. 将示…

【索引的数据结构】第2章节:InooDB和MyISAM索引结构对比

目录结构 之前整篇文章太长&#xff0c;阅读体验不好&#xff0c;将其拆分为几个子篇章。 本篇章讲解 InnoDB 和 MyISAM 索引结构对比。 InnoDB 的 BTree 索引注意事项 根页面位置万年不变 上述我们在索引迭代的过程中&#xff0c;为了更佳形象的描述&#xff0c;所以将顺序…