IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> C++ STL中的sort源码浅析 -> 正文阅读

[数据结构与算法]C++ STL中的sort源码浅析

首先提出几个问题:
在这里插入图片描述

C++ STL默认提供以下两个sort接口

void std::sort(const _RanIt _First, const _RanIt _Last)
void std::sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred)

第一个函数的参数是排序区间,第二个函数的参数是排序区间和指定排序方式的函数对象

C++ STL默认使用less函数对象,进行升序排序

template <class _RanIt>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last) { // order [_First, _Last)
    _STD sort(_First, _Last, less<>{});
}

而实际上,对外提供的两个sort接口,最后调用的都是三个参数的这个sort接口,该接口源码如下:

template <class _RanIt, class _Pr>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred) { // order [_First, _Last)
    _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));
}

主要调用的就是_Sort_unchecked函数

void _Sort_unchecked(_RanIt _First, _RanIt _Last, _Iter_diff_t<_RanIt> _Ideal, _Pr _Pred)
  • _First:首元素的迭代器
  • _Last:末尾元素后继位置的迭代器
  • _Ideal:_Sort_unchecked会递归调用,_Ideal的初始值是排序区间的元素个数,每次递归都会减小,用于控制递归层数
  • _Pred:指定排序方式的函数对象

_Sort_unchecked函数源码如下:

template <class _RanIt, class _Pr>
_CONSTEXPR20 void _Sort_unchecked(_RanIt _First, _RanIt _Last, _Iter_diff_t<_RanIt> _Ideal, _Pr _Pred) {
    // order [_First, _Last)
    for (;;) {
        if (_Last - _First <= _ISORT_MAX) { // small
            _Insertion_sort_unchecked(_First, _Last, _Pred);
            return;
        }

        if (_Ideal <= 0) { // heap sort if too many divisions
            _Make_heap_unchecked(_First, _Last, _Pred);
            _Sort_heap_unchecked(_First, _Last, _Pred);
            return;
        }

        // divide and conquer by quicksort
        auto _Mid = _Partition_by_median_guess_unchecked(_First, _Last, _Pred);

        _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;
        }
    }
}

元素少,使用插入排序

// _INLINE_VAR constexpr int _ISORT_MAX = 32; // maximum size for insertion sort
if (_Last - _First <= _ISORT_MAX) { // small
    _Insertion_sort_unchecked(_First, _Last, _Pred);
    return;
}

首先判断排序区间的元素个数是否不足_ISORT_MAX,这个_ISORT_MAX默认是32,如果元素个数不够,就调用插入排序算法完成排序,并退出程序

由于算法主体还是使用快速排序,快速排序会把小的元素往左调整,大的元素往右调整,随着快速排序的进行,元素会逐渐趋于有序。而对于趋于有序的序列,插入排序的效率是最高的(排某个元素的时候只需要比较很少的次数)

递归次数过多,使用堆排序

if (_Ideal <= 0) { // heap sort if too many divisions
    _Make_heap_unchecked(_First, _Last, _Pred);
    _Sort_heap_unchecked(_First, _Last, _Pred);
    return;
}

由于_Sort_unchecked会递归调用,_Ideal的初始值是排序区间的元素个数,为了控制递归层数,每次递归都会以如下方式减小:

_Ideal = (_Ideal >> 1) + (_Ideal >> 2);

算法主体还是使用快速排序,当快排递归为一单边树导致递归次数过多时,为避免栈溢出和效率低下的问题,_Ideal的值会减小为0。接着就会用堆排序的方式对剩余区间的元素进行排序(堆排序时间复杂度稳定为 O ( l o g 2 n ) O(log_2n) O(log2?n),而且不占用额外的空间)

虽然归并排序的时间复杂度也是 O ( l o g 2 n ) O(log_2n) O(log2?n),但会占用额外的内存空间,源码上还是选择堆排序

在这里插入图片描述

在这里插入图片描述
当递归层数较深时,待排区间内元素也不会很多了,在数据量较小的情况下,堆排和归并排序效率相当

主体使用快速排序

// divide and conquer by quicksort
auto _Mid = _Partition_by_median_guess_unchecked(_First, _Last, _Pred);

_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;
}

最后一段,先调用快速排序,然后根据返回的_Mid,判断下次递归排序的区间,再调用_Sort_unchecked

总结

  1. STL里sort算法用的是什么排序算法?
    主要使用快速排序;区间元素少于32时,使用插入排序;快排的递归次数过多,使用堆排序

  2. 快速排序的时间复杂度不是稳定的 O ( n l o g 2 n ) O(nlog_2n) O(nlog2?n),最坏情况会变成 O ( n 2 ) O(n^2) O(n2),怎么解决复杂度恶化问题?
    快速排序不是稳定的O(log2n),最坏的情况会变成O(n^2),可以通过 改插入排序 或者 三数取中 的方法改善情况恶化

  3. 递归过深会引发什么问题?
    递归太深可能导致函数调用开销过多,甚至栈溢出,程序崩溃

  4. 怎么控制递归深度?如果达到递归深度了还没排完序怎么办?
    快速排序复杂度恶化时,递归会很深,STL的sort中有一个变量_Ideal,每递归深入一次,_Ideal就会减小,当_Ideal为0时,就会调用稳定 O ( n l o g 2 n ) O(nlog_2n) O(nlog2?n)的非递归堆排序。此外还会判断当前区间的排序元素个数,少于32时,会转入插入排序,这也能防止递归次数过多导致复杂度恶化

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-19 12:02:44  更:2022-05-19 12:03:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/30 1:38:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码