数据结构—内部排序(下)

文章目录

  • 8.内部排序(下)
    • (6).归并排序
      • #1.先做合并
      • #2.再来排序
      • #3.代码实现
      • #4.稳定性与时间复杂度分析
    • (7).快速排序
      • #1.算法思想
      • #2.代码实现
      • #3.稳定性与时间复杂度分析
    • (8).基数排序
      • #1.算法思想
      • #2.稳定性和时间复杂度分析
    • 小结

8.内部排序(下)

(6).归并排序

  那我们的时间复杂度还能不能更低一点呢?毕竟 O ( n 2 ) O(n^2) O(n2)的时间复杂度真的很高,一般在oj上,1秒差不多只能执行 1 0 8 10^8 108次运算,那么如果我想做一个 1 0 5 10^5 105规模的数据的排序, O ( n 2 ) O(n^2) O(n2)的排序方式就可能会超时了,所以我们得想办法突破一下

  事实上,对于 O ( n 2 ) O(n^2) O(n2)的算法,我们总是会想能不能给它优化到 O ( n log ⁡ n ) O(n\log{n}) O(nlogn),因此排序算法有没有机会来到这个时间复杂度呢?接下来我要介绍的归并排序和快速排序都能达到 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)的平均时间复杂度

#1.先做合并

  第一个问题是,如果我有两个规模相似的有序数组a和b,我们要怎么把它们合并成一个数组呢?如果用我们前面提到的选择排序之类的思路,我们可以很轻松地写出一个 O ( n 2 ) O(n^2) O(n2)的算法,但这样就没意思了,我们能不能在线性的时间复杂度内完成这件事呢?当然可以,我们来看这么一个流程:
p17

  我们有i和j两个“指针”分别用来遍历a和b,我们每一次都比较i和j对应的值,如果 a [ i ] ≤ b [ j ] a[i] \leq b[j] a[i]b[j],则不断地把a中的元素放到c中,直到 a [ i ] > b [ j ] a[i] > b[j] a[i]>b[j],比如上面这张图中,两边分别指向第一个元素,然后开始比较,发现1比2小,于是把1放进c中,之后:
p18

  一直这么做,就把1,2,2一起放进c中了,接下来发现b对应的终于比a小了,这时候让b的指针往后跳一位:p19

  然后发现两个相等了,不过前面规则已经很清楚了, a [ i ] ≤ b [ j ] a[i]\leq b[j] a[i]b[j]的时候把a插入,所以我们再把a的3插入:
p20

  这时候我们发现a的指针已经跑完了,b还没跑完,怎么办呢?这简单,我们直接把b的所有值全都插到c的后面就好了:
p21

  收工!我们顺利地合并了这两个有序序列,而且如果我们的a和b分别对应的是前面和后面的两个序列,那么经过这样一轮合并操作后,相同元素的先后顺序并没有改变,这保证了归并排序是一种稳定的排序方式

  所以今天在这里就可以给出代码了:

vector<int> Merge(vector<int>& a, vector<int>& b)
{vector<int> c;int i = 0, j = 0;while (i < a.size() && j < b.size()) {if (a[i] <= b[j]) {c.push_back(a[i++]);}else {c.push_back(b[j++]);}}while (i < a.size()) {c.push_back(a[i++]);}while (j < b.size()) {c.push_back(b[j++]);}return c;
}

  非常简单的想法!并且我们发现这个合并过程可以把两个基本有序的序列,合并成为一个更大的稳定有序序列,因此我们接下来就可以尝试一下完成这个排序的流程了。

#2.再来排序

  既然有合,那就要分,归并排序的基础是将一个完整的序列依次二分,然后再依次合并,所以我们首先需要把Merge函数改成这个样子,我们得保证我们能够对于同一个数组内部的数据进行归并操作:

void Merge(vector<int>& a, int low, int mid1, int end)
{   int i = low, j = mid1 + 1, k = low;vector<int> b(a.size());while (i <= mid1 && j <= end) {if (a[i] <= a[j]) {b[k++] = a[i++];}else {b[k++] = a[j++];}}while (i <= mid1) {b[k++] = a[i++];}while (j <= end) {b[k++] = a[j++];}for (int u = low; u <= end; u++) {a[u] = b[u];}
}

  然后排序的部分就简单得离谱了:

