STL中sort的底层实现

文章目录

  • 1、源码分析
  • 2、算法优化
  • 3、总结

在讲解STL中sort的底层原理之前,先引申出这样几个问题?

①STL中sort的底层是采用哪种或者哪几种排序?
②STL中sort会导致栈溢出吗?
③快速排序的时间复杂度是不稳定的 l o g 2 n log_2n log2n,最坏情况会变为n2,如何解决?
④STL中sort是如何控制递归深度的,如果已经到达了递归的深度,此时还没排完序该怎么办?

1、源码分析

C++STL中的sort默认会给我们提供两个接口,如下:

template <class RandomAccessIterator> void sort (RandomAccessIterator first, RandomAccessIterator last)
template <class RandomAccessIterator, class Compare> void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp)

第一个函数,有两个参数,分别表示迭代器的起始位置,和结束位置,它俩之间就是需要排序的区间

第二个函数,多了一个参数Compare comp,它比较的方式,可以是一个普通函数,或者是仿函数,或者是lambda表达式

如果我们不指定比较方式,默认采用升序排序

以下是vs2017的sort的源码,位置在\include\algorithm中

const int _ISORT_MAX = 32;	// maximum size for insertion sorttemplate<class _RanIt,class _Pr> inlinevoid _Sort_unchecked(_RanIt _First, _RanIt _Last, _Iter_diff_t<_RanIt> _Ideal, _Pr _Pred){	// order [_First, _Last), using _Pred_Iter_diff_t<_RanIt> _Count;while (_ISORT_MAX < (_Count = _Last - _First) && 0 < _Ideal){	// divide and conquer by quicksortauto _Mid = _Partition_by_median_guess_unchecked(_First, _Last, _Pred);// TRANSITION, VSO#433486_Ideal = (_Ideal >> 1) + (_Ideal >> 2);	// allow 1.5 log2(N) divisionsif (_Mid.first - _First < _Last - _Mid.second){	// loop on second half_Sort_unchecked(_First, _Mid.first, _Ideal, _Pred);_First = _Mid.second;}else{	// loop on first half_Sort_unchecked(_Mid.second, _Last, _Ideal, _Pred);_Last = _Mid.first;}}if (_ISORT_MAX < _Count){	// heap sort if too many divisions_Make_heap_unchecked(_First, _Last, _Pred);_Sort_heap_unchecked(_First, _Last, _Pred);}else if (2 <= _Count){_Insertion_sort_unchecked(_First, _Last, _Pred);	// small}}template<class _RanIt,class _Pr> inlinevoid sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred){	// order [_First, _Last), using _Pred_Adl_verify_range(_First, _Last);  //用于检查区间的合法性const auto _UFirst = _Get_unwrapped(_First);const auto _ULast = _Get_unwrapped(_Last);_Sort_unchecked(_UFirst, _ULast, _ULast - _UFirst, _Pass_fn(_Pred));}template<class _RanIt> inlinevoid sort(const _RanIt _First, const _RanIt _Last){	// order [_First, _Last), using operator<_STD sort(_First, _Last, less<>());}

我们在调用sort时(没有传入第三个参数),在algorithm中会去调用sort(const _RanIt _First, const _RanIt _Last),在内部会调用sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred),进而会去调用_Sort_unchecked(_RanIt _First, _RanIt _Last, _Iter_diff_t<_RanIt> _Ideal, _Pr _Pred)

因此,主要调用的就是_Sort_unchecked函数:

void _Sort_unchecked(_RanIt _First, _RanIt _Last, _Iter_diff_t<_RanIt> _Ideal, _Pr _Pred)

_First:首元素的迭代器
_Last:末尾元素下一个位置的迭代器
_Ideal:递归层数
_Pred:指定的排序方式

在_Sort_unchecked函数中,如果需要排序的元素是否大于_ISORT_MAX(32)并且递归深度大于0,就进入while循环,通过_Partition_by_median_guess_unchecked函数选取一个mid,然后更新当前排序元素个数的递归深度,接着会递归调用_Sort_unchecked函数。

