【数据结构与算法】(18):树形选择排序:按照锦标赛的思想进行排序

news2024/9/20 16:14:59

🤡博客主页:Code_文晓

🥰本文专栏:数据结构与算法

😻欢迎关注:感谢大家的点赞评论+关注,祝您学有所成!


✨✨💜💛想要学习更多数据结构与算法点击专栏链接查看💛💜✨✨ 


在选择类排序中,除了我们以往学习过的简单选择排序和堆排序之外,比较重点的还有树形选择排序,因为这种排序在面试中也偶有出现,所以这节课我们也来讲一讲。

1.1 基本概念与算法描述

树形选择排序又叫锦标赛排序(Tournament Sort),是一种按照锦标赛的思想进行选择排序的方法。属于对简单选择排序的一种改进。

我们尝试描述一下树形选择排序算法:对n个记录的关键字进行两两比较。然后在其中 ⌈\frac{n}{2}⌉ 个较小者中再进行两两比较,如此重复,直到选出最小关键字(按从小到大排序)为止。

以数组 { 16,1,45,23,99,2,18,67,42,10 } 为例,参考图1。

图1从下向上观察,这是第一趟排序,目的是从所有数组中选出值最小的元素。我们尝试描述下具体的操作步骤。

  • 开始两两比较,于是元素16和1比较选择1,元素45和23比较选择23,元素99和2比较选择2,18和67比较选择18,42和10比较选择10。

  • 现在,选择出的元素1、23、2、18、10又进行两两比较,元素1和23比较选择1,元素2和18比较选择2,元素10没有比较的对象直接被选择。

  • 现在,选择出的元素1、2、10又进行两两比较,元素1和2比较选择1,元素10没有比较的对象直接被选择。

  • 现在,选择出的元素1、10又进行比较,选择1。最终这个1也是树形结构的树根,找个地方保存本趟排序的最小元素1。

接着,在树叶中把第一趟已经选择出的元素1标记为一个最大值 ∞(这表示元素1不可能在比较中被再次选中了),然后进行第二趟排序,如图2所示。

图2还是从下向上观察,这是第二趟排序,前面挑选出的最小值1已经找了个地方保存,这里直接把1的值修改为一个最大值∞,这样,对节点进行两两比较时,标记为最大值的节点就不可能被选中。第二趟排序需要进行什么比较呢?

  • 开始两两比较,元素16和最大值比较,选择元素16。元素45和23、99和2、18和67、42和10就不需要再次比较(因为第一趟排序比较过了)。

  • 现在,选择出的元素16和23比较,选择元素16。元素2和18,元素10同样因为第一趟比较过,不需要再次比较。

  • 现在,选择出的元素16和2比较,元素10同样因为第一趟比较过,不需要再次比较。

  • 现在,选择出的元素2、10进行比较,选择2。最终这个2也是树形结构的树根,找个地方保存本趟排序的最小元素2。

然后继续把第二趟中已经选择出的元素2标记为一个最大值,就可以开始第三趟排序,这里就不赘述了。

所以可以看到,经过一次(第一趟)的完全比较后,从第二趟开始就不再需要完全的两两比较,这样就达到了节省时间提高效率的目的,这就是树形选择排序相较于简单选择排序一个重大的改进之处。但是也应该看到,树形选择排序需要通过构造出二叉树这种树形结构来辅助排序,所以还需要辅助存储空间。

上述图1和图2意在阐述树形选择排序理论,理论上来说树形选择排序并不复杂。但若通过代码实现,则是需要构建一棵完全二叉树来实现对数据排序的。换句话说,图1和图2绘制得比较简单,很多额外的节点并没有绘制出来。

