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 sort

template<class _RanIt,
	class _Pr> inline
	void _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 quicksort
		auto _Mid = _Partition_by_median_guess_unchecked(_First, _Last, _Pred);
		// TRANSITION, VSO#433486
		_Ideal = (_Ideal >> 1) + (_Ideal >> 2);	// allow 1.5 log2(N) divisions

		if (_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> inline
	void 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> inline
	void 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> inline
	pair<_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 (;;)
		{	// partition
		for (; _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 upward
			if (_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 downward
			if (--_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> inline
	void _Guess_median_unchecked(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred)
	{	// sort median element to middle
	using _Diff = _Iter_diff_t<_RanIt>;
	const _Diff _Count = _Last - _First;
	if (40 < _Count)
		{	// median of nine
		const _Diff _Step = (_Count + 1) >> 3; // +1 can't overflow because range was made inclusive in caller
		const _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> inline
	void _Med3_unchecked(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred)
	{	// sort median of three elements to middle
	if (_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)层,如果超过了最大递归深度,则采用堆排序

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Java (spring-boot)的二手物品交易平台

​ 一、项目介绍 1、管理员功能 &#xff08;1&#xff09;登录&#xff1a;管理员能够根据账号访问系统。 &#xff08;2&#xff09;用户管理&#xff1a;管理员可以添加、删除、修改用户信息&#xff0c;查看用户列表&#xff0c;对用户进行管理和控制。例如&#xff0c…

死锁的预防、避免、检测和消除

一、预防死锁 1. 破坏互斥条件 2. 破坏不剥夺条件 3.破坏请求和保持条件 4.破坏循环等待条件 二、避免死锁 避免死锁的一种方法是使用银行家算法&#xff0c;它涉及到安全序列的概念。银行家算法是一种资源分配和死锁避免的算法&#xff0c;它确保系统能够分配资源而不会导致死…

十五、YARN辅助架构

1、学习内容 &#xff08;1&#xff09;了解什么是代理服务器 &#xff08;2&#xff09;了解什么是历史服务器 2、辅助架构 &#xff08;1&#xff09;辅助架构的由来 对于YARN架构来讲&#xff0c;除了ResourceManager集群资源总管家、NodeManager单机资源管家两个核心角…

『番外篇三』Swift “乱弹”之带索引遍历异步序列(AsyncSequence)

概览 在 Swift 开发中,我们往往在遍历集合元素的同时希望获得元素对应的索引。在本课中,我们将向小伙伴们展示除 enumerated() 方法之外的几种实现思路。在玩转普通集合之后,我们将用“魔法棒”进一步搞定异步序列带索引遍历的实现。 在本篇博主中,您将学到以下内容: 概…

Makefile基础使用与原理

一、基本概念 通常我们编写好代码后&#xff0c;都需要编译&#xff0c;只是这些操作是由IDE来完成&#xff0c;我们只需要点击一个编译按钮。当项目工程越来越庞大&#xff0c;存在几十个甚至更多的文件的时候&#xff0c;你使用的不是IDE工具&#xff0c;而是命令行&#xf…

基于JavaWeb+SSM+Vue微信小程序的移动学习平台系统的设计和实现

基于JavaWebSSMVue微信小程序的移动学习平台系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 Lun文目录 第1章 绪论 1 1.1 课题背景 1 1.2 课题意义 1 1.3 研究内容 2 第2章 开发环…

采用nodejs + socket.io实现简易聊天室功能(群聊 + 私聊)

项目演示 支持群聊以及私聊 项目代码 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport…

Android12-RK3588_s-开机动画

目录 一、实现自定义开机动画 步骤一&#xff1a;准备 bootanimation.zip 步骤二&#xff1a;将 bootanimation.zip 放到 /system/media/bootanimation.zip下 步骤三&#xff1a;重启即可 二、注意事项 2.1 bootanimation.zip 压缩 2.2 bootanimation.zip 存放 2.3 boo…

RabbitMQ插件详解:rabbitmq_web_stomp【RabbitMQ 六】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 《RabbitMQ Web STOMP&#xff1a;打破界限的消息传递之舞》 前言STOMP协议简介STOMP&#xff08;Simple Text Oriented Messaging Protocol&#xff09;协议简介STOMP与WebSocket的关系 WebSocket和R…

Certbot实现 HTTPS 免费证书(Let‘s Encrypt)自动续期

Certbot实现 HTTPS 自动续期 以前阿里云支持申请一年的免费https证书&#xff0c;那每年我们手动更新证书并没什么大问题&#xff0c;但现在阿里云的免费证书仅支持3个月&#xff0c;这意味着每三个月都要要申请一下证书显得非常麻烦。 下面我们使用Certbot实现ssl证书的自动…

[Linux] LVS负载均衡群集+NAT部署

一、LVS负载均衡群集知识 1.1 群集的的定义及意义 Cluster&#xff0c;集群&#xff08;也称群集&#xff09;由多台主机构成&#xff0c;但对外只表现为一一个整体&#xff0c;只提供一-个访问入口(域名或IP地址)&#xff0c; 相当于一台大型计算机。 群集的作用&#xff1…

常用函数之js复制图片至剪切板

背景 最近在工作中遇到了一个需求&#xff0c;点击按钮将Echart图复制到剪切板&#xff0c;然后按Ctrl&#xff08;command&#xff09;V可以直接复制到聊天软件&文档编辑器中。本以为这是一个比较简单的需求&#xff0c;好像找了一圈资料&#xff0c;发现事情并不简单&am…

java线程的几种状态

一、线程的状态 Java中的线程有以下几种状态&#xff1a; 1. 新建状态&#xff08;New&#xff09;&#xff1a;当线程对象被创建但还没有被调用start()方法时&#xff0c;线程处于新建状态。 2. 运行状态&#xff08;Runnable&#xff09;&#xff1a;当线程启动后&#xff0c…

西瓜视频RenderThread引起的闪退问题攻坚历程

背景 影响 西瓜之前存在过一类RenderThread闪退&#xff0c;从堆栈上看&#xff0c;全部都是系统so调用&#xff0c;给人的第一印象像是一个系统bug&#xff0c;无从下手。闪退集中在Android 5~6上&#xff0c;表现为打开直播间立即闪退。该问题在2022年占据Native Crash Top5&…

C++异步网络库workflow系列教程(3)Series串联任务流

往期教程 如果觉得写的可以,请给一个点赞关注支持一下 观看之前请先看,往期的两篇博客教程,否则这篇博客没办法看懂 workFlow c异步网络库编译教程与简介 C异步网络库workflow入门教程(1)HTTP任务 C异步网络库workflow系列教程(2)redis任务 简介 首先,workflow是任务流的意…

『番外篇二』Swift “黑魔法”之动态获取类实例隐藏属性的值

概览 在 Swift 代码的调试中,我们时常惊叹调试器的无所不能:对于大部分“黑盒”类实例的内容,调试器也都能探查的一清二楚。 想要自己在运行时也能轻松找到 Thread 实例“私有”属性的值吗(比如 seqNum)? 在本篇博文中您将学到如下内容: 概览1. 借我,借我,一双慧眼吧…

Dockerfile创建镜像LNMP+WordPress

目录 实验部署 nginx 配置mysql 配置php 实验部署 INMPwordpress nginx 172.111.0.10 docker-nginx mysql 172.111.0.20 docker-mysql php 172.111.0.30 docker-php nginx 关闭防火墙和安全机制在opt目录创建nginx MySQL php目录 cd nginx mysql php vim Dockerfile#声…

rabbitmq-windows安装使用-简易后台界面-修改密码

文章目录 1.下载2.安装3.安装 RabbitMQ4.后台访问5.修改密码 1.下载 将erlang运行时和rabbitmq-windows版本&#xff0c;上传在csdn&#xff0c;下载链接。https://download.csdn.net/download/m0_67316550/88633443 2.安装 右键&#xff0c;以管理员身份运行rabbitmq。启动…

mysql:修改整数字段的显式长度不生效

例如&#xff0c;我使用mysql 8.2.0版本&#xff0c;想修改整数字段的显式长度&#xff0c;不会生效&#xff0c;提醒整数的显示长度已经废弃&#xff0c;会在将来某个版本去掉&#xff1a; mysql官网中也有说明&#xff1a; https://dev.mysql.com/doc/refman/8.2/en/numeric…

【JetBrains】将Gateway中的GoLand回滚到无bug旧版本

问题背景 2023-12-15 我把 Gateway 中使用的 GoLand 从 2023.2.x 升级到了 2023.3 &#xff0c;然后编辑文件过程中输入时时不时会显示错误信息&#xff0c;然后就会进入无法输入&#xff08;键入也不会看到增加字符&#xff09;但能粘贴的奇怪状态。 问题解决 升级到 2023.…