【数据结构】八大排序(二)

目录

前言:

冒泡排序

冒泡排序代码实现

冒泡排序特性总结

快速排序

单趟排序hoare版本

单趟排序挖坑法

单趟排序快慢指针法

 快速排序整体概览

快排的优化

三数取中法选key

小区间优化


前言:

上文介绍了直接插入排序,希尔排序,选择排序,堆排序并对四种排序进行了详尽的探讨,本文将以排序的基本思想,代码实现和各种排序的特性总结三个角度继续分析冒泡排序,快速排序

冒泡排序

冒泡排序的基本思想:

将待排序的序列从前向后依次比较相邻元素的值,如果逆序则交换

升序:将数组中相邻元素从前往后依次进行比较,如果前一个元素比后一个元素大,则交换,一趟下来后最大元素就在数组的末尾;

降序:将数组中相邻元素从前往后依次进行比较,如果前一个元素比后一个元素小,则交换,一趟下来后最小元素就在数组的末尾;

具体步骤如下

  1. 比较待排序序列中相邻的两个元素,如果发现左边的元素比右边的元素大,则交换这两个元素
  2. 每一对相邻的两个元素重复第一步的动作,从左至右依次执行,最后的元素一定是最大的元素
  3. 由于最大的元素位于数组尾元素的位置,下一趟两两比较的范围为待排序序列的首元素倒数第二个元素所处的位置,这段范围成为新的待排序序列,重复步骤1,步骤2;
  4. 持续以上步骤 1, 步骤 2, 步骤 3 的工作,每重复一次需要排序的序列中少一个最右边的元素,直到没有任何一对数字需要比较排序;

......

冒泡排序代码实现

