std::sort的底层原理(混合排序算法)

std::sort 是 C++ 标准库中最常用的排序算法之一,其底层实现虽然在不同的编译器和标准库实现中有所差异,但大多数实现都遵循一定的准则,通常使用混合排序算法。常见的实现方案包括 快速排序(QuickSort)插入排序(Insertion Sort)堆排序(HeapSort) 等,基于数据的大小和特性进行选择。我们将以 libc++libstdc++ 等实现为例来探讨 std::sort 的源码实现。

1. std::sort 的核心思想

std::sort 是一个 排序算法,它的目标是对一个区间中的元素进行排序,并且提供 O(n log n) 平均时间复杂度。根据不同的输入情况,std::sort 会采用不同的排序策略。例如,对于大数据集,它通常会使用快速排序,而对于小数据集或几乎有序的数组,它可能会使用插入排序。

2. 常见 STL 实现

2.1 libstdc++(GCC 的标准库实现)

libstdc++ 中的 std::sort 实现通常使用 混合排序算法。具体的实现大致可以分为以下几个步骤:

  1. 小数据量切换到插入排序:当数据规模小于一定阈值时,std::sort 切换到插入排序。
  2. 使用快速排序:对于较大数据集,std::sort 会使用快速排序(QuickSort)。为了避免快速排序的最坏情况,std::sort 通常会采用 三数取中法(Median-of-Three) 来选择基准元素,从而减少退化为 O(n²) 的概率。
  3. 堆排序(HeapSort)作为备用:如果快速排序的递归深度过大,或者递归调用导致栈溢出,std::sort 会退回到堆排序。

为什么当数量<=16个时不继续用快排而是插入排序:

因为插入排序在CPU缓存中连续性好,这种小量排序哪怕时间复杂度时On2的,仍然可以比插入排序这种缓存经常跳跃的要快。

2.2 libc++(LLVM 的标准库实现)

libc++std::sort 实现也采用类似的混合排序策略。它使用 快速排序 作为默认算法,并且结合 插入排序堆排序 来优化不同的情况。特别地,libc++ 在选择基准元素时,也会采用 三数取中法,并且在递归深度过大时,会使用堆排序。

3. std::sort 的源码实现(以 GCC libstdc++ 为例)

为了更直观地了解 std::sort 的实现,我们将基于 libstdc++(GCC 的标准库)来分析其实现。以下是简化版的 std::sort 实现:

3.1 快速排序的实现

std::sort 中,快速排序通常是主要的排序算法。为了避免最坏情况,std::sort 会在每次递归时使用 三数取中法 来选择基准元素。这个方法是通过选择序列的第一个元素、最后一个元素和中间元素中的中位数来作为基准。

template <typename RandomAccessIterator>
void quick_sort(RandomAccessIterator first, RandomAccessIterator last) {if (last - first <= 1) return;// 选择基准元素:三数取中法RandomAccessIterator mid = first + (last - first) / 2;if (*mid < *first) std::swap(*mid, *first);if (*last < *first) std::swap(*last, *first);if (*mid < *last) std::swap(*mid, *last);RandomAccessIterator pivot = mid;// 分区操作RandomAccessIterator left = first;RandomAccessIterator right = last - 1;while (true) {while (*left < *pivot) ++left;while (*pivot < *right) --right;if (left >= right) break;std::swap(*left, *right);++left;--right;}quick_sort(first, right);quick_sort(left, last);
}
3.2 插入排序的实现

当数据规模较小或数据已经有序时,std::sort 会切换到 插入排序。插入排序的时间复杂度是 O(n²),但在数据量较小时,插入排序比其他 O(n log n) 算法(如快速排序)表现更好。

template <typename RandomAccessIterator>
void insertion_sort(RandomAccessIterator first, RandomAccessIterator last) {for (RandomAccessIterator i = first + 1; i != last; ++i) {auto key = *i;RandomAccessIterator j = i;while (j > first && *(j - 1) > key) {*j = *(j - 1);--j;}*j = key;}
}
3.3 堆排序的实现

如果快速排序的递归深度过大,或者出现堆栈溢出,std::sort 会切换到 堆排序。堆排序的时间复杂度为 O(n log n),并且在最坏情况下依然能保持这一复杂度。