如果没有进入while循环,就说明但当前排序的元素个数小于等于_ISORT_MAX(32),或者递归深度小于等于0,接着就判断当前排序的元素个数是否大于_ISORT_MAX(32),如果是则说明递归深度小于等于0,因此就采用堆排序来控制递归深度

如果上述条件都没满足,则说明需要排序的元素小于等于32,大于2,此时采用插入排序来进行优化

递归的深度通过以下方式来控制,每次递归,递归的深度都会通过以下方式减少

_Ideal = (_Ideal >> 1) + (_Ideal >> 2);	// allow 1.5 log2(N) divisions

最多允许递归1.5*log2(N)层,比如需要排序的元素为1024,那么1.5*log2(1024)=15层,因此最多递归15层,如果需要排序的元素为512,那么最多递归8层

2、算法优化

在这里就不再过多赘述快排的原理,如果还有不知道的小伙伴,就看看我以往写的博客快速排序

因为快排需要选一个基准值用于比较,如果只是单纯的选择最左边的值或者选择最右边的,当将一个有序数列进行反向排序时(升序改为降序,降序改为升序),那么时间复杂度必然为n2,为了防止这个问题,在_Guess_median_unchecked函数内还进行了优化,优化的方式就是三数取中

auto _Mid = _Partition_by_median_guess_unchecked(_First, _Last, _Pred);