void MergeSort(vector<int>& a, int left, int right)
{if (left >= right) return;int mid = (left + right) / 2;MergeSort(a, left, mid);MergeSort(a, mid + 1, right);Merge(a, left, mid, right);
}

  没错,我们每次二分序列,对左右两个子序列排序,再合并,这就好了。

#3.代码实现

  我们把前面两个部分的代码合在一起:

void Merge(vector<int>& a, int low, int mid1, int end)
{   int i = low, j = mid1 + 1, k = low;vector<int> b(a.size());while (i <= mid1 && j <= end) {if (a[i] <= a[j]) {b[k++] = a[i++];}else {b[k++] = a[j++];}}while (i <= mid1) {b[k++] = a[i++];}while (j <= end) {b[k++] = a[j++];}for (int u = low; u <= end; u++) {a[u] = b[u];}
}void MergeSort(vector<int>& a, int left, int right)
{if (left >= right) return;int mid = (left + right) / 2;MergeSort(a, left, mid);MergeSort(a, mid + 1, right);Merge(a, left, mid, right);
}

  好了!归并排序就完成了

#4.稳定性与时间复杂度分析

  稳定性前面已经分析过了,归并排序是稳定的排序方式,接下来简单分析一下时间复杂度: T ( n ) = 2 T ( n 2 ) + n T(n) = 2T(\frac{n}{2}) + n T(n)=2T(2n)+n
  这个式子,有点眼熟,根据我们在线性表(下)最后提到的主定理,我们可以直接得到:
O ( T ( n ) ) = O ( n log ⁡ n ) O(T(n))= O(n\log{n}) O(T(n))=O(nlogn)
  很好!我们写出了第一个达到 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)时间复杂度的排序算法。

(7).快速排序

#1.算法思想

  快速排序的想法其实和归并差不多,它采取了一种叫做分而治之(Divide and Conquer) 的思想,但是它比归并更加宽松一点,我们每次在待排序的序列中选取一个基准值(pivot),将比基准值大的放在基准值的右边,把比基准值小的放在左边,然后再对左右两个序列依次排序,好像跟归并有点像,也是把大问题分成若干子问题进行解决。
p22

  快速排序的过程差不多是这样,我们把序列不断切分,直到只有一个数据的情况下就不再切分,直接返回,最后得到的序列就是有序的了,快速排序的流程主要是保证了经过一轮排序的右边永远都是大于等于左边的,因此右边后续无论如何排序,都不会影响到整个序列的顺序,并且整个过程还是在逐渐变有序的

#2.代码实现

void quick_sort(vector<int>& vec, int low, int high)
{if (low >= high) return;int i{ low }, j{ high }, t{ vec[low] };while (i < j) {while (i < j && vec[j] >= t) {j--;}if (i < j) vec[i++] = vec[j];while (i < j && vec[i] <= t) {i++;}if (i < j) vec[j--] = vec[i];}vec[i] = t;quick_sort(vec, low, j - 1);quick_sort(vec, j + 1, high);
}

  快速排序可以不借助辅助数组,完成原地排序,我们需要用到两个指针i和j,i从左往右走,j从右往左走,在二者相遇之前,只要遇到了比基准值大/小的,就进行交换,最后i和j相遇的位置正好就是基准值被放下的位置

  题外话,如果你用python写快排,这个写法会方便很多:

def quick_sort(l):if len(l) <= 1:return lelse:pivot = l[0]left, right, mid = [], [], []for i in l:if i < pivot:left.append(i)elif i > pivot:right.append(i)else:mid.append(i)return quick_sort(left) + mid + quick_sort(right)

  因为python中的list构造很简单,所以写法也就对应的很简单了