template <typename RandomAccessIterator>
void heapify(RandomAccessIterator first, RandomAccessIterator last, RandomAccessIterator root) {RandomAccessIterator left = first + (2 * (root - first) + 1);RandomAccessIterator right = first + (2 * (root - first) + 2);RandomAccessIterator largest = root;if (left < last && *left > *largest) largest = left;if (right < last && *right > *largest) largest = right;if (largest != root) {std::swap(*root, *largest);heapify(first, last, largest);}
}template <typename RandomAccessIterator>
void heap_sort(RandomAccessIterator first, RandomAccessIterator last) {for (RandomAccessIterator i = first + (last - first) / 2 - 1; i >= first; --i) {heapify(first, last, i);}for (RandomAccessIterator i = last - 1; i > first; --i) {std::swap(*first, *i);heapify(first, i, first);}
}
3.4 混合排序的实现

std::sort 在不同的场景下会选择不同的排序算法。如果数据规模较小,则切换到插入排序;如果数据较大且递归深度过大,则切换到堆排序。实际的 std::sort 通常会有一个阈值来决定何时切换到插入排序。

template <typename RandomAccessIterator>
void sort(RandomAccessIterator first, RandomAccessIterator last) {if (last - first <= INSERTION_SORT_THRESHOLD) {insertion_sort(first, last);  // 小规模使用插入排序} else {quick_sort(first, last);  // 默认使用快速排序}
}

std::sort 的优缺点总结

std::sort 是 C++ 标准库中最常用的排序算法,它的底层实现通常是基于 混合排序算法,结合了多种排序算法的优点,以确保在不同数据分布和规模下都能够提供高效的排序性能。以下是 std::sort 的优缺点总结:

优点
  1. 高效的平均性能

    • std::sort 在大多数情况下的时间复杂度为 O(n log n),特别是对于无序的大数据集。通常,std::sort 默认使用快速排序(QuickSort),在数据量大时能够提供非常高效的排序。
  2. 针对小数据集优化

    • 对于数据量较小或已部分排序的数据,std::sort 会使用 插入排序,插入排序在小数据集或接近有序的情况下具有较低的开销,能够比其他排序算法(如快速排序)更快。
  3. 避免最坏情况的性能退化

    • 快速排序的最坏时间复杂度为 O(n²),但通过采用 三数取中法(Median-of-Three)来选择基准元素,可以有效避免数据已经有序时发生最坏情况。此外,递归深度过深时,std::sort 可能会转而使用 堆排序,保证最坏情况的时间复杂度始终为 O(n log n)。
  4. 内存效率

    • std::sort 通常是 原地排序(in-place sort),不需要额外的内存开销。与需要额外内存的排序算法(如归并排序)相比,std::sort 在内存消耗上更加高效。
  5. 稳定性(可选)

    • 虽然 std::sort 本身不是稳定排序算法(即相等的元素可能会改变原来的顺序),但 std::stable_sort 是稳定版本,适用于那些需要保留相等元素顺序的应用场景。
  6. 符合标准

    • std::sort 是 C++ 标准库的一部分,提供跨平台的兼容性。任何支持 C++ 标准库的编译器都实现了该算法,因此可以保证代码在不同平台间的可移植性。

缺点
  1. 最坏情况性能依然可能较差

    • 即使通过三数取中法和堆排序的退化策略,std::sort 的性能在某些极端情况下(例如大量重复元素或完全有序数据)仍然可能退化。对于非常特殊的输入数据,最坏情况下的性能可能接近 O(n²),尤其是在没有进行递归深度控制或选择基准元素不当时。
  2. 不稳定排序

    • std::sort 本身是一个 不稳定的排序算法,即相等的元素在排序后可能会改变它们的相对顺序。如果排序的稳定性对你的应用至关重要,你需要使用 std::stable_sort,而后者的性能稍逊一筹,尤其是在大数据集的情况下。
  3. 需要选择阈值

    • 在实际实现中,std::sort 会根据数据大小来决定是否使用插入排序、快速排序或堆排序,这通常需要为阈值选择适当的数值。如果选择不当,可能会导致性能下降。例如,如果快速排序的阈值设置过低,可能会导致不必要的插入排序操作,反之,如果阈值过高,可能无法充分利用插入排序的小数据集优化。
  4. 递归栈深度

    • 快速排序依赖递归实现,在递归调用的过程中,尤其是在数据规模非常大的时候,可能会面临 栈溢出 的风险。虽然许多实现通过尾递归优化或限制递归深度来避免这个问题,但在某些情况下,仍然可能因为过深的递归导致性能问题。
  5. 算法的实现复杂度

    • std::sort 作为一个混合排序算法,涉及多个算法(快速排序、堆排序、插入排序)的切换和优化,这使得实现比单一的排序算法更为复杂。如果对底层实现不熟悉,可能会增加理解和调试的难度。

4. 总结

std::sort 是 C++ 标准库中的高效排序算法,采用了混合排序策略,默认使用快速排序来排序大数据集,对于小数据集则使用插入排序,在需要时会退回到堆排序。std::sort 的实现还使用了多种优化策略,包括三数取中法、递归深度控制等,确保在不同情况下都能提供 O(n log n) 的平均时间复杂度。通过这些优化,std::sort 在大多数实际应用中表现出色。

如果你有兴趣了解具体的实现,可以查看 GCC 或 LLVM 标准库源代码,那里提供了更为详细的实现细节。

推荐:

  • std::sort 源码讲解(深入源码) gcc13.2 c++17
  • what-algorithms-are-used-in-c11-stdsort-in-different-stl-implementations
  • std::sort
    原理和源码讲解(深入源码

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

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

相关文章

Java连接MySQL(测试build path功能)

Java连接MySQL&#xff08;测试build path功能&#xff09; 实验说明下载MySQL的驱动jar包连接测试的Java代码 实验说明 要测试该情况&#xff0c;需要先安装好MySQL的环境&#xff0c;其实也可以通过测试最后提示的输出来判断build path是否成功&#xff0c;因为如果不成功会直…

DQN系列算法详解

代码链接见文末 1. Q-learning 1.1 概述 Q-Learning是一种强化学习算法,目的是通过选择能带来最大长期收益的行为来完成任务。 做事包含瞬时奖励和记忆经验奖励: 在Q-Learning中,每个动作都会带来“瞬时奖励”,同时也会根据过去的经验记住哪些行为更有利。瞬时奖励: 这里…

七、箭头函数及简写、arguments、剩余参数、展开运算符、解构数组与对象、数组常见方法(forEach、map、join、reduce)

1. 箭头函数 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head>…

.NET桌面应用架构Demo与实战|WPF+MVVM+EFCore+IOC+DI+Code First+AutoMapper

目录 .NET桌面应用架构Demo与实战|WPFMVVMEFCoreIOCDICode FirstAutoPapper技术栈简述项目地址&#xff1a;功能展示项目结构项目引用1. 新建模型2. Data层&#xff0c;依赖EF Core&#xff0c;实现数据库增删改查3. Bussiness层&#xff0c;实现具体的业务逻辑4. Service层&am…

【蓝桥杯备赛】深秋的苹果

# 4.1.1. 题目解析 要求某个区间内的数字两两相乘的总和想到前缀和&#xff0c;但是这题重点在于两两相乘先硬算&#xff0c;找找规律&#xff1a; 比如要算这串数字的两两相乘的积之和&#xff1a; 1, 2, 3 1*2 1*3 2*3 1*(23) 2*3 前缀和数组&#xff1a; 1 3 6 发现…

通过 Docker 对 MySQL 做主从复制的时候,因为ip不对导致不能同步。后又因为二进制的偏移量写的不对,导致不能同步的问题

问题一&#xff1a;Error connecting to source slave127.0.0.1:3307. This was attempt 3/86400, with a delay of 30 seconds between attempts. Message: Cant connect to MySQL server on 127.0.0.1:3307 (111) 就是因为这个ip不对&#xff0c;导致的异常。 解决方式&…

【原创】如何备份和还原Ubuntu系统,非常详细!!

前言 我在虚拟机装了一个xfce4的Ubuntu桌面版&#xff0c;外加输入法、IDEA等&#xff0c;我想将这个虚拟机里的系统直接搬到物理机中&#xff0c;那我可以省的再重新装一遍、配置xfce4桌面、修改一堆快捷键还有配置idea了&#xff0c;那直接说干就干。 本教程基于Ubuntu24.0…

VMware 中 虚拟机【Linux系统】固定 ip 访问

注意&#xff1a;这里的 参考链接 VMWare虚拟机设置固定ip_vmware虚拟机修改ip地址-CSDN博客 VMwareCentOS 7 静态IP设置方法&#xff08;保姆级教程&#xff0c;建议收藏&#xff09;-阿里云开发者社区 1&#xff09;查看宿主机中 VMnet8 的网络配置 ipconfig 2&#xff…

Windows环境GeoServer打包Docker极速入门

目录 1.前言2.安装Docker3.准备Dockerfile4.拉取linux环境5.打包镜像6.数据挂载6.测试数据挂载7.总结 1.前言 在 Windows 环境下将 GeoServer 打包为 Docker&#xff0c;可以实现跨平台一致性、简化环境配置、快速部署与恢复&#xff0c;同时便于扩展集成和版本管理&#xff0c…

day03(单片机高级)RTOS

目录 RTOS(实时操作系统) 裸机开发模式 轮询方式 前后台&#xff08;中断方式&#xff09; 改进&#xff08;前后台&#xff08;中断&#xff09;&#xff09;定时器 裸机进一步优化 裸机的其他问题 RTOS的概念 什么是RTOS 为什么要使用 RTOS RTOS的应用场景 RTOS的…

VScode使用Batch Runner插件在终端运行bat文件

搜索并安装插件Batch Runner 创建测试文件 echo off echo "Hello world"按F5运行

Debezium日常分享系列之:Debezium3版本Debezium connector for JDBC

Debezium日常分享系列之&#xff1a;Debezium3版本Debezium connector for JDBC 概述JDBC连接器的工作原理消费复杂的Debezium变更事件至少一次的传递多个任务数据和列类型映射主键处理删除模式幂等写入模式演化引用和大小写敏感性连接空闲超时数据类型映射部署Debezium JDBC连…

Redis-08 Redis集群

Redis槽位 Redis分片 Redis集群优势 主要掌握第三种 为什么槽位是16384&#xff1f; 三主三从&#xff1a; 每个主机只能写在自己的槽位 所以登录redis集群记得加参数 -c 比如redis-cli -a dc123 -p 6380 -c 加了 -c 相当于会进行路由转发&#xff0c;不属于自己槽位的…

微知-DOCA ARGP参数模块的相关接口和用法(config单元、params单元,argp pipe line,回调)

文章目录 1. 背景2. 设置参数的主要流程2.1 初始化2.2 注册某个params的处理方式以及回调函数2.4 定义好前面的params以及init指定config地点后start处理argv 3. 其他4. DOCA ARGP包相关4.1 主要接口4.2 DOCA ARGP的2个rpm包4.2.1 doca-sdk-argp-2.9.0072-1.el8.x86_64.rpm4.2.…

智能指针原理、使用和实现——C++11新特性(三)

目录 一、智能指针的理解 二、智能指针的类型 三、shared_ptr的原理 1.引用计数 2.循环引用问题 3.weak_ptr处理逻辑 四、shared_ptr的实现 五、定制删除器 六、源码 一、智能指针的理解 问题&#xff1a;什么是智能指针&#xff1f;为什么要有智能指针&#xff1f;智…

初识Linux · 信号处理 · 续

目录 前言&#xff1a; 可重入函数 重谈进程等待和优化 前言&#xff1a; 在前文&#xff0c;我们已经介绍了信号产生&#xff0c;信号保存&#xff0c;信号处理的主题内容&#xff0c;本文作为信号处理的续篇&#xff0c;主要是介绍一些不那么重要的内容&#xff0c;第一个…

IPTV智慧云桌面,后台服务器搭建笔记

环境CentOs7.9 &#xff0c;安装宝塔yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh 访问宝塔&#xff0c;修改服务器端口安全组端口 26029 注意&#xff01;&#xff01;&#xff01;&#xff01…

模型的评估指标——IoU、混淆矩阵、Precision、Recall、P-R曲线、F1-score、mAP、AP、AUC-ROC

文章目录 预测框的预测指标——IoU&#xff08;交并比&#xff09;分类预测指标混淆矩阵&#xff08;Confusion Matrix&#xff0c;TP、FP、FN、TN)Precision&#xff08;精度&#xff09;Recall&#xff08;召回率&#xff09;P-R曲线F1-scoreTPR、TNR、FPR、FNRROC曲线下面积…

本草智控:中药实验管理的智能时代

3系统分析 3.1可行性分析 通过对本中药实验管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本中药实验管理系统采用SSM框架&#xff0c;JAVA作为开发语…

父组件提交时让各自的子组件验证表格是否填写完整

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 父组件中有三个表格&#xff0c;表格中时输入框&#xff0c;有些输入框是必填的&#xff0c;在父组件提交时需要验证这三个表格的必填输入框中是否有没填写的。 原因分析&#xff1a; 提示&#xff1a…