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
    原理和源码讲解(深入源码

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

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

相关文章

DreamClear:字节跳动开源了高性能图像修复技术,中科院加持,商业免费使用

哇&#xff0c;字节跳动开源了DreamClear项目&#xff0c;采用的是Apache-2.0开源协议&#xff0c;可以商用&#xff0c;并且用户可以自由地使用、复制、修改和分发该软件&#xff0c;甚至可以用于私有项目中。这对于开发者和企业来说是个好消息&#xff0c;因为它们可以利用这…

Flutter:android studio无法运行到模拟机的问题

提示如下错误信息&#xff1a; Entrypoint is not a Dart filenot applicable for the "main.dart" configurat点击运行按钮提示让填写以下信息 或者出现无法选择模拟机的情况 发下下列问题&#xff1a; 无法运行的项目默认根目录地址&#xff1a; 可以正常运行…

FromData格式提交接口时入参被转成JSON格式问题

本地上传文件后通过事件提交文件&#xff0c;一般先通过前端组件生成文本流&#xff0c;在通过接口提交文本流&#xff0c;提交文本流一般使用FormData的入参形式传入&#xff0c;接口请求头也默认"Content-Type": “multipart/form-data”&#xff0c;但是某些场景统…

<AI 学习> 下载 Stable Diffusions via Windows OS

注意&#xff1a; 不能使用 网络路径 不再支持 HTTPS 登录&#xff0c;需要 Token 1. 获得合法的授权 Stability AI License — Stability AI 上面的链接打开&#xff0c;去申请 许可 2. 拥有 HuggingFace 账号 注册&#xff1a;https://huggingface.co/ 3. 配置 Tok…

MySQL缓存使用率超过80%的解决方法

MySQL缓存使用率超过80%的解决方法 一、识别缓存使用率过高的问题1.1 使用SHOW GLOBAL STATUS命令监控1.2 监控其他相关指标二、分析缓存使用率过高的原因2.1 数据量增长2.2 查询模式变化2.3 配置不当三、解决缓存使用率过高的方法3.1 调整Buffer Pool大小3.1.1 计算合理的Buff…

39.安卓逆向-壳-smali语法3(方法)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信。 工…

《FreeRTOS任务基础知识以及任务创建相关函数》

目录 1.FreeRTOS多任务系统与传统单片机单任务系统的区别 2.FreeRTOS中的任务&#xff08;Task&#xff09;介绍 2.1 任务特性 2.2 FreeRTOS中的任务状态 2.3 FreeRTOS中的任务优先级 2.4 在任务函数中退出 2.5 任务控制块和任务堆栈 2.5.1 任务控制块 2.5.2 任务堆栈…

RHCE的学习(18)

第二章 变量和引用 深入认识变量 在程序设计语言中&#xff0c;变量是一个非常重要的概念。也是初学者在进行Shell程序设计之前必须掌握的一个非常基础的概念。只有理解变量的使用方法&#xff0c;才能设计出良好的程序。本节将介绍Shell中变量的相关知识。 什么是变量 顾名思义…

AG32 FPGA部分简单开发

环境 Quartus 13.0&#xff08;Quartus 不能使用Lite 版本&#xff0c;需要使用Full 版本&#xff09;AGM SDKSupra&#xff08;快捷方式在SDK目录下&#xff0c;具体路径为AgRV_pio\packages\tool-agrv_logic\bin&#xff09; FPGA编程 在AG32芯片中&#xff0c;拥有异构双…

__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined

VUE_PROD_HYDRATION_MISMATCH_DETAILS 未明确定义。您正在运行 Vue 的 esm-bundler 构建&#xff0c;它期望这些编译时功能标志通过捆绑器配置全局注入&#xff0c;以便在生产捆绑包中获得更好的tree-shaking优化。 Vue.js应用程序正在使用ESM&#xff08;ECMAScript模块&#…

Spring——事务

事务 JdbcTemplate 简介 Spring框架对JDBC进行封装&#xff0c;使用JdbcTemplate方便实现对数据库操作 准备工作 ①搭建子模块 搭建子模块&#xff1a;spring-jdbc-tx ②加入依赖 <dependencies><!--spring jdbc Spring 持久化层支持jar包--><dependenc…

集合类源码浅析のJDK1.8ConcurrentHashMap(下篇)

文章目录 前言一、分段扩容1、addCount2、transfer3、helpTransfer 二、查询二、删除总结 前言 主要记录ConcurrentHashMap&#xff08;笔记中简称CHM&#xff09;的查询&#xff0c;删除&#xff0c;以及扩容方法的关键源码分析。 一、分段扩容 1、addCount 扩容的逻辑主要在…

H5页面多个视频如何只同时播放一个?

目录 背景1. 首先介绍下 muted 属性2. 监听播放和暂停操作3. 视频播放完毕后返回桌面&#xff0c;再进入H5页面发现视频封面丢失置灰解决思路&#xff1a; 背景 页面模块同时有个四个视频模块&#xff0c;发现可以同时播放四个视频&#xff0c;但是理想的是每次只播放一个。 …

D69【 python 接口自动化学习】- python 基础之数据库

day69 Python 执行 SQL 语句 学习日期&#xff1a;20241115 学习目标&#xff1a; MySQL 数据库&#xfe63;- Python连接redis 学习笔记&#xff1a; redis数据库的用途 使用Python访问redis数据库 使用Python对redis数据库进行读写操作 总结 1. redis是一款高性能的键…

jmeter常用配置元件介绍总结之逻辑控制器

系列文章目录 安装jmeter jmeter常用配置元件介绍总结之逻辑控制器 逻辑控制器1.IF控制器2.事务控制器3.循环控制器4.While控制器5.ForEach控制器6.Include控制器7.Runtime控制器8.临界部分控制器9.交替控制器10.仅一次控制器11.简单控制器12.随机控制器13.随机顺序控制器14.吞…

21.<基于Spring图书管理系统②(图书列表+删除图书+更改图书)(非强制登录版本完结)>

PS&#xff1a; 开闭原则 定义和背景‌ ‌开闭原则&#xff08;Open-Closed Principle, OCP&#xff09;‌&#xff0c;也称为开放封闭原则&#xff0c;是面向对象设计中的一个基本原则。该原则强调软件中的模块、类或函数应该对扩展开放&#xff0c;对修改封闭。这意味着一个软…

springboot实现简单的数据查询接口(无实体类)

目录 前言&#xff1a;springboot整体架构 1、ZjGxbMapper.xml 2、ZjGxbMapper.java 3、ZjGxbService.java 4、ZjGxbController.java 5、调用接口测试数据是否正确 6、打包放到服务器即可 前言&#xff1a;springboot整体架构 文件架构&#xff0c;主要编写框选的这几类…

【已解决】 Tomcat10.1.x使用JSTL标签库

IDEA创建Java EE项目&#xff0c;使用Spring Spring MVC MyBatis框架&#xff0c;使用maven管理依赖。项目当前的环境是&#xff1a; Tomat 10.1.28Maven 3.6.3JDK 17 项目的功能&#xff1a;读取数据库的report表中的数据&#xff0c;返回一个List集合对象reportList在JSP…

权限相关知识

1.Linux权限的概念 在说Linux权限的概念之前我来问大家一个问题&#xff0c;你们觉得什么是权限&#xff1f; 权限平时的体现呢&#xff0c;就比如不是校长的亲戚就不能逛办公室&#xff0c;没充会员的爱奇艺看不了VIP影视剧&#xff0c;没成会员的的蛋糕店拿不到会员价等等等…

uniapp如何i18n国际化

1、正常情况下项目在代码生成的时候就已经有i18n的相关依赖&#xff0c;如果没有可以自行使用如下命令下载&#xff1a; npm install vue-i18n --save 2、创建相关文件 en文件下&#xff1a; zh文件下&#xff1a; index文件下&#xff1a; 3、在main.js中注册&#xff1a…