【数据结构取经之路】建堆堆排序

目录

引言

建堆的两种方法

一、向上调整建堆

二、向下调整建堆

两种建堆方式的性能比较

堆排序

堆排序的思想

堆排序的时间复杂度

堆排序的空间复杂度

堆排序代码


 

引言

首先,介绍一下本次的主人公——堆。堆是一种数据结构,在逻辑上是一棵二叉树,在物理上是一维数组。堆,可以作为堆排序的前提,还可以有效解决TopK问题。学会建堆是学会堆排序的必要前提,因次,在讲堆排序之前,我们先聊聊如何建堆。

建堆的两种方法

不管是向上调整算法还是向下调整算法,都是有一个重要前提的。如果要调成小堆,那么在插入数据之前,要保证原有的二叉树是小堆:如果要调成大堆,要保证在插入数据之前,原有的二叉树是大堆。

请看上图,预期是插入77后调为大堆。首先的明白一点,77只会沿着它的祖宗往上调直到根,也就是图中的红色线路,右边是调整完以后得到的二叉树,它根本不是大堆,原因就在于插入77之前原有的二叉树不是大堆,调完后,只有红色线路的满足大堆的大小关系,但其他部分并不满足。所以才会要求,插入数据前,原有二叉树是大堆。预期是调为小堆也同理。

一、向上调整建堆

向上调整建堆就是在模拟向堆插入数据的过程。下面将以建大堆为例。

第一个数据插入时,堆里只有一个元素,不需要调整,而且,单个结点既可以认为是大堆,也可以认为是小堆,满足向上调整的前提。当插入19时,因为19 > 11,所以需要向上调整,确保是大堆,插入9时,9 < 19,不需要调整 ,插入再多的数据也同理。因为堆的底层是用数组实现的,所以我们可以通过控制数组的下标来控制二叉树,即控制堆。请看代码:

#include <stdio.h>

void Print(int* a, int n)
{
	for (int i = 0; i < n; i++) printf("%d ", a[i]);
	printf("\n");
}

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

void AdjustUp(int* 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 BuildHeap(int* a, int n)
{
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}
}

int main()
{
	int a[] = { 5,1,8,5,};
	int n = sizeof(a) / sizeof(int);
	Print(a, n);
	BuildHeap(a, n);
	Print(a, n);
	return 0;
}

二、向下调整建堆

以调成小堆为例。

如上图,52要向下调整建小堆的前提是,红圈内必须是小堆,即它的左右子树必须是小堆。 同时,还需注意,52是要和它较小的孩子比较的,如果它比它较小的孩子还小,就不用换下来,反之,则需要交换,直到不满足交换条件。

代码:

#include <stdio.h>

void Print(int* a, int n)
{
	for (int i = 0; i < n; i++) printf("%d ", a[i]);
	printf("\n");
}

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

void AdjustDown(int* a, int n, int parent)
{
	//假设左右孩子中小的那个为左孩子,如果假设错误,下面会修正
	int child = parent * 2 + 1;

	while (child < n)
	{
        //child + 1 < n,判断右孩子是否存在
	    if (child + 1 < n && a[child + 1] < a[child]) child++;

		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void BuildHeap(int* a, int n)
{
	//从最后一个节点的父亲开始向下调整
	for (int i = (n - 2) / 2; i >= 0; i--)AdjustDown(a, n, i);
}

int main()
{
	int a[] = { 4,1,7,4,7,9,0,1 };
	int n = sizeof(a) / sizeof(int);
	Print(a, n);
	BuildHeap(a, n);
	Print(a, n);
	return 0;
}

两种建堆方式的性能比较

上图的满二叉树一共有16个结点,其中,最后一层占了一半,倒数第二层占了四分之一,其他的满二叉树也是这种情况。 

向上调整算法:假设二叉高度为h,除去第一层不用向上调整,在最坏情况下,其余各层的每一个节点都需要向上调整。因为向上调整的次数与层数有关,所以设前h层需要调整次数的函数为F(h).

F(h) = 2 * 1 + 2^2 * 2 + ……+ 2^(h-1) * (h-1)  =  2^h * (h - 2) + 2  

h = log(N + 1)  ==> N = 2^h - 1 

得到近似值 F(N) = N*logN

所以,向上调整建堆的时间复杂度为:O(n*logn)  

向下调整算法:

F(h) = 2^(h - 2) * 1 + 2^(h-3) * 2 + ……+ 2 * (h - 2) + 2^0 * (h - 1)

h = log(N + 1)  ==> N = 2^h - 1 

F(N) = N - log(N + 1)

取近似值:F(N) = N

所以,向下调整建堆的时间复杂度为:O(n)

通过分析,我们可以看到,向上调整算法,节点数量越多,调整次数(层数越深)越多;向下调整算法,节点数量越多,调整次数越少。从这一角度定性分析,也可以得出向下调整算法较优的结论。

堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。

堆排序的思想

以上图为例,将堆顶元素与最后一个元素交换,此时,最大的元素已经被选出并放到了数组的尾部,接着,再调用向下调整算法,对0到n-1下标的元素进行向下调整,调成大堆后,继续将堆顶元素交换到尾部n-1的位置,此时选出了次大的数……重复上述调整交换的操作,直到只有一个元素。

升序建大堆,降序建小堆。下面以大堆为例说明原因。

大堆,每次均把堆顶的元素交换到了尾部。第一次交换时,把最大的交换到了下标为n-1的位置,第二次交换时,把次大的交换到了下标为n-2的位置,这样,待排完序后,数组就成了升序。归根结底,是因为有交换这一操作。降序用小堆同理。 

堆排序的时间复杂度

O(n*logn)

堆排序的空间复杂度

堆排序是一种原地排序算法,不需要额外的空间来存储数据,所以它的空间复杂度为O(1)

堆排序代码

#include <stdio.h>

void Print(int* a, int n)
{
	for (int i = 0; i < n; i++) printf("%d ", a[i]);
	printf("\n");
}

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;

	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child]) child++;

		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

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

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

