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.xdnf.cn/news/16470.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【freertos】FreeRTOS时间管理

FreeRTOS时间管理 一、睡眠延时函数1、vTaskDelay2、vTaskDelayUntil3、相对延时与绝对延时对比 二、自定义延时函数1、微秒延时2、毫秒延时 一、睡眠延时函数 1、vTaskDelay \quad 在UCOSIII 中延时函数OSTimeDly()可以设置为三种模式:相对模式、周期模式和绝对模式。在FreeR…

栈相关算法题1|通过栈判断链表是否对称|共享栈入栈出栈|括号匹配|多种括号配对|递归求序列最大值(C)

通过栈判断链表是否对称 设单链表的表头指针为L&#xff0c;data域为字符型&#xff0c;判断该链表的全部n个字符是否中心对称 xyx&#xff0c;xyyx 算法思想 使用栈来判断链表中的数据是否中心对称&#xff0c;让链表的前一半元素依次进栈 在处理链表的后一半元素时&#x…

datawhale11月组队学习 模型压缩技术3:2:4结构稀疏化BERT模型

文章目录 一、 半结构化稀疏性简介二、 代码实践2.1 定义辅助函数2.2 加载模型、tokenizer和数据集2.3 测试baseline模型指标2.4 对BERT-base模型进行半结构稀疏化 《datawhale2411组队学习之模型压缩技术1&#xff1a;模型剪枝&#xff08;上&#xff09;》&#xff1a;介绍模…

Qt中实现旋转动画效果

使用QPropertyAnimation类绑定对应的属性后 就可以给这个属性设置对应的动画 //比如自定义了属性 Q_PROPERTY(int rotation READ rotation WRITE setRotation)//给这个属性加动画效果 //参数1&#xff1a;谁要加动画效果 //参数2&#xff1a;哪个属性加动画效果 //参数3&…

视频流媒体播放器EasyPlayer.js RTSP播放器视频颜色变灰色/渲染发绿的原因分析

EasyPlayer.js RTSP播放器属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;无须安装任何插件&#xff0c;起播快、延迟低、兼容性强&#xff0c;使用非常便捷。 EasyPlayer.js播放器不仅支持H.264与H.265视频编码格式&#xff0…

SpringBoot+Vue3开发会议管理系统

1 项目介绍 会议管理系统&#xff0c;简化公司内会议方面的流程&#xff0c;提供便捷。实现对会议室的管理、会议的管理、会议预约的管理&#xff0c;三大主流程模块。 系统分为三种角色&#xff0c;分别是员工、管理员和超级管理员。 员工角色功能&#xff1a;查看会议室占…

前端 JS 实用操作总结

目录 1、重构解构 1、数组解构 2、对象解构 3、...展开 2、箭头函数 1、简写 2、this指向 3、没有arguments 4、普通函数this的指向 3、数组实用方法 1、map和filter 2、find 3、reduce 1、重构解构 1、数组解构 const arr ["唐僧", "孙悟空&quo…

Clip结合Faiss+Flask简易版文搜图服务

一、实现 使用目录结构&#xff1a; templates ---upload.html faiss_app.py 前端代码&#xff1a;upload.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content&quo…

【鸿蒙开发】第十一章 Stage模型应用组件-任务Mission

目录 1 任务(Mission)管理场景 2 任务&#xff08;Mission&#xff09;与启动模式 2.1 singleton单实例模式 2.2 multiton多实例模式 2.3 specified指定实例模式 3 页面栈及任务链 3.1 页面栈 3.2 任务链 4 设置任务快照的图标和名称 4.1 设置任务快照的图标&#xf…

探索 HTML 和 CSS 实现的模拟时钟

效果演示 这段代码是一个模拟时钟的 HTML 和 CSS 代码。它创建了一个简单的数字时钟界面&#xff0c;包括时针、分针和秒针。 HTML <div class"face"><p class"v-index">II</p><p class"h-index">II</p><d…

CSS预编译器:让样式编写更高效的秘密武器(6)

在现代前端开发中&#xff0c;CSS 预编译器是一种非常有用的工具&#xff0c;它通过扩展 CSS 语言的功能&#xff0c;帮助开发者更高效地编写和维护样式代码。本文将介绍 CSS 预编译器的基本原理&#xff0c;并重点讲解 LESS 的安装和使用方法。 1. 基本原理 编写 CSS 时&…

Vue3中实现插槽使用

目录 一、前言 二、插槽类型 三、示例 四、插槽的分类实现 1. 基本插槽 2. 命名插槽 3. 默认插槽内容 4. 作用域插槽&#xff08;Scoped Slots&#xff09; 5. 多插槽与具名插槽组合 一、前言 在 Vue 3 中&#xff0c;插槽&#xff08;Slot&#xff09;用于实现组件的内…

爬虫——Requests库的使用

在爬虫开发中&#xff0c;HTTP请求是与服务器进行交互的关键操作。通过发送HTTP请求&#xff0c;爬虫可以获取目标网页或接口的数据&#xff0c;而有效地处理请求和响应是爬虫能够高效且稳定运行的基础。Requests库作为Python中最常用的HTTP请求库&#xff0c;因其简洁、易用和…

如何使用EasyExcel生成多列表组合填充的复杂Excel示例

作者&#xff1a;Funky_oaNiu 一、&#xff08;需求&#xff09;生成的表格效果&#xff1a;二、搞一个模板文件三、建立对应的表格实体类四、开始填充五、Vue3前端发起请求下载六、官方文档及AI问答 一、&#xff08;需求&#xff09;生成的表格效果&#xff1a; 其中只有顶部…

三、计算机视觉_02计算机视觉领域的四大基本任务

0、前言 计算机视觉是人工智能领域的一个重要分支&#xff0c;它是一个跨学科的领域&#xff0c;涉及计算机科学、人工智能、机器学习、图像处理、神经科学等多个学科的知识 计算机视觉使用计算机技术来模拟人类视觉系统的功能&#xff0c;使计算机能够从图像或多维数据中提取…

Docker: ubuntu系统下Docker的安装

安装依赖 操作系统版本 Ubuntu Kinetic 22.10Ubuntu Jammy 24.04 (LTS)Ubuntu Jammy 22.04 (LTS)Ubuntu Focal 20.04 (LTS)Ubuntu Bionic 18.04 (LTS) CPU架构支持 ARMx86_64 查看我们的系统版本信息 uname -a通过该命令查得cpu架构是x86_64的&#xff1b; cat /etc/*re…

【已解决】 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…

LeetCode74. 搜索二维矩阵(2024冬季每日一题 6)

给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。…

数据分析24.11.13

Excel 函数 求和 函数 sum() sumif() SUMIF(range, criteria, [sum_range]) sumifs() average() count() max() min() 逻辑 函数 if() iferror() 查询函数 VLOOKUP()

DveOps-Git-版本控制

1. 概述 分布式版本控制系统 版本控制 2. Git极速上手指南 官方传送门:Git - Branching and Merging 2.1 安装 ## windows https: git-scm.com/download/## Linux(CentOS/Fedora/Rocky Linux/RHEL) yum install -y git ## MacOS brew install git## Ubuntu/Debian apt in…