回忆一下二叉树的性质5——具有n(n>0)个节点的完全二叉树的高度为 ⌈log^{n+1}_{2}⌉ 或者 ⌊log^{n+1}_{2}⌋ +1。同时,你也需要知道,含有n个叶子节点的完全二叉树的高度是 ⌈log^{n+1}_{2}⌉ +1。以这个理论为指导(为了能够正确编写出代码),绘制一下更详细的树形选择排序示意图。依旧以数组 { 16,1,45,23,99,2,18,67,42,10 } 举例来解释树形选择排序。

  • 把该数组中的所有元素都看成是完全二叉树的叶子,根据“含有n个叶子节点的完全二叉树的高度是 ⌈log^{n}_{2}⌉ +1”,树形选择排序所要创建的这棵完全二叉树高度应该是5。

  • 第一趟,两两比较,找到最小值保存到根节点中,如图3所示。

  • 接着,沿着根节点向叶子节点找,找到了最小值1所在的叶子节点,把该叶子节点的值从原来保存的1修改为最大值 ∞,如图4所示。

  • 接着要开始第二趟比较了,第二趟比较时叶子节点之间不再需要两两比较,只需要16和∞作比较,此时当然是16更小,于是,沿着这个比较路线再前进到树根,就能把当前树中的最小节点找到并保存到根中。如图5所示。

  • 接着,沿着根节点向叶子节点找,找到了最小值2所在的叶子节点,把该叶子节点的值从原来保存的2修改为最大值 ∞,如图6所示。

持续上述步骤,就可以把整个数据序列按从小到大的顺序排列好。

1.2 实现代码

下面我给出树形选择排序的实现代码。