int main()
{
	int a[] = { 4,1,8,4,6,0,1,7,3,9 };
	int n = sizeof(a) / sizeof(int);
	Print(a, n);
	HeapSort(a, n);
	Print(a,n);
	return 0;
}

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

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

相关文章

Java数据结构-优先级队列

文章目录 前言一、优先级队列1.1 概念 二、优先级队列的模拟实现2.1 堆的概念2.2 堆的存储方式2.3 堆的创建2.3.1 堆向下调整2.3.2 堆的创建2.3.3 建堆的时间复杂度 2.4 堆的插入与删除2.4.1 堆的插入2.4.2 堆的删除 2.5 用堆模拟实现优先级队列 三、常用接口介绍3.1 PriorityQ…

php 对接IronSource海外广告平台收益接口Reporting API

今天对接的是IronSource广告reporting api接口&#xff0c;拉取广告收益回来自己做统计。记录分享给大家 首先是文档地址,进入到Inmobi后台就能看到文档地址以及参数&#xff1a; 文档地址&#xff1a;https://developers.is.com/ironsource-mobile/air/reporting/ 在这里插入图…

5 个适用于 Windows 10 和 11 的最佳 PDF 转 Word 转换器

PDF 文件是共享文档的首选格式&#xff0c;但是此类文件存在一些限制&#xff0c;导致难以修改或编辑。因此&#xff0c;您可能会发现自己正在寻找一种将 PDF 文件转换为 Word 或其他可编辑格式的方法。 有许多不同的 PDF 转换器&#xff0c;每种转换器提供的功能略有不同。本…

代码+视频,R语言使用BOOT重抽样获取cox回归方程C-index(C指数)可信区间

bootstrap自采样目前广泛应用与统计学中&#xff0c;其原理很简单就是通过自身原始数据抽取一定量的样本&#xff08;也就是取子集&#xff09;&#xff0c;通过对抽取的样本进行统计学分析&#xff0c;然后继续重新抽取样本进行分析&#xff0c;不断的重复这一过程N&#xff0…

还原wps纯粹的编辑功能

1.关闭稻壳模板&#xff1a; 1.1. 启动wps(注意不要乱击稻壳模板&#xff0c;点了就找不到右键菜单了) 1.2. 在稻壳模板选项卡右击&#xff1a;选不再默认展示 2.关闭托盘中wps云盘图标&#xff1a;右击云盘图标/同步与设置&#xff1a; 2.1.关闭云文档同步 2.2.窗口选桌面应用…

BFS 最短路径

目录 原理剖析&#xff1a; 1、 1926. 迷宫中离入口最近的出口 2、 433. 最小基因变化 3、 127. 单词接龙 4、 675. 为高尔夫比赛砍树 原理剖析&#xff1a; 为什么BFS能够解决最短路径问题&#xff1f; 对于无权图(边权为1)或所有边权重相等的情况&#xff0c;BFS是一种有…

ASP.NET Mvc+FFmpeg+Video实现视频转码

目录 首先&#xff0c;做了视频上传的页面&#xff1a; FFmpeg&#xff1a;视频转码 FFmpegHelper工作类&#xff1a; 后台控制器代码&#xff1a; 前端视图代码&#xff1a; 参考文章&#xff1a; 首先&#xff0c;做了视频上传的页面&#xff1a; 借鉴了这篇文章 ASP.…