#3.稳定性与时间复杂度分析

  因为我们在双指针来回跳转,所以快速排序注定是一种不稳定的排序算法,接下来我们来分析一下快速排序的最优时间复杂度,快速排序最优情况就是每一次都恰好把序列分成两半,因此我们可以写出下面的表达式: T ( n ) = 2 T ( n 2 ) + n T(n) = 2T(\frac{n}{2}) + n T(n)=2T(2n)+n
  哈,这好像跟归并一模一样嘛,所以快速排序的最优时间复杂度就是 O ( T ( n ) ) = O ( n log ⁡ n ) O(T(n))=O(n\log{n}) O(T(n))=O(nlogn)
  为什么这里强调是最优呢,因为基于pivot的选择,我们的快速排序会发生明显的退化,比如:[9, 8, 7, 6, 5, 4, 3, 2, 1],我们每次选取第一个元素作为pivot,那么第一次就是:[1, 8, 7, 6, 5, 4, 3, 2, 1],然后是[1, 8, 7, 6, 5, 4, 3, 2, 9],接下来是[1, 8, 7, 6, 5, 4, 3, 2],然后是[2, 7, 6, 5, 4, 3, 8],很好!我们的算法退化了!每一次挑出一个最小或者最大的,快速排序在完全顺序或完全逆序的情况下会退化为 O ( n 2 ) O(n^2) O(n2)

  不过这不影响我们用快速排序,在《算法导论》中有对于快速排序平均时间复杂度的证明,其平均时间复杂度为 O ( n log ⁡ n ) O(n\log{n}) O(nlogn),碍于篇幅,这里就不再证明了。为了避免退化的情况发生,我们一般采取改进pivot选取以及结合插入排序两种方法来解决,前者可以从序列里随机挑选pivot,可能可以使得左序列和右序列的分配更加平均,而结合插入排序则是在数据的有序程度到达一定程度之后采取,这样可以将时间复杂度进一步下降,毕竟,快速排序最不擅长的基本有序恰恰是插入排序最擅长的部分

  所以我们在C++中使用的algorithm里的std::sort就是一种快速排序+插入排序的排序算法,它可以比较稳定地达到 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)的时间复杂度。

(8).基数排序

  前面说的两种排序算法属于基于比较的排序算法,而接下来我们要说一种不基于比较的排序方法,而这种方法,顺利地将排序的时间复杂度降到了线性时间复杂度,伟大的进步!接下来我们来看看它是怎么做的吧!

#1.算法思想

  基数排序的思想主要源自多关键字排序,比如对于一系列多位的整数,比较大小我们从低位到高位比较(LSD,最低位优先),也可以从高位到低位比较(MSD,最高位优先)。

  我们这里以低位到高位为例,比如[49, 96, 80, 62, 100, 94, 67, 79, 87, 53],首先我们按照低位0~9分成几个子序列[[80], [62], [53], [94], [96], [67, 87], [49, 79]],之后顺着重新收集成序列[80, 62, 53, 94, 96, 67, 87, 49, 79],再按照十位排:[[49], [53], [62, 67], [79], [80, 87], [94, 96]],再收集起来得到[49, 53, 62, 67, 79, 80, 87, 94, 96],因为排序关键字只有个位和十位两个,因此我们的排序已经结束了,你发现,这简直快得离谱啊!

  是的,我们只用了两轮操作就已经把整个序列排到有序了。

#2.稳定性和时间复杂度分析

  基数排序的稳定性我们在前面是已经看到了的,我们根据排序关键字分组的过程以及后续收集的过程会严格依照原始序列的顺序进行,因此基数排序是稳定的排序。

  基数排序的时间复杂度是线性的,假设排序关键字有n个,关键字对应有r个基,d为数据量,那么基数排序的时间复杂度即为 O ( d ( r + n ) ) O(d(r+n)) O(d(r+n)),那么对于我们前面提到的两位十进制数的排序,它的时间复杂度就是 O ( 12 d ) = O ( d ) O(12d)=O(d) O(12d)=O(d),也就是线性的时间复杂度,真的很快。

  基数排序的时间复杂度相对比较高,并且代码实现比较复杂,在这里就不给出代码样例了,了解即可。