#define INT_MAX_MY 2147483647//整型能够保存的最大数值,作为标记使用
//树形选择排序(从小到大)
template<typename T>
void TreeSelSort(T myarray[], int length)
{
	//ceil是系统函数:ceil(x)函数返回的是大于或等于x的最小整数
	int treelvl = (int)ceil(log(length) / log(2)) + 1; //5:完全二叉树高度(含有n个叶子节点的完全二叉树的高度是⌈logn⌉ +1)

	//treelvl高的完全二叉树最多有nodecount个节点,如果有nodecount个节点,此时的完全二叉树其实是满二叉树
	int nodecount = (int)pow(2, treelvl) - 1; //31:满二叉树是指一棵高度为h,且含有2h-1个节点的二叉树

	//treelvl-1 高的完全二叉树最多有nodecount2个节点
	int nodecount2 = (int)pow(2, treelvl - 1) - 1; //15

	int* pidx = new int[nodecount];//保存节点的下标用的内存

	//叶子节点保存元素的下标值(就等于保存了元素的值)
	for (int i = 0; i < length; ++i)
	{
		pidx[nodecount2 + i] = i; //pidx[15] = 0; pidx[16] = 1....;pidx[24] = 9
	} //end for

	//给多余的叶子节点赋予一个最大值作为标记
	for (int i = nodecount2 + length; i < nodecount; ++i) //i=25~30
	{
		pidx[i] = INT_MAX_MY;  //pidx[25] = MAX;pidx[26] = MAX; ......pidx[30] = MAX
	}

	int tmpnode2 = nodecount2;  //15
	int tmpnode = nodecount;    //31

	//现在要开始给非叶子节点赋值了,非叶子节点下标是[0]~[14]
	//第一趟排序要给非叶子节点赋值,还要两两进行节点比较,所以要单独处理
	while (tmpnode2 != 0)
	{
		//第一次for执行i值分别为:15、17、19、21、23、25、27、29
		//第二次for执行i值分别为:7,9,11,13
		//第三次for执行i值分别为:3,5
		//第四次for执行i值分别为:1
		for (int i = tmpnode2; i < tmpnode; i += 2)
		{
			//第一次for这个pidx的下标【(i + 1) / 2 - 1】分别是7,8,9,10,11,12,13,14
			//第二次for这个pidx的下标【(i + 1) / 2 - 1】分别是3,4,5,6
			//第三次for这个pidx的下标【(i + 1) / 2 - 1】分别是1,2
			//第四次for这个pidx的下标【(i + 1) / 2 - 1】分别是0
			//把两个孩子中小的孩子值给爹
			if (pidx[i] != INT_MAX_MY && pidx[i + 1] != INT_MAX_MY)  //如果pidx[i]和pidx[i+1]都是正常值,那自然是可以比较
			{
				if (myarray[pidx[i]] <= myarray[pidx[i + 1]])
				{
					pidx[(i + 1) / 2 - 1] = pidx[i];
				}
				else
				{
					pidx[(i + 1) / 2 - 1] = pidx[i + 1];
				}
			}
			else if( pidx[i] != INT_MAX_MY) //pidx[i]是正常值,因为有上个if在,说明pidx[i + 1]不是正常值
			{
				pidx[(i + 1) / 2 - 1] = pidx[i];
			}
			else //走到这里,说明pidx[i + 1]是正常值或者是INT_MAX_MY值
			{
				pidx[(i + 1) / 2 - 1] = pidx[i + 1];
			}
		} //end for
		tmpnode = tmpnode2;  //15,7,3,1
		tmpnode2 = (tmpnode2 - 1) / 2; //7,3,1,0
	} //end while

	T* ptmparray = new T[length]; //临时保存排好序的数据

	for (int i = 0; i < length; i++)
	{
		ptmparray[i] =  myarray[pidx[0]]; //将当前最小值赋给ptmparray[i]临时保存

		int leafidx = 0;

		//沿树根找最小值结点在叶子中的序号
		//leafidx = 0,1,3,7,16分别追溯到叶子中的编号
		for (int j = 1; j < treelvl; j++)
		{
			if (pidx[2 * leafidx + 1] == pidx[leafidx])
			{
				leafidx = 2 * leafidx + 1;
			}
			else
			{
				leafidx = 2 * leafidx + 2;
			}
		} //end for j

		//此时的leafidx就是完全二叉树叶子节点中的那个最小值的下标
		pidx[leafidx] = INT_MAX_MY; //leafidx = 16。
		while (leafidx)
		{
			//leafidx = 7,3,1,0
			leafidx = (leafidx + 1)/2 - 1;//序号为leafidx的结点的双亲结点序号
			if (pidx[2 * leafidx + 1] != INT_MAX_MY && pidx[2 * leafidx + 2] != INT_MAX_MY)  //如果pidx[i]和pidx[i+1]都是正常值,那自然是可以比较
			{
				if (myarray[ pidx[2 * leafidx + 1]] <= myarray[pidx[2 * leafidx + 2]])
				{
					pidx[leafidx] = pidx[2 * leafidx + 1];
				}
				else
				{
					pidx[leafidx] = pidx[2 * leafidx + 2];
				}
			}
			else if (pidx[2 * leafidx + 1] != INT_MAX_MY)
			{
				pidx[leafidx] = pidx[2 * leafidx + 1];
			}
			else
			{
				pidx[leafidx] = pidx[2 * leafidx + 2];
			}
		}//end while
	} //end for i

	//把数据从ptmparray拷贝回myarray
	for (int i = 0; i < length; i++)
	{
		myarray[i] = ptmparray[i];
	} //end for i

	//释放内存
	delete[] ptmparray;
	delete[] pidx;
	return;
}

在main主函数中,加入测试代码。

int arr[] = {16,1,45,23,99,2,18,67,42,10};
int length = sizeof(arr) / sizeof(arr[0]);   //数组中元素个数
TreeSelSort(arr, length);//对数组元素进行树形选择排序
cout <<"树形选择排序结果为:";
for (int i = 0; i < length; ++i)
{
	cout << arr[i] <<"";
}
cout << endl; //换行

代码的执行结果如下:

        树形选择排序算法因为含有n个叶子节点的完全二叉树的高度是 ⌈log^{n}_{2}⌉ +1,除了最小关键字外,每次选择其他最小关键字只需要 ⌈log^{n}_{2}⌉ 次比较,因为还有 n-1 个关键字需要进行这个次数的比较,所以可以认为该算法的时间复杂度是 O(nlog^{n}_{2})。

        对于算法的空间复杂度,在上述实现代码中,是需要一些辅助空间帮忙实现排序的(空间换时间),比如存储完全二叉树节点,还可能需要存储其他一些数据比如临时的排好序的数据。当然,也可以用其他办法,而不是必须用临时空间保存排好序的数据,不过总体来看,树形选择排序的空间复杂度为O(n)。

        此外,经过我测试,认为上述算法的实现代码是稳定的。如果你稍微调整一下其实现代码,改为不稳定的也很容易。

