数据结构七大排序算法图解——选择排序动图演示

系列文章目录

四、选择排序

紧接上一篇交换排序

前言:

1、直接选择排序

思想:

例题:

代码部分:

性能分析

2、树形选择排序

思想:

例题一:

例题二:

性能分析

3、堆排序

定义:

方法:

如何“筛选”?

例题:

如何“建初始堆”?

例题:

代码部分

性能分析

4、总结

直接选择排序

树形排序

堆排序


前言:

选择排序的主要思想是每一趟从待排序列中选取一个关键字值最小的记录,也即第 1 趟从 n 个记录中选取关键字值最小的记录,在第 2 趟中,从剩下的 n-1 个记录中选取关键字值最小的记录,直到整个序列中的记录都选完位置。这样,由选取记录的顺序便可得到按关键字值有序的序列。

1、直接选择排序

思想:

首先在所有记录中选出关键字值最小的记录,把它与第一个记录进行位置交换,然后在其余的记录中再选出关键字值次小的记录与第二个记录进行位置交换,依此类推,直到所有记录排好序。

此处引用网上一张比较经典的gif来展示直接选择排序的整个过程:

例题:

假设待排序的 8 个记录的关键字序列为 { 5,3,6,4,7,1,8,2} 排序过程如下图所示
红颜色为已排好序的数字,考试时一般用【中括号】代替

在这里插入图片描述

初始序列就把它看成无序序列

然后从无序序列中选择最小的跟这无序序列中第一个数交换,构成有序序列

第二趟也是从无序序列中选择最小的跟无序序列中第一个数交换

下面每一趟以此类推

每趟序列遍历完后,有序序列记录的个数 + 1,无序序列的个数 - 1,

代码部分:

public void selectSort(){ RecordNode temp; //定义一个零时变量temp for(int i=0; i<this.curlen-1;i++) //进行i-1次遍历,this.curlen:数组的长度 { int min=i; for(int j=i+1;j<this.curlen; j++) //找到一个最小的 if (r[j].key.compareTo(r[min].key)<0) min=j; if (min!=i) {temp=r[i]; //以下是交换代码 r[i]=r[min]; r[min]=temp;} } } 

性能分析

2、树形选择排序

思想:

  • 此处引用网上一张比较经典图来展示树形选择排序的整个过程:

先对 n 个记录进行俩俩比较,比较的结果是把关键字值较小者作为优胜者上升到父结点,得到比较的优胜者(关键字值较小者),作为第一步比较的结果保留下来;

然后对这个记录再进行关键字的两两比较,如此重复,直到选出一个关键字值最小的记录为止。

这个过程可用一个含有n个叶子结点的完全二叉树来表示

例题一:

对于由8个关键字组成的序列 { 52,39,67,95,70,8,25,¯52¯ },使用树形选择排序选出最小关键字值的过程可用下图所示的完全二叉树来表示。

在这里插入图片描述

可以看到关键字都处在最后一层的叶子节点上
怎么选呢?俩俩 PK ,谁小谁上去到父亲节点,52 和 39,39 上去;67 和 95,67 上去;70 和 8,8 上去;
到上一层同样也是谁小谁上去到父亲节点,最后关键字最小的在最上面

例题二:

同上图例题一,找次小值

在这里插入图片描述

在例题一中,最小的找到了,然后把他当做无穷大 “∞”,然后再俩俩 PK 就可以找到次小,次小的找到了再把它替换成无穷大,每次都找到这些当中最小的弄到父亲节点,以此类推,最后就排好序了

性能分析

3、堆排序

定义:


换一种说法就是:
所有的父亲结点都大于孩子结点的叫大顶堆
所有的父亲结点都小于孩子结点的叫小顶堆
不大不小的不叫堆

此处引用网上一张比较经典的图来展示大小顶堆:
        大顶堆中根节点的值一定是整个序列中最大的
        小顶堆中根节点的值一定是整个序列中最小的

所以,堆的含义表明:完全二叉树中所有非终端结点(父亲结点)的值均不大于(或不小于)其左、右孩子结点的值。

  • 此处引用网上一张比较经典的 gif来展示:

方法:

首先把待排序的记录序列对应成一棵完全二叉树,并把它转换成一个初始堆(即首先建初始堆)。这时,根结点具有最大(或最小)的关键字值,然后,交换根结点和最后一个结点(即第n个结点)的位置,除最后一个结点之外,前 n-1 个结点仍构成一棵完全二叉树,再把它们调整为一个堆。同样交换根结点和最后一个结点(即第 n-1 个结点)。重复进行下去,直到只剩下一个根结点为止,便得一个有序表。

如何“筛选”?

所谓 “ 筛选 ” 指的是调整的过程,对一棵左/右子树均为堆的完全二叉树,“ 调整 ” 根结点使整个二叉树也成为一个堆。

例题:

排序之前的关键字序列为
{ 40,55,49,73,12,27,98,81,64,36 }

下图中,方框内,棕色的为交换的,绿色的为不需要动的

“ 筛选 ” 是一个从上往下进行的过程。
可以看到每个父亲结点都比子节点大,所以是大顶堆

在这里插入图片描述

但是我们在排好序后最大值 98 应该放在最后面,下标为 9 的这个位置,但这样就需要把 12 挪上去,就是最大值需要跟最后一个结点的值进行交换

在这里插入图片描述

但在 98 和 12 进行互换之后,它就不是堆了,因此,需要对它进行 “ 筛选 ”,仍然把他调整成大顶堆
这时候需要从上到下筛选,但需要注意的是:因为已经找到最大值了,并且把他放在最后面,所以不需要再排最大值 98 了
先看 12、81、49,如果是大顶堆,需要把 81 放在最上面,81 和 12 交换

在这里插入图片描述

 但现在还不是大顶堆,接下来 73、12、36 比较,谁大谁上去,并交换,所以 73 和 12 交换

在这里插入图片描述

 然后 55 和 64 比较,谁大谁上去

在这里插入图片描述

这样的话又变成大顶堆了
根结点 81 最大,和无序序列的最后一个交换,这里是 12 。。。。。以此类推,每次筛选都能找到最大的,这样就排好序了

如何“建初始堆”?

如果建一个大顶堆,最顶上的根结点一定是最大的
如果建一个小顶堆,最顶上的根结点一定是最小的

例题:

建堆是一个从下往上进行 “ 筛选 ” 的过程。

例如:排序之前的关键字序列为
{ 40,55,49,73,12,27,98,81,64,36 }

在这里插入图片描述

先看下标 4 和 9,36 比 12 大,36 上去,
再看左边下标 3、7、8,81 比 73、64大,81 上去,
再看右边下标 2、5、6,98 比 27、49大,98 上去,

在这里插入图片描述

然后再来看下标 1、3、4,81 比 55、36大,81 上去,

在这里插入图片描述

但这样我们就发现一个问题:下标 3、7、8,这三个数(55、73、64)就不能构成大顶堆了
所以三个数比较后,73上去

在这里插入图片描述

然后看下标 0、1、2,98 比 81、40大,98 上去

在这里插入图片描述

但现在下标 2、5、6,这三个数(40、27、49)就不能构成大顶堆了
所以三个数比较后,49上去

在这里插入图片描述

现在,左/右子树都已经调整为堆,最后只要调整根结点,使整个二叉树是个 “ 堆 ” 即可。
这个就是建堆的过程,从下往上筛选(按照编号最大,并且有孩子的,此题中从 4 开始调整,然后 3、2、1),调整的过程中注意有可能打破子树上的堆,需要继续调。

代码部分

public class HeapSort { public static void heapSort(int[] arr) { if (arr == null || arr.length == 0) { return; } int len = arr.length; // 构建大顶堆,这里其实就是把待排序序列,变成一个大顶堆结构的数组 buildMaxHeap(arr, len); // 交换堆顶和当前末尾的节点,重置大顶堆 for (int i = len - 1; i > 0; i--) { swap(arr, 0, i); len--; heapify(arr, 0, len); } } private static void buildMaxHeap(int[] arr, int len) { // 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆 for (int i = (int)Math.floor(len / 2) - 1; i >= 0; i--) { heapify(arr, i, len); } } private static void heapify(int[] arr, int i, int len) { // 先根据堆性质,找出它左右节点的索引 int left = 2 * i + 1; int right = 2 * i + 2; // 默认当前节点(父节点)是最大值。 int largestIndex = i; if (left < len && arr[left] > arr[largestIndex]) { // 如果有左节点,并且左节点的值更大,更新最大值的索引 largestIndex = left; } if (right < len && arr[right] > arr[largestIndex]) { // 如果有右节点,并且右节点的值更大,更新最大值的索引 largestIndex = right; } if (largestIndex != i) { // 如果最大值不是当前非叶子节点的值,那么就把当前节点和最大值的子节点值互换 swap(arr, i, largestIndex); // 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。 heapify(arr, largestIndex, len); } } private static void swap (int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } 

性能分析

4、总结

直接选择排序

首先在所有记录中选出关键字值最小的记录,把它与第一个记录进行位置交换,然后在其余的记录中再选出关键字值次小的记录与第二个记录进行位置交换,依此类推,直到所有记录排好序。

树形排序

先对 n 个记录进行俩俩比较,比较的结果是把关键字值较小者作为优胜者上升到父结点,得到比较的优胜者(关键字值较小者),作为第一步比较的结果保留下来;然后对这个记录再进行关键字的两两比较,如此重复,直到选出一个关键字值最小的记录为止。

堆排序

如果想要排升序,就应该建立大堆;
如果想要排降序;就应该建立小堆。

筛选就是被破坏后比较、交换。谁大谁上去,从上到下的顺序
建堆是一个从下往上进行 “ 筛选 ” 的过程。

Read more

告别亡羊补牢!金仓SQL防火墙,99.99%精准拦截恶意注入

告别亡羊补牢!金仓SQL防火墙,99.99%精准拦截恶意注入

开发留的坑,数据库来填!金仓数据库SQL防火墙,精准拦截99.99%的恶意SQL 在数字化转型的浪潮中,数据已成为企业的核心资产。然而,SQL注入攻击如同潜伏在阴影中的“不速之客”,时刻威胁着数据库的安全。即使开发团队严守预编译、输入过滤等防线,遗留代码、第三方组件的漏洞或人为疏忽仍可能给攻击者可乘之机。难道只能被动挨打、疲于补漏吗? 金仓数据库(KingbaseES)V009R002C014版本内置的SQL防火墙,给出了一种更聪明的答案——从数据库内核层构建主动防御,让恶意SQL无处遁形,安全团队从此告别“亡羊补牢”,真正实现“规则先行”。 一、SQL注入:那个偷偷溜进房子的“不速之客” SQL注入的原理并不复杂,却极其致命:攻击者将恶意代码伪装成正常输入,欺骗数据库执行非预期操作。 举个简单的例子:一个登录表单中,用户在用户名栏输入 ' OR '1'='1,后台的查询语句可能就变成了: SELECT * FROM users

By Ne0inhk
从“多库并存”到“一库多能”:聊聊金仓KingbaseES的融合架构实践

从“多库并存”到“一库多能”:聊聊金仓KingbaseES的融合架构实践

干数据库这行快十年了,亲眼见证了企业数据架构的变迁。早年做项目,最头疼的就是“数据竖井”——交易系统用Oracle,用户行为日志扔到MongoDB,时序监控数据塞进InfluxDB,图谱关系又得搞个Neo4j。每个库都有自己的语法、管理工具和运维体系,开发团队整天在不同数据库之间做数据同步和格式转换,数据一致性难保证,系统复杂度却直线上升。 这几年“融合数据库”的概念越来越热,但很多厂商的理解还停留在“多模接口”层面。直到去年深度参与了某城商行的核心系统分布式改造项目,用金仓数据库KingbaseES 完整跑了一轮,才算真正体会到什么是“一库多能”的设计哲学。今天就跟大家聊聊我们的实践心得,特别是金仓在这方面的独特思考。 一、为什么是“一库多能”,不是“多库拼装”? 先看个真实场景。我们那个银行客户要做实时反欺诈,需要在一个查询里关联:用户账户信息(结构化)、近期交易流水(带时序特征)、设备指纹(JSON文档)、社交关系图谱(判断是否团伙),以及地理位置信息(空间数据)。如果按传统思路,至少要跨5个不同数据库做联合查询,光数据同步延迟就够受的,更别说保证事务一致性了。

By Ne0inhk
Flutter 组件 jaspr_serverpod 适配鸿蒙 HarmonyOS 实战:前后端同构,构建全栈式组件渲染与高性能后端集成架构

Flutter 组件 jaspr_serverpod 适配鸿蒙 HarmonyOS 实战:前后端同构,构建全栈式组件渲染与高性能后端集成架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 jaspr_serverpod 适配鸿蒙 HarmonyOS 实战:前后端同构,构建全栈式组件渲染与高性能后端集成架构 前言 在鸿蒙(OpenHarmony)生态迈向全栈式开发、涉及跨端组件复用及高性能服务端逻辑集成的背景下,如何实现前端 UI 组件与后端业务逻辑的“无缝类型对齐”,已成为提升全栈研发效率与系统稳定性的关键议题。在鸿蒙设备这类强调分布式架构与端云协同的环境下,如果前端应用(Jaspr)与后端引擎(Serverpod)依然依赖碎片的 REST 协议驱动,由于由于接口契约的离散性,极易由于由于“前后端模型失致”导致线上环境的数据解析崩溃或并发冲突。 我们需要一种能够支持全栈 Dart 编写、具备自动代码生成且支持服务器端渲染(SSR)的同构映射方案。 jaspr_serverpod 为 Flutter/Dart 开发者引入了“全栈闭环”开发模式。

By Ne0inhk
最新Spring Security实战教程(十三)会话管理机制 - 并发控制与会话固定攻击防护

最新Spring Security实战教程(十三)会话管理机制 - 并发控制与会话固定攻击防护

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Micro麦可乐的博客 🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战 🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战 🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解 🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用 ✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧 💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程 🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整 🌞《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术 如果文章能够给大家带来一定的帮助!欢迎关注、评

By Ne0inhk