小结

  排序的东西,其实看起来都不是很难,但是对于我们来说,排序算是基础的内容,因为我们总是在追求有序的东西,毕竟有序的东西总是会有很好的性质,下一篇中我们会开始介绍的相关内容,预计会分成上下两篇发布。

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

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

相关文章

C++ [多态]

本文已收录至《C语言和高级数据结构》专栏&#xff01; 作者&#xff1a;ARMCSKGT 多态 前言正文多态的概念多态的定义构成多态的条件关于final和override关于重载,重写和重定义 抽象类概念补充 多态的原理虚表指针和虚表关于虚函数的调用动态绑定和静态绑定 单继承与多继承中的…

it is missing from your system. Install or enable PHP‘s sodium extension.

Composer with –ignore-platform-reqext-sodium it is missing from your system. Install or enable PHP’s sodium extension. 出现如上的报错是的 开启php.ini中的 sodium的扩展

Docker Rootfs

一、rootfs 介绍 rootfs 是一个操作系统所包含的文件、配置和目录&#xff0c;并不包括操作系统内核。在 Linux 操作系统中&#xff0c;这两部分是分开存放的&#xff0c;操作系统只有在开机启动时才会加载指定版本的内核镜像。 实际上&#xff0c;同一台机器上的所有容器&am…

【解决方案】危化品厂区安防系统EasyCVR+AI智能监控

危化品属于危险、易燃易爆、易中毒行类&#xff0c;一旦在生产运输过程中发生泄漏后果不堪想象&#xff0c;所以危化品的生产储存更需要严密、精细的监控&#xff0c;来保障危化品的安全。EasyCVRTSINGSEE青犀AI智能分析网关搭建的危化品智能监控方案就能很好的为危化品监管保驾…

RedisTemplate乱码问题

其实这是在解决一个项目问题是发现的&#xff0c;因为原开发者的大意&#xff0c;造成了系统出现严重的逻辑问题。 因为系统系统采用分模块开发&#xff0c;某模块使用Spring提供的RedisTemplate进行值的读写&#xff0c;另一位使用了框架基于Jedis的一套公用方法进行值的读写…

海康设备、LiveNVR等通过GB35114国密协议对接到LiveGBS GB28181/GB35114平台的详细操作说明

一、LiveNVR通过GB35114接入LiveGBS 1.1 开启LiveGBS 35114功能 信令服务livecms.ini配置文件中[sip]增加一行gm1 启动LiveCMS 1.2 生成设备端证书 我们用LiveNVR做为设备端向LiveGBS注册&#xff0c;这里先生成LiveNVR的设备证书&#xff0c;并将LiveNVR的设备证书给LiveGB…

echarts实现不展示X轴Y轴轴线、刻度

今日工作中需要实现折线图的简图&#xff0c;就是只看个大概趋势不展示具体坐标&#xff0c;查阅了文档记录一下。 initCharts(_id, _name, yAxisData, _unit){if(this[_id]) this[_id].clear();this[_id] $echarts.init(document.getElementById(_id));const options {grid…

瑞吉外卖Day04

