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

🤡博客主页: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; //15int* 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;  //15int 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值分别为:1for (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 fortmpnode = tmpnode2;  //15,7,3,1tmpnode2 = (tmpnode2 - 1) / 2; //7,3,1,0} //end whileT* 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,0leafidx = (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拷贝回myarrayfor (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.mzph.cn/news/763756.shtml

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

相关文章

【系统架构师】-计算机网络

1、网络的划分 网络性能指标&#xff1a;速率、带宽(频带宽度或传送线路速率)、吞吐量、时延、往返时间、利用率。 网络非性能指标&#xff1a;费用、质量、标准化、可靠性、可扩展性、可升级性、易管理性和可维护性。 总线型(利用率低、干扰大、价格低)、 星型(交换机转发形…

【并查集专题】【蓝桥杯备考训练】:网络分析、奶酪、合并集合、连通块中点的数量、格子游戏【已更新完成】

目录 1、网络分析&#xff08;第十一届蓝桥杯省赛第一场C A组/B组&#xff09; 2、奶酪&#xff08;NOIP2017提高组&#xff09; 3、合并集合&#xff08;模板&#xff09; 4、连通块中点的数量&#xff08;模板&#xff09; 5、格子游戏&#xff08;《信息学奥赛一本通》…

flink1.18.0报错 an implicit exists from scala.Int => java.lang.Integer, but

完整报错 type mismatch;found : Int(100)required: Object Note: an implicit exists from scala.Int > java.lang.Integer, but methods inherited from Object are rendered ambiguous. This is to avoid a blanket implicit which would convert any scala.Int to a…

【Java反序列化】CommonsCollections-CC1链分析

前言 好几天没发博文了&#xff0c;偷偷憋了个大的——CC1链分析&#xff0c;手撸了一遍代码。虽然说&#xff0c;这个链很老了&#xff0c;但还是花费了我一段时间去消化吸收&#xff0c;那么接下来&#xff0c;我会简洁的介绍下整个链的利用过程&#xff0c;还有哪些不理解的…

初识C++(一)

目录 一、什么是C 二、关键字&#xff1a; 三、命名空间 &#xff1a; 1. C语言存在的问题&#xff1a; 2. namespace关键字&#xff1a; 3. 注意点&#xff1a; 4.使用命名空间分为三种&#xff1a; 四、输入输出&#xff1a; 五、缺省函数&#xff1a; 1. 什么是缺省…

【Linux】进程地址空间——有这篇就够了

前言 在我们学习C语言或者C时肯定都听过老师讲过地址的概念而且老师肯定还会讲栈区、堆区等区域的概念&#xff0c;那么这个地址是指的物理内存地址吗&#xff1f;这里这些区域又是如何划分的呢&#xff1f; 我们在使用C语言的malloc或者C的new函数开辟空间时&#xff0c;开辟…

栈——数据结构——day4

栈的定义 栈是限定仅在一段进行插入和删除操作的线性表。 我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表&#xff0c;简称LIFO结构。 栈的插入操作&#xff0c;叫作进栈&#…

【收藏】什么是API测试?这是我见过的最全的测试指南!

在最近的部署中&#xff0c;当我被问到“什么是API测试&#xff1f;”时&#xff0c;我正与客户一起制定API测试策略。那时我突然意识到&#xff0c;要描述API测试居然是一件如此具有挑战性的事情&#xff0c;即使你如实地描述了它&#xff0c;也往往听起来很无聊和复杂。 好吧…

第十二届蓝桥杯省赛CC++ 研究生组

十二届省赛题 第十二届蓝桥杯省赛C&C 研究生组-卡片 第十二届蓝桥杯省赛C&C 研究生组-直线 第十二届蓝桥杯省赛C&C 研究生组-货物摆放 第十二届蓝桥杯省赛C&C 研究生组-路径 第十二届蓝桥杯省赛C&C 研究生组-时间显示 第十二届蓝桥杯省赛C&C 研究生组…

AI PPT生成工具 V1.0.0