1.3 小结

        这节课我们一起学习了选择类排序中的树形选择排序。树形选择排序是一种按照锦标赛的思想进行选择排序的方法,属于对简单选择排序的一种改进。它会通过多趟排序来对 n 个记录的关键字进行两两比较,然后在其中 ⌈\frac{n}{2}⌉ 个较小者中再进行两两比较,如此重复,直到选出最小关键字(按从小到大排序)为止。

        树形选择排序的每一趟排序都会减少需要两两比较的元素数量,从而达到了节省时间提高效率的目的,这就是树形选择排序相较于简单选择排序一个重大的改进之处。 但是我们也应该看到,树形选择排序需要通过构造出二叉树这种树形结构来辅助排序,所以还需要辅助存储空间。

        这篇文章我们也详细解释了树形选择排序的概念,通过多个示意图对该排序的算法进行了详尽的描述,也为你提供了完整的实现代码。最后强调一个细节,树形选择排序算法的时间复杂度是 O(nlog^{n}_{2}),空间复杂度为 O(n),算法是稳定的。

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

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

相关文章

yaml 语法和在线解析工具

文章目录 在线解析工具1. 简介2. 语法规则3. 数据类型3.1 数组&#xff1a;3.2对象&#xff1a;3.3 标量3.4 复合结构3.5 锚点3.5.1 单个锚点3.5.6 多个锚点 3.6 引号 参考 在线解析工具 工具1 工具2 1. 简介 Yaml是一种可读性高的数据标记语言&#xff0c;Yaml文件是一种配…

6 修改主机名和HOSTS文件

后期我们会配置多台服务器&#xff0c;那么每台服务器我们都会给定一个主机名&#xff0c;方便后期通过主机名进行访问。主机名的修改我们可以在安装操作系统时对其修改&#xff0c;如果忘记了&#xff0c;就可以修改配置文件完成&#xff0c;像后期我们进行虚拟机克隆后&#…

Docker常用命令练习

文章目录 Docker常用命令练习1.docker 基础命令2.镜像命令3.保存镜像4.加载镜像5.容器命令6.环境变量7. --rm8. --networkhost Docker常用命令练习 1.docker 基础命令 安装docker yum install docker启动docker systemctl start docker关闭docker systemctl stop docker重…

LeetCode-热题100:17.电话号码的字母组合

题目描述 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a; digits “23” 输出&a…

2024年【P气瓶充装】复审模拟考试及P气瓶充装操作证考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 P气瓶充装复审模拟考试根据新P气瓶充装考试大纲要求&#xff0c;安全生产模拟考试一点通将P气瓶充装模拟考试试题进行汇编&#xff0c;组成一套P气瓶充装全真模拟考试试题&#xff0c;学员可通过P气瓶充装操作证考试全…

【阿里云物联网】上报设备数据

前言 MQTT客户端上传数据到阿里云服务端&#xff0c;并且能将数据显示出来。在此之前&#xff0c;我们先要懂得阿里云给设备管理划分的概念。首先是产品&#xff0c;所以在产品里要配置内容&#xff0c;产品下的设备才可以使用&#xff0c;比如主题大类都是在产品里面就可以查…

使用 Amazon SageMaker 微调 Llama 2 模型

本篇文章主要介绍如何使用 Amazon SageMaker 进行 Llama 2 模型微调的示例。 这个示例主要包括: Llama 2 总体介绍Llama 2 微调介绍Llama 2 环境设置Llama 2 微调训练 前言 随着生成式 AI 的热度逐渐升高&#xff0c;国内外各种基座大语言竞相出炉&#xff0c;在其基础上衍生出…

I2C芯片24C02/4/8/16(EEPROM)解读

一.原理图 24C01的硬件连接图如下&#xff1a; 二.24C0x系列芯片规格 三.24C0x芯片结构 下面简述EEPROM内部存储结构。 3.1 内部存储结构 根据24C02芯片的Datasheet描述&#xff0c;其内部存储结构应该如下图所示。 其它容量的EEPROM内部结构依此类推。 3.2 地址 3.2.1 器件…