template<class _RanIt,class _Pr> inlinepair<_RanIt, _RanIt>_Partition_by_median_guess_unchecked(_RanIt _First, _RanIt _Last, _Pr _Pred){	// partition [_First, _Last), using _Pred//这里取了一个中间值,但是在_Guess_median_unchecked内部会对该位置的数据进行优化_RanIt _Mid = _First + ((_Last - _First) >> 1);	// TRANSITION, VSO#433486_Guess_median_unchecked(_First, _Mid, _Last - 1, _Pred);_RanIt _Pfirst = _Mid;_RanIt _Plast = _Pfirst + 1;while (_First < _Pfirst&& !_DEBUG_LT_PRED(_Pred, *(_Pfirst - 1), *_Pfirst)&& !_Pred(*_Pfirst, *(_Pfirst - 1))){--_Pfirst;}while (_Plast < _Last&& !_DEBUG_LT_PRED(_Pred, *_Plast, *_Pfirst)&& !_Pred(*_Pfirst, *_Plast)){++_Plast;}_RanIt _Gfirst = _Plast;_RanIt _Glast = _Pfirst;for (;;){	// partitionfor (; _Gfirst < _Last; ++_Gfirst){if (_DEBUG_LT_PRED(_Pred, *_Pfirst, *_Gfirst)){}else if (_Pred(*_Gfirst, *_Pfirst)){break;}else if (_Plast != _Gfirst){_STD iter_swap(_Plast, _Gfirst);++_Plast;}else{++_Plast;}}for (; _First < _Glast; --_Glast){if (_DEBUG_LT_PRED(_Pred, *(_Glast - 1), *_Pfirst)){}else if (_Pred(*_Pfirst, *(_Glast - 1))){break;}else if (--_Pfirst != _Glast - 1){_STD iter_swap(_Pfirst, _Glast - 1);}}if (_Glast == _First && _Gfirst == _Last){return (pair<_RanIt, _RanIt>(_Pfirst, _Plast));}if (_Glast == _First){	// no room at bottom, rotate pivot upwardif (_Plast != _Gfirst){_STD iter_swap(_Pfirst, _Plast);}++_Plast;_STD iter_swap(_Pfirst, _Gfirst);++_Pfirst;++_Gfirst;}else if (_Gfirst == _Last){	// no room at top, rotate pivot downwardif (--_Glast != --_Pfirst){_STD iter_swap(_Glast, _Pfirst);}_STD iter_swap(_Pfirst, --_Plast);}else{_STD iter_swap(_Gfirst, --_Glast);++_Gfirst;}}}

这个Mid会返回两个值,[Mid.first,Mid.second]范围内是等于基准值的

选择基准值时,还调用函数_Guess_median_unchecked进行了优化,这个函数就是三数取中

template<class _RanIt,class _Pr> inlinevoid _Guess_median_unchecked(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred){	// sort median element to middleusing _Diff = _Iter_diff_t<_RanIt>;const _Diff _Count = _Last - _First;if (40 < _Count){	// median of nineconst _Diff _Step = (_Count + 1) >> 3; // +1 can't overflow because range was made inclusive in callerconst _Diff _Two_step = _Step << 1; // note: intentionally discards low-order bit_Med3_unchecked(_First, _First + _Step, _First + _Two_step, _Pred);_Med3_unchecked(_Mid - _Step, _Mid, _Mid + _Step, _Pred);_Med3_unchecked(_Last - _Two_step, _Last - _Step, _Last, _Pred);_Med3_unchecked(_First + _Step, _Mid, _Last - _Step, _Pred);}else{_Med3_unchecked(_First, _Mid, _Last, _Pred);}}

这里首先会判断需要排序的元素个数_Count = _Last - _First如果大于40个,则会在if内部定义两个变量,_Step 和 _Two_step,分别表示从区间的起始位置开始的1/8位置处和1/4位置处,此时就有9个位置,_First,_First + _Step,_First + _Two_step,_Mid - _Step,_Mid,_Mid + _Step,_Last - _Two_step,_Last - _Step,_Last。如图所示:

在这里插入图片描述

接着通过4组_Med3_unchecked函数,将该9个位置的数据,按照特定的排序规则排序(升序或者降序)

如果需要排序的元素个数大于40,则只需_Last,_Mid 和 _Last 位置所在的数据按照特定的排序规则排序(升序或者降序)

_Med3_unchecked函数

template<class _RanIt,class _Pr> inlinevoid _Med3_unchecked(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred){	// sort median of three elements to middleif (_DEBUG_LT_PRED(_Pred, *_Mid, *_First)){_STD iter_swap(_Mid, _First);}if (_DEBUG_LT_PRED(_Pred, *_Last, *_Mid)){	// swap middle and last, then test first again_STD iter_swap(_Last, _Mid);if (_DEBUG_LT_PRED(_Pred, *_Mid, *_First)){_STD iter_swap(_Mid, _First);}}}

_DEBUG_LT_PRED这个宏的作用就是判断后两个参数,也就是位置所对应的数据是否按照特定的排序方式(_Pred)排序,如果不是的话,就通过iter_swap交换

通过_Guess_median_unchecked(_First, _Mid, _Last - 1, _Pred)这个函数,我们已经取得mid基准值,并且mid位置的数据一定大于等于_First,并且小于等于_Last - 1

接着会定义 _Pfirst 和 _Plast

_RanIt _Pfirst = _Mid;
_RanIt _Plast = _Pfirst + 1;

此时的位置关系图如下:

在这里插入图片描述

后面两个while是用来选等于基准值的数,最后_Pfirst的处等于基准值,而_Pfirst + 1一定不等于基准值

接着会定义_Gfirst 和 _Glast

_RanIt _Gfirst = _Plast;
_RanIt _Glast = _Pfirst;

位置关系如下:

在这里插入图片描述

下面的两个for循环就是分别是_Gfirst出发向右走,_Glast出发向左走,以_Gfirst为例子,如果遇到大于基准值(_Pfirst)那么进行下一轮循环,如果小于,那么break,然后在下面和_Glast交换,如果是相等那么和_Plast交换并更新

3、总结

①STL中sort的底层是采用哪种或者哪几种排序?

采用三种排序
如果元素的个数小于等于32,那么就直接使用插入排序

如果元素的个数大于32,但是已经达到最大的递归深度,则采用堆排序
这里为什么采用堆排序,而不采用归并排序呢?因为堆排序不需要消耗额外的空间

如果元素的个数大于32,但还没达到最大的递归深度,则采用快速排序

STL中sort会导致栈溢出吗?

不会,因为对最大的递归深度做了限制,最大的深度不能超过1.5*log2(N)层

③快速排序的时间复杂度是不稳定的 l o g 2 n log_2n log2n,最坏情况会变为n2,如何解决?

sort快排进行了优化,即对基准值做了优化,如果基准值恰好是最大或者最小值那么快排的复杂度会达到n2,因此需要选择一个适中的基准值。如果元素个数大于40个,那么会将整个区间划分为8段,9个位置,对这个9个位置的数据进行排序,最终取出mid。如果元素个数小于等于40个,那么会将整个区间划分为2段,3个位置,对这个3个位置的数据进行排序,最终取出mid。

④STL中sort是如何控制递归深度的,如果已经到达了递归的深度,此时还没排完序该怎么办?

通过这行代码控制递归深度

_Ideal = (_Ideal >> 1) + (_Ideal >> 2);	// allow 1.5 log2(N) divisions

最大的深度不能超过1.5*log2(N)层,如果超过了最大递归深度,则采用堆排序

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

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

相关文章

2024年顶级的9个 Android 数据恢复工具(免费和付费)

不同的事情可能会损坏您的Android手机并导致您丢失数据。但大多数时候&#xff0c;您可以使用取证工具恢复部分或全部文件。 问题可能来自手机的物理损坏、磁盘的逻辑故障、完整的系统擦除&#xff0c;或者只是简单的粗心大意。 但是&#xff0c;无论数据丢失的原因是什么&am…

docker小白第四天

docker小白第一天 什么是镜像 1、是一种轻量级、可执行的独立软件包&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等)&#xff0c;这个打包好的运行环境就…

三、Spring IoC 容器和核心概念

本章概要 组件和组件管理概念 什么是组件&#xff1f;我们的期待Spring充当组件管理角色&#xff08;IoC&#xff09;组件交给Spring管理优势 Spring IoC 容器和容器实现 普通和复杂容器SpringIoC 容器介绍SpringIoC 容器具体接口和实现类SpringIoC 容器管理配置方式 Spring I…

Golang学习之路一开山篇

Golang学习之路一开山篇 初识 Golang 我第一次接触 Golang 是在2016年, 当时在深圳工作, 项目需要用Golang, 当时在犹豫要不要学还是走, 毕竟Java开发搞了很多年了, 说放弃还是有难度的, 其实也不是放弃Java, 说不定其他项目还是要使用Java. 在领导的再三劝说下, 开启了Golan…

嵌入式开发人员需要具备哪些能力?

大家好&#xff0c;今天给大家介绍嵌入式开发人员需要具备哪些能力&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 嵌入式开发人员需要具备以下能力&#xff1a; 熟练掌握C/C语…

Kubernetes 的用法和解析 -- 2

一.集群常用指令 1.1 基础控制指令 # 查看对应资源: 状态 $ kubectl get <SOURCE_NAME> -n <NAMESPACE> -o wide [rootkube-master ~]# kubectl get pods -n kuboard -o wide# 查看对应资源: 事件信息 $ kubectl describe <SOURCE_NAME> <SOURCE_NAME_R…

产品入门第五讲:Axure交互和情境

目录 一.Axure交互和情境的介绍 1.交互介绍 概念 常见的Axure交互设计技巧 2.情境介绍 概念 常见的Axure情境设计技巧&#xff1a; 二.实例展示 1.ERP登录页到主页的跳转 2.ERP的菜单跳转到各个页面 &#x1f4da;&#x1f4da; &#x1f3c5;我是默&#xff0c;一个…

七. 使用ts写一个贪吃蛇小游戏

之前学习了几篇的ts基础&#xff0c;今天我们就使用ts来完成一个贪吃蛇的小游戏。 游戏拆解 我们将我们的任务进行简单拆解分析。 首先我们应该有一个窗口&#xff0c;我们叫做屏幕。让蛇在里面移动&#xff0c;所有我们应该想到要设计一个大盒子当作地图。考虑到食物以及蛇…

【LeetCode刷题笔记(7-1)】【Python】【四数之和】【哈希表】【中等】

文章目录 四数之和题目描述示例 1示例 2提示解决方案1&#xff1a;【四层遍历查找】解决方案2&#xff1a;【哈希表】【三层遍历】 结束语 四数之和 四数之和 题目描述 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件…

服务器一直掉线怎么回事?

随着网络的高速发展&#xff0c;不管是网站还是游戏&#xff0c;如果遇到服务器卡顿的情况&#xff0c;会造成用户访问网站或进游戏&#xff0c;网站页面长时间无法打开&#xff0c;游戏页面运行卡顿&#xff0c;这样就很容易会造成用户的流失&#xff0c;从而导致业务亏损极大…

可视化数据监控大屏网页界面,数据大屏模版PS资料(免费UI源文件)

数据大屏模板在大数据领域被广泛应用&#xff0c;其优势在于能够将复杂的数据通过图形、图表等方式呈现出来&#xff0c;使数据更易于理解。数据大屏模板可以用来进行数据分析。通过对数据的比较、趋势分析、异常检测等&#xff0c;可以发现数据中的规律和问题&#xff0c;为决…

Appium知多少

Appium我想大家都不陌生&#xff0c;这是主流的移动自动化工具&#xff0c;但你对它真的了解么&#xff1f;为什么很多同学搭建环境时碰到各种问题也而不知该如何解决。 appium为什么英语词典查不到中文含义&#xff1f; appium是一个合成词&#xff0c;分别取自“application…

51单片机项目(21)——基于51单片机的音乐流水灯

1.功能描述 本次所做设计&#xff0c;有流水灯的功能&#xff0c;使用了16颗LED灯&#xff0c;同时还可以播放音乐。单片机存储了三首音乐&#xff0c;通过声音检测模块触发其进行切换。&#xff08;仿真图里面使用一个按键来代码声音检测模块&#xff09; 此外&#xff0c;还…

四十七、Redis分片集群

目录 一、分片集群结构 二、散列插槽 1、Redis如何判断某个key应该在哪个实例&#xff1f; 2、如何将同一类数据固定的保存在同一个Redis实例&#xff1f; 三、集群伸缩 四、故障转移 1、当集群中有一个master宕机时 &#xff08;1&#xff09;自动转移 &#xff08;2&…

使用opencv的Laplacian算子实现图像边缘检测

1 边缘检测介绍 图像边缘检测技术是图像处理和计算机视觉等领域最基本的问题&#xff0c;也是经典的技术难题之一。如何快速、精确地提取图像边缘信息&#xff0c;一直是国内外的研究热点&#xff0c;同时边缘的检测也是图像处理中的一个难题。早期的经典算法包括边缘算子方法…

Linux的文件系统 内核结构

Linux的文件系统 Q1&#xff1a;什么是文件系统&#xff1f; A&#xff1a;在学术的角度下&#xff0c;文件系统指“操作系统用于明确存储设备组织文件的方法”&#xff0c;是“文件管理系统”的简称&#xff0c;本质也是代码&#xff0c;一段程序 Q2&#xff1a;文件系统&…

Elasticsearch:相关性工作台 - BM25 及 ELSER 的相关性比较

我们知道 Elastics Learned Sparse EncoderR (ELSER) 可以被用来做语义搜索。它是一个 out-of-domain 的语义搜索模型。无需训练&#xff0c;我们就可以得到很好的相关性。有关 ELSER 的更多知识&#xff0c;请参考文章 “Elastic Learned Sparse Encoder 简介&#xff1a;Elas…

jenkins-Generic Webhook Trigger指定分支构建

文章目录 1 需求分析1.1 关键词 : 2、webhooks 是什么&#xff1f;3、配置步骤3.1 github 里需要的仓库配置&#xff1a;3.2 jenkins 的主要配置3.3 option filter配置用于匹配目标分支 实现指定分支构建 1 需求分析 一个项目一般会开多个分支进行开发&#xff0c;测试&#x…

网络入门---可变参数原理和日志模拟实现

目录标题 前言有关函数的几个性质介绍可变参数的用法介绍可变参数的一个注意事项可变参数的底层原理va_listva_endva_startva_arg_INTSIZEOF 可变参数的注意事项日志的实现日志的测试 前言 在上一篇文章中我们介绍了TCP协议有关的函数&#xff0c;大致就是服务端先通过listen函…

Springboot的火车票订票系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; Springboot的火车票订票系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#…