AI PPT是一款高效快速的PPT生成工具&#xff0c;能够一键生成符合相关主题的PPT文件&#xff0c;大大提高工作效率。生成的PPT内容专业、细致、实用。 软件特点 免费无广告&#xff0c;简单易用&#xff0c;快速高效&#xff0c;提高工作效率 一键生成相关主题的标题、大纲、…

TCP | TCP协议格式 | 三次握手

1.TCP协议 为什么需要 TCP 协议 &#xff1f;TCP 工作在哪一层&#xff1f; IP网络层是不可靠的&#xff0c;TCP工作在传输层&#xff0c;保证数据传输的可靠性。 TCP全称为 “传输控制协议&#xff08;Transmission Control Protocol”&#xff09;。 TCP 是面向连接的、可靠…

YOLOV9训练自己的数据集

1.代码下载地址GitHub - WongKinYiu/yolov9: Implementation of paper - YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information 2.准备自己的数据集 这里数据集我以SAR数据集为例 具体的下载链接如下所示&#xff1a; 链接&#xff1a;https:/…

备战蓝桥杯Day34 - 每日一题

题目描述 解题思路 1.输入数据n&#xff0c;并将字符串类型转换成整数类型 2.求出输入n是2的几次幂&#xff08;调用math库中的求对数的方法&#xff09;&#xff0c;在下面的循环中要用到 3.定义sum和&#xff0c;将抽取到的牌的总数加起来存储 4.count 0 # 记录 2 的第几…

算法打卡day20|二叉树篇09|Leetcode 669. 修剪二叉搜索树、108.将有序数组转换为二叉搜索树、538.把二叉搜索树转换为累加树

算法题 Leetcode 669. 修剪二叉搜索树 题目链接:669. 修剪二叉搜索树 大佬视频讲解&#xff1a;修剪二叉搜索树视频讲解 个人思路 把这道题想复杂了&#xff0c;还想着如何去重构树 解法 递归法 依旧递归三步走 1.确定递归函数的参数以及返回值 这题递归需要返回值&#…

探索人工智能基础:从概念到应用【文末送书-42】

文章目录 人工智能概念人工智能基础【文末送书-42】 人工智能概念 人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;作为当今科技领域的热门话题&#xff0c;已经深刻地影响着我们的生活和工作。但是&#xff0c;要理解人工智能&#xff0c;我们首先需…

【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器

【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器 文章目录 【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器一、环境说明二、安装配置步骤1.下载完成后&#xff0c;传至服务器或者本机并解压至C:/Program Files/目录下2.打开PowerShell终端3.进入到包含ssh可执行exe文件的文…

物联网和工业物联网的区别——青创智通

工业物联网解决方案-工业IOT-青创智通 物联网&#xff08;IoT&#xff09;和工业物联网&#xff08;IIoT&#xff09;作为现代科技的重要分支&#xff0c;正在逐渐渗透到我们的日常生活和工业生产中。它们的应用范围广泛&#xff0c;涵盖了从智能家居到自动化工厂的多个领域。…

类于对象(上)--- 类的定义、访问限定符、计算类和对象的大小、this指针

在本篇中将会介绍一个很重要和很基础的Cpp知识——类和对象。对于类和对象的篇目将会有三篇&#xff0c;本篇是基础篇&#xff0c;将会介绍类的定义、类的访问限定符符和封装、计算类和对象的大小、以及类的 this 指针。目录如下&#xff1a; 目录 1. 关于类 1.1 类的定义 2 类…

Windows Insiders WSLg Linux GUI App 支持尝鲜

2021 年 4 月 21 日&#xff0c;微软在 Developer Blogs 发布了 Windows 预览版 WSL&#xff08;Windows Linux 子系统&#xff09; 对 Linux GUI App 的支持的公告&#x1f517;&#xff0c;碰巧&#x1f600;我最近重装了波电脑&#xff0c;系统换成了 Windows Insiders&…

Python Flask框架 -- 模版渲染、模版访问对象属性、过滤器与自定义过滤器

模版渲染 使用 render_template 来渲染模板 from flask import Flask, render_templateapp Flask(__name__)app.route(/blog/<blog_id>) def blog_detail(blog_id):return render_template(blog_detail.html, blog_idblog_id, username核)if __name__ __main__:app.r…