BitMap介绍与应用

文章目录 BitMapBitMap介绍BitMap 结构RoaringBitmap 常见BitMapJava中的BitSetRedis中的BitMapClickHouse中的BitMap BitMap应用案例人群圈选 BitMap 场景一&#xff1a;(大部分开发面试都会遇到的一个问题&#xff09; 有10亿个用户id (int类型)&#xff0c;判断用户是否登…

Vue el-table 合并单元格

一般常见的就是下图这种的单列&#xff0c;上下重复进行合并。 有时候可能也会需要多行多列的合并。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content&qu…

【LeetCode】--- 动态规划 集训(一)

目录 一、1137. 第 N 个泰波那契数1.1 题目解析1.2 状态转移方程1.3 解题代码 二、面试题 08.01. 三步问题2.1 题目解析2.2 状态转移方程2.3 解题代码 三、746. 使用最小花费爬楼梯3.1 题目解析3.2 状态转移方程3.3 解题代码 一、1137. 第 N 个泰波那契数 题目地址&#xff1a…

FloodFill算法——岛屿数量

文章目录 题目解析算法解析代码解析 题目解析 岛屿数量 题目依旧是熟悉的配方&#xff0c;熟悉的味道&#xff0c;还是那个0还是那个1还是那个二维矩阵&#xff0c;这时候BFS和DFS闻着味就来了&#xff0c;我们来看一下这个题目&#xff0c;这个题目也很容易理解如下图有一个…

阿里云2核4G服务器租用价格和性能测评

阿里云2核4G服务器租用优惠价格&#xff0c;轻量2核4G服务器165元一年、u1服务器2核4G5M带宽199元一年、云服务器e实例30元3个月&#xff0c;活动链接 aliyunfuwuqi.com/go/aliyun 活动链接如下图&#xff1a; 阿里云2核4G服务器优惠价格 轻量应用服务器2核2G4M带宽、60GB高效…

市场复盘总结 20240322

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 36% 最常用…

力扣题库27题移除元素(c语言)

解法&#xff1a; int removeElement(int* nums, int numsSize, int val) {int src0,dst0;while(src<numsSize){if(nums[src]val){src;}else{nums[dst]nums[src];src;dst;}}return dst; }

SCI一区 | Matlab实现PSO-TCN-BiGRU-Attention粒子群算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现PSO-TCN-BiGRU-Attention粒子群算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现PSO-TCN-BiGRU-Attention粒子群算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述…

visual studio卸载几种方法

1、控制面板卸载&#xff1b; 2、有时候会发现控制面板卸载会失败&#xff0c;无法卸载&#xff0c;这时候要先把下面目录的关于visual studio的都删除&#xff0c;然后重启电脑后&#xff0c;重新安装vs即可。

C语言预编译#pragma宏的作用

在嵌入式编程中&#xff0c;#pragma 指令具有非常重要的作用&#xff0c;因为它允许开发者在不同的编译器之间传达特定的编译指令。由于嵌入式编程通常与硬件紧密相关&#xff0c;且资源有限&#xff0c;这些指令可以帮助开发者更有效地利用可用资源&#xff0c;优化程序&#…

基于python+vue的stone音乐播放器的设计与实现flask-django-php-nodejs

随着我国经济的高速发展与人们生活水平的日益提高&#xff0c;人们对生活质量的追求也多种多样。尤其在人们生活节奏不断加快的当下&#xff0c;人们更趋向于足不出户解决生活上的问题&#xff0c;stone音乐播放器展现了其蓬勃生命力和广阔的前景。与此同时&#xff0c;为解决用…

docker快速安装达梦数据库

docker快速安装达梦数据库 文章目录 docker快速安装达梦数据库前言环境准备下载镜像运行、配置容器 前言 因为公司需要将自己的底代码平台与客户的需求做适配&#xff0c;客户要求必须满足信创要求&#xff0c;使用达梦数据库。所以需要将原有的MySQL数据库与达梦数据库适配&a…