1.文件的上传下载 Value("${reggie.path}")private String basePath;/*** 文件上传** param file* return*/PostMapping("/upload")public R<String> upload(MultipartFile file) {log.info("文件上传");//原始文件名String originalFilen…

JimuReport积木报表 v1.6.5 版本发布—免费报表工具

项目介绍 一款免费的数据可视化报表&#xff0c;含报表和大屏设计&#xff0c;像搭建积木一样在线设计报表&#xff01;功能涵盖&#xff0c;数据报表、打印设计、图表报表、大屏设计等&#xff01; Web 版报表设计器&#xff0c;类似于excel操作风格&#xff0c;通过拖拽完成报…

k8s集群搭建(ubuntu 20.04 + k8s 1.28.3 + calico + containerd1.7.8)

环境&需求 服务器&#xff1a; 10.235.165.21 k8s-master 10.235.165.22 k8s-slave1 10.235.165.23 k8s-slave2OS版本&#xff1a; rootvms131:~# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 20.04.5 LTS Release: …

从头开始的卷积神经网络

VGG-16 卷积神经网络。来源&#xff1a;LearnOpenCV 参考资料&#xff1a;这篇文章可以在 Kaggle Notebook &#x1f9e0; Convolutional Neural Network From Scratch上更好地阅读。路易斯费尔南多托雷斯 一、说明 本文详细介绍在tf2.0上&#xff0c;使用ceras实现基本的神经…

μC/OS-II---计时器管理1(os_tmr.c)

目录 创建一个计时器重新启动一个计时器停止一个计时器删除一个计时器 计时器是倒计时器&#xff0c;当计数器达到零时执行某个动作。用户通过回调函数提供这个动作。回调函数是用户声明的函数&#xff0c;在计时器到期时被调用。在回调函数中绝对不能进行阻塞调用&#xff08;…

springboot单体项目部署

配置类 检查跨域配置类 检查黑白名单是否有问题&#xff0c;是否需要更改 配置文件 检查端口 查看端口是否为需要搭建的端口 检查数据源 查看数据库是否为线上数据库 配置页面 注意&#xff1a;如果是单体项目的话&#xff0c;前端页面是和后端整合在一起的&#xff0…

第二篇 《随机点名答题系统》——题库管理详解(类抽奖系统、在线答题系统、线上答题系统、在线点名系统、线上点名系统、在线考试系统、线上考试系统)

目录 1.功能需求 2.数据库设计 3.流程设计 4.关键代码 4.1.题库维护 4.1.1数据请求示意图 4.1.2添加题库&#xff08;login.php&#xff09;数据请求代码 4.1.3删除题库&#xff08;login.php&#xff09;数据请求代码 4.1.4 业务处理Service&#xff08;tiKuService…

【React】Redux基本使用

什么情况使用 Redux &#xff1f; Redux 适用于多交互、多数据源的场景。简单理解就是复杂 从组件角度去考虑的话&#xff0c;当我们有以下的应用场景时&#xff0c;我们可以尝试采用 Redux 来实现 某个组件的状态需要共享时 一个组件需要改变其他组件的状态时 一个组件需要…

11-13 /11-14代理模式 AOP

调用者 代理对象 目标对象 代理对象除了可以完成核心任务&#xff0c;还可以增强其他任务,无感的增强 代理模式目的: 不改变目标对象的目标方法的前提,去增强目标方法 分为:静态代理,动态代理 静态代理 有对象->前提需要有一个类&#xff0c;那么我们可以事先写好一个类&a…

【Mycat2实战】三、Mycat实现读写分离

1. 无聊的理论知识 什么是读写分离 读写分离&#xff0c;基本的原理是让主数据库处理事务性增、改、删操作&#xff0c; 而从数据库处理查询操作。 为什么使用读写分离 从集中到分布&#xff0c;最基本的一个需求不是数据存储的瓶颈&#xff0c;而是在于计算的瓶颈&#xff…

汽配零件发FBA美国专线

随着电商的迅速发展&#xff0c;跨境电商平台如亚马逊的FBA(Fulfillment by Amazon)服务成为了许多商家选择的销售渠道。对于汽配零件行业来说&#xff0c;发FBA美国专线可以打开更广阔的市场&#xff0c;并且有望获得可观的发展前景。下面将从市场分析和前景两个方面来探讨汽配…

pyTorch Hub 系列#2:VGG 和 ResNet

一、说明 在上一篇教程中,我们了解了 Torch Hub 背后的本质及其概念。然后,我们使用 Torch Hub 的复杂性发布了我们的模型,并通过相同的方式访问它。但是,当我们的工作要求我们利用 Torch Hub 上提供的众多全能模型之一时,会发生什么? 在本教程中,我们将学习如何利用称为…

[NSSRound#7 Team]ShadowFlag

文章目录 前置知识/proc目录python的反弹shellpin码计算 解题步骤 前置知识 /proc目录 Linux系统上的/proc目录是一种文件系统&#xff0c;用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息&#xff0c;甚至可以通过更改其中某些文件来改变内核的运行状态。/pro…