void bubblesort(int arr[], int n)
{// 外层循环控制冒泡排序的趟数int i = 0;for (i = 0; i < n - 1; i++){//内层循环控制要比较元素的个数int j = 0;for (j = 0; j < n - i - 1; j++){int temp = 0;if (arr[j]>arr[j + 1]){temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}

冒泡排序特性总结

1.时间复杂度

冒泡排序的平均 时间复杂度是O(n^2),最好时间复杂度是O(n),最坏时间复杂度是O(n^2);

2. 空间复杂度

额外开辟的空间为常数个,所以空间复杂度为O(1)

3.算法稳定性

冒泡排序是一种稳定的排序算法,即相等元素的相对位置在排序前后不会发生改变;

快速排序

快速排序的基本思想

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值右子序列中所有元素均大于基准值,然后对左右子序列分别递归地进行快速排序,直到所有元素都排列在相应位置上为止;

单趟排序hoare版本

思路如下:

1. 选取待排序序列最左边元素作为基准值key,定义两个指针left和right分别指向待排序序列的最左侧和最右侧,right从右向左走,left 从左向右走;

2.right先走,right指针找比基准值key小的数;left后走, left找比基准值key大的数;找到后将两个数交换位置;

3. 当left指针与right指针相遇时,将相遇位置的值与基准值key进行交换;

void swap(int* p, int* q)
{int tmp = *p;*p = *q;*q = tmp;
}
//单趟排序hoare版本
int PartSort(int* a, int left, int right)
{int keyi = left;//选取左边作为基准值,则右边先走while (left < right)//left与right相遇,左边找大,右边找小的循环终止{//右边找小while (left<right && a[right] >= a[keyi]){right--;}//左边找大while (left<right && a[left] <= a[keyi]){left++;}//交换swap(&a[left], &a[right]);}swap(&a[keyi], &a[left]);return left;
}

思考: 为什么left与right相遇位置处的数值比key要小

left与right相遇有如下俩种情形:

相遇的第一种情形:right动left不动,相遇位置为left位置,left位置与前一个right位置进行了交换,交换之后left位置比key小;

相遇的第二种情形:right不动left动,相遇位置为right位置,此时left找大没有找到与right相遇,而right位置一定比key小;

思考:为什么左边取基准值key,右边先走?

保证当left与right相遇时,相遇位置小于基准值

  1. right 停下时,left 与 right 相遇,由于right 找比 key小的值,所以此时 right 的位置一定比key小;
  2. left 停下时,right 与 left 进行交换,交换后 left 指向的值比 key 小,此时 right 遇到 left 的位置一定比key小;

单趟排序挖坑法

思路如下:

1. 选择数组第一个数作为基准数,基准数的位置形成一个坑位
2. 定义两个指针left与right分别指向待排序序列的第一个数和最后一个数;
3. 从right开始向前遍历,找到第一个小于基准数的数a[right],将其赋值给a[left],a[right]成为一个新的坑位;
4. 从left开始向后遍历,找到第一个大于基准数的数a[left],将其赋值给a[right],a[left]成为一个新的坑位;
5. 重复步骤3和4,直到left > right
6. 将基准数填入最后一个坑中,a[left]=key;

//单趟排序挖坑法
int PartSort(int* a, int left, int right)
{int key = a[left];int hole = left;while (left < right){//右边先走while (left < right && a[right] >= key){right--;}a[hole]=a[right];hole = right;//左边再走while (left < right&&a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}

单趟排序快慢指针法

思路如下:

1. 选取待排序序列的第一个元素作为基准值key;
2. 定义两个指针prev和cur,prev指针指向序列开头,cur指针指向prev指针的后一个位置
3. 从cur开始向前遍历,如果遇到比key小的元素,就将prev指针向后移动一位,并交换prev和cur指向的元素;
4. 遍历结束后,将key与prev指向的元素交换位置,此时prev指向的位置就是key的最终位置;

//单趟排序前后指针法
int PartSort(int* a, int left, int right)
{int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){//(++prev)!=cur为了防止++prev与cur指向相同数值,此时交换毫无意义;if (a[cur] < a[keyi]&&(++prev)!=cur){swap(&a[cur], &a[prev]);}cur++;}swap(&a[keyi], &a[prev]);return prev;
}

 快速排序整体概览

快速排序首先选取基准值key,将待排序序列分为两个子序列,左子序列放比基准值小的数,右子序列放比基准值大的数,然后再将子序列以以上方式同样分割,直到数组有序,下图单趟排序采取hoare版本

  • 经过一趟hoare排序,区间被划分为[begin, keyi-1] U keyi U [keyi+1, end] ,单趟排序一次就会唯一确定一个元素来到最终排序所确定的位置

  • 先递归排序左子序列[begin, keyi-1] , 再递归排序右子序列[keyi+1, end],类似二叉树前序遍历

  • 根据上图可得出递归终止条件为区间不存在(begin>end)或者只有一个元素(begin=end)
void QuickSort(int*a, int begin, int end)
{//递归终止的条件//1.区间不存在(begin>end)或者只有一个元素(begin=end)if (begin >= end)return;int keyi = PartSort3(a, begin, end);// 一趟排序原排序区间划分为如下三部分//[begin keyi-1]U[keyi]U[keyi+1,end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

快排的优化

三数取中法选key

若选取的基准值key是序列中最大或最小的数值,则快速排序的递归深度会非常深,排序效率会很低;若是一个有序数组使用快速排序,则递归深度为n,单趟排序也为n,时间复杂度为O(n^2);

假定待排序序列的左下标left , 右下标right,左右下标所对应的中间下标 midi=(left+right)/2,取三个下标所对应的值的中间值为基准值key,可以防止快排出现最差情形;

//三数取中法选keyi keyi为中间值的下标
int GetMidi(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] > a[mid]){if (a[mid] > a[right]){return mid;}//mid为最小值else if (a[left] > a[right]){return right;}else{return left;}}else{if (a[mid] < a[right]){return mid;}//mid为最大值else if (a[left]>a[right]){return left;}else{return right;}}
}

小区间优化

由于快速排序是递归实现的,而每一次函数调用,均需要开辟函数栈帧,当递归到最后几层时,此时数组中的值其实已经接近有序,最后几层的函数调用会极大占用函数栈帧所开辟的空间;

假设数组大小N=10,即二叉树节点个数N=10;

10个数值,递归了3~4层,而最后三层占据比例为87.5%,调用函数的次数就越多,开辟函数栈帧的消耗越大,导致效率下降,代价太大,在递归分割过程中,当区间较小时,不再递归分割排序,序列在递归分割过程中,子序列已经接近有序插入排序每一次单趟排序都只向前遍历到最大值之后,一共执行n次,若数组有序,则总共只执行n次,最差情况下每次都只是从i遍历到0下标,综合考虑是执行次数最优,故选择直接插入排序进行优化

void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;// 小区间优化,小区间不再递归分割排序,降低递归次数if ((end - begin + 1) > 10){int keyi = PartSort(a, begin, end);// [begin, keyi-1] keyi [keyi+1, end]QuickSort1(a, begin, keyi - 1);QuickSort1(a, keyi + 1, end);}else{InsertSort(a + begin, end - begin + 1);}
}

 

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

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

相关文章

2 线、3 线和 4 线 RTD 配置之间有什么区别?

电阻温度检测器 (RTD) 是温度传感器的一种&#xff0c;由于其准确性、可重复性和稳定性而广泛应用于各种工业应用。这些设备通过感测材料温度变化时电阻的变化来测量温度。 RTD 探头有多种配置&#xff0c;包括 2 线、3 线和 4 线型号。这些类型之间存在显着差异&#xff0c;在…

浅析函数防抖节流

防抖和节流都是前端开发中常用的优化性能的技术。 一、定义 防抖&#xff1a; 防抖指的是在事件触发后&#xff0c;在规定的时间内若再次触发&#xff0c;则重新计时&#xff0c;直到规定时间内没有再次触发事件&#xff0c;才执行事件处理。这样可以避免在短时间内频繁地触发…

ArrayList与顺序表的简单理解

前言----list 在集合框架中&#xff0c;List是一个接口&#xff0c;继承自Collection。Collection也是一个接口&#xff0c;该接口中规范了后序容器中常用的一些方法&#xff0c;具体如下所示&#xff1a; Iterable也是一个接口&#xff0c;表示实现该接口的类是可以逐个元素进…

编写安全 JavaScript 代码的最佳实践

编写安全 JavaScript 代码的最佳实践 JavaScript 的动态特性使其成为事实上的浏览器语言和世界上最流行的编程语言。 JS 最受欢迎的有用功能之一是即时分析。这意味着浏览器在下载内容的同时执行代码&#xff0c;这显然有其优势。然而&#xff0c;这种程度的自由也伴随着问题…

Redis实战命令

实战命令 单值缓存 set key value get key 对象缓存 &#xff08;1&#xff09;set user:1 value(json格式) &#xff08;2&#xff09;mset user:1:name junfeng user:1:age 18 mget user:1:name user:1:age 分布式锁 分布式锁解决了什么问题&#xff1f; 分布式锁解…

[带余除法寻找公共节点]二叉树

二叉树 题目描述 如上图所示&#xff0c;由正整数1, 2, 3, ...组成了一棵无限大的二叉树。从某一个结点到根结点&#xff08;编号是1的结点&#xff09;都有一条唯一的路径&#xff0c;比如从10到根结点的路径是(10, 5, 2, 1)&#xff0c;从4到根结点的路径是(4, 2, 1)&#x…

【中间件】服务化中间件理论intro

中间件middleware 内容管理 intro服务化middleware架构注册中心intro服务治理系统intro 本文主要intro服务化中间件的探讨 去年cfeng写了一篇博客走马观花般阐述了Spring Cloud下面的各种中间件&#xff0c;连深入使用都谈不上&#xff0c;只能说intro&#xff0c;在实际work中…

linux用户身份切换su和 sudo

su 切换root&#xff0c;但是&#xff0c;环境变量是之前用户的 可以看到利用su切换&#xff0c;根目录还是pro1的 su - 连同环境一起切换成root&#xff0c;切换后工作目录都不一样了&#xff0c;看输入内容左侧信息&#xff0c;和第一个图片比较 -c仅执行一次命令&#xff0…

面试必须要知道的MySQL知识--索引

10 索引 10.1 数据页存储结构 10.1.1 数据页的各个部分 在讲索引之前&#xff0c;让我们看看一个单独的数据页是什么样子的 去除掉一些我们不太需要那么关注的部分后&#xff0c;简化如下&#xff1a; 也就是说平时我们在一个表里插入的一行一行的数据会存储在数据页里&#…

k8s-deployment控制器 5

K8s控制器是Kubernetes&#xff08;简称k8s&#xff09;系统中一个重要的组成部分&#xff0c;它是一个管理Pod的中间层&#xff0c;可以创建和管理多个Pod副本&#xff0c;确保它们按照预定的数量和行为进行运行。 通过编写yaml文件将信息全部存到etcd中&#xff0c;控制器通…

HTTP协议发展

HTTP 1.0 -> HTTP 1.1 -> HTTP 2.0 -> HTTP 3.0 (QUIC) 每一代HTTP解决了什么问题&#xff1f; 下图说明了主要功能。 HTTP 1.0 于 1996 年最终确定并完整记录。对同一服务器的每个请求都需要单独的 TCP 连接。 HTTP 1.1 于 1997 年发布。TCP 连接可以保持打开状态…

Flask教程入门

1.学习Flask之前&#xff0c;首先需要对URL进行一定的了解。 URL的一些知识&#xff1a; 1.URL只能包含ASCII码里面一些可显示的字符&#xff0c;如A-Z&#xff0c;a-z&#xff0c;0-9&#xff0c;&&#xff0c;#&#xff0c;%&#xff0c;&#xff1f;&#xff0c;/等字符…

spring boot项目未将resource目录标志为资源目录导致配置文件无效因而运行报错问题

能编译&#xff0c;但不能运行。感觉配置文件没有生效。 将程序代码发给同事&#xff0c;我自己能跑&#xff0c;他不能跑&#xff0c;提示无法构造redis对象。redis的链接写在配置文件里&#xff0c;其实是可以连接的。然后从GIT库下载代码&#xff0c;也同样不能跑。同事的操…

外网IP和内网IP的区别

首先得先知道什么是ip地址&#xff0c;它就是唯一标识连接网络的设备的&#xff0c;即IP地址充当了设备在网络中的“住址”&#xff0c;使得设备能够相互通信和交换数据。 我们常听开发人员说外网内网&#xff0c;那么它们有什么区别呢&#xff1f; 外网可以理解为互联网&…

Springboot的excel导出

这里导出excel用到的是 阿里巴巴的easyexcel 1、首先导入依赖 <!--alibaba easyexcel--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.6</version> </dependency> 2、…

第二十章——多线程

一.线程简介 线程的特点 1.进程是资源分配的最小单位&#xff0c;线程是最小的执行单位 2.一个进程可以有多个线程 3.线程共享进程资源 二.创建线程 1.继承Thread类 1.Thread类是java.lang包中的一个类&#xff0c;从这个类实例化的对象代表线程&#xff0c;程序员启动一个新…

三 STM32F4使用Sys_Tick 实现微秒定时器和延时

更多细节参考这篇 1. 什么是时钟以及作用 1.1 什么是时钟 时钟是由电路产生的周期性的脉冲信号&#xff0c;相当于单片机的心脏 1.2 时钟对于STM32的作用 指令同步&#xff1a;cpu和内核外设使用时钟信号来进行指令同步数据传输控制&#xff1a; 时钟信号控制数据在内部总…

sqli-labs(6)

27. 过滤了union和select 使用双写绕过 有报错信息使用报错注入 1and(extractvalue(1,concat(0x5c,database())))and11 1and(updatexml(1,concat(0x7e,database(),0x7e),1))and11 1and(extractvalue(1,concat(0x5c,(selseselectlectect(group_concat(table_name))from(inform…

Spring Boot 3 + Spring Security 6 最新版本修改 Json 登录后 RememberMe 功能问题失效的解决方案

当 Spring Boot 版本更新到 3 之后&#xff0c;最低要求的 JDK 版本变为 17&#xff0c;相应的 最新版本的 Spring Security 的配置也发生了变化&#xff0c;一下主要讲解一些新的 Spring Security 的配置方法 1. 配置由继承WebSeucrityConfigurerAdapter变成只需添加一个Secur…

每天五分钟计算机视觉:LeNet是最早用于数字识别的卷积神经网络

LeNet 假设你有一张 32321 的图片,然后使用 6 个 55的过滤器,步幅为 1,padding 为 0,输出结果为 28286。图像尺寸从 3232 缩小到 2828。 然后进行池化操作,使用平均池化,过滤器的宽度为 2,步幅为 2,图像的尺寸,高度和宽度都缩小了 2 倍,输出结果是一个14146 的图像。…