【pycharm】如何将pacharm设置成中文

【pycharm】汉化教程——如何将pacharm设置成中文 1、打开pycharm 2、点击file 3、点击setting——Plugins——搜索Chinese——点击如下图图标进行下载 汉化后界面情况&#xff1a;

【数据结构与算法】(13):交换排序之冒泡排序和快速排序

&#x1f921;博客主页&#xff1a;Code_文晓 &#x1f970;本文专栏&#xff1a;数据结构与算法 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多数据结构与算法点击专栏链接查看&…

生成器模式(软考uml C++版)

按照软考中级软件设计师中指定的生成器模式uml图&#xff0c;可编写对应的C&#xff0b;&#xff0b;代码&#xff1a; #include<iostream> #include<vector> #include<string> using namespace std;/*创建者模式&#xff0c;又名生成器模式意图&#xff1a…

每日五道java面试题之springMVC篇(四)

目录&#xff1a; 第一题. Spring MVC怎么样设定重定向和转发的&#xff1f;第二题.Spring MVC怎么和AJAX相互调用的&#xff1f;第三题. 如何解决POST请求中文乱码问题&#xff0c;GET的又如何处理呢&#xff1f;第四题. Spring MVC的异常处理&#xff1f;第五题. 如果在拦截请…

【JWT】入门 *JWT*,并封装一个实用的 *JWT* 工具类

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 【JWT】入门 *JWT*&#xff0c;并封装一个实用…

SQLiteC/C++接口详细介绍之sqlite3类(八)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;七&#xff09; 下一篇&#xff1a; SQLiteC/C接口详细介绍之sqlite3类&#xff08;八&#xff09;&#xff08;暂未发表&#xff09; 24.sqlite3_cr…

网络安全msf学习1

工具&#xff1a;netcat 用途 &#xff1a;端口连接、数据提交 工具nmap 用途&#xff1a;端口扫描、服务识别、操作系统指纹识别 工具 httprint 用途&#xff1a;通过远程http指纹判断http服务类型 工具&#xff1a; tamper ie 用途&#xff1a; http数据包修改、转发工…

SpringMVC基础之工作流程

文章目录 SpringMVC 的工作流程1. 总图2. DispatcherServlet3. 必需的配置4. 加载配置文件的两个时机5. 定义控制器6. 创建 JSP 视图 SpringMVC 的工作流程 1. 总图 如上图&#xff0c;Spring MVC 程序的完整执行流程如下&#xff1a; 用户通过浏览器发送请求&#xff0c;请求…

怎样提升小程序日活?签到抽奖可行吗?

一、 日活运营策略 小程序应该是即用即走的&#xff0c;每个小程序都在用户中有自己的独特定位&#xff0c;可能是生活日常必备&#xff08;美食、团购、商城&#xff09;&#xff0c;也可能是工作办公必备&#xff08;文档、打卡、工具&#xff09;。 如果你想要让自己的小程…

sqllab第十九关通关笔记

知识点&#xff1a; 错误注入 最大长度为32位&#xff1b;如果目标长度>32时&#xff0c;需要利用截取函数进行分段读取referer注入 insert语句update语句 通过admin admin进行登录发现页面打印除了referer字段的信息 这应该是一个referer注入 首先进行测试一下 构造payl…

实现elasticsearch和数据库的数据同步

1. 数据同步 elasticsearch中的酒店数据来自于mysql数据库&#xff0c;因此mysql数据发生改变时&#xff0c;elasticsearch也必须跟着改变&#xff0c;这个就是elasticsearch与mysql之间的数据同步。 1.1. 思路分析 常见的数据同步方案有三种&#xff1a; 同步调用 异步通知…

在macOS上安装Homebrew教程

1.打开终端&#xff1a; 打开Finder&#xff0c;转到应用程序 > 实用工具文件夹&#xff0c;然后双击终端.app。 或者&#xff0c;使用Spotlight搜索&#xff08;按下 Command(⌘) Spacebar&#xff09;并输入“终端”&#xff0c;然后回车以打开。 也可以像我一样把终端…

【SQL Server】实验五 视图

1 实验目的 掌握SQL视图语句的基本使用方法&#xff0c;如CREATE VIEW、DROP VIEW。掌握视图更新、WITH CHECK OPTION等高级功能的使用。 2 实验内容 2.1 掌握SQL视图语句的基本使用方法 创建视图&#xff08;省略视图列名&#xff09;。创建视图&#xff08;不能省略列名的…