跟我学c++中级篇——STL算法之排序

Posted 太平洋工作室

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跟我学c++中级篇——STL算法之排序相关的知识,希望对你有一定的参考价值。

一、排序算法

数据结构和算法里,或者说在计算机的算法里,排序算法一定是无法绕过的一个算法,而且排序算法种类很多,和树、堆等数据结构都多多少少的互相纠缠。常见的排序算法按不同的特点可以分为比较类型排序和非比较类型排序,内部排序和外部排序等,包括以下几种:
冒泡排序算法、选择排序算法、插入排序算法、快速排序算法、希尔排序算法、堆排序算法、归并排序算法。其它如基数排序算法、桶排序算法和计数排序算法,不是很常见。
其主要的特征如下:

order

排序是基本的算法,在各种语言和库中都或多或少的有封装,在STL中当然也有对排序的封装,一般常用的是选择排序和快速排序以及堆排序比较多。在STL也可以通过提供自定义的排序算法进行排序处理。

二、STL库中的排序

STL的排序整体是以快速为主(分段递归),但当分段排序时达到某个域值,就会使用插入排序,当然递归层次过深,则采用堆排序,防止出现最坏的情况。换句话说,在STL库的排序中,是根据情况动态的使用三种排序方法来达到最优的排序速度的。STL中的比较主要分为有以下几种:
1、整体的比较,如sort()函数。
2、区间比较,如partial_sort()函数。
3、堆排序函数,sort_heap()函数。
在c++20中还有一个std::ranges::sort()函数,这个看起来更好用更方便一些。
看一下相关的源码:

template<class _RanIt,
    class _Prinline
    void sort(const _RanIt _Firstconst _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,
    class _Prinline
    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 _BidIt,
    class _Prinline
    _BidIt _Insertion_sort_unchecked(_BidIt _Firstconst _BidIt _Last, _Pr _Pred)
    {
   // insertion sort [_First, _Last), using _Pred
    if (_First != _Last)
        {
        for (_BidIt _Next = _First; ++_Next != _Last; )
            {   // order next element
            _BidIt _Next1 = _Next;
            _Iter_value_t<_BidIt> _Val = _STD move(*_Next);

            if (_DEBUG_LT_PRED(_Pred, _Val, *_First))
                {   // found new earliest element, move to front
                _Move_backward_unchecked(_First, _Next, ++_Next1);
                *_First = _STD move(_Val);
                }
            else
                {   // look for insertion point after first
                for (_BidIt _First1 = _Next1;
                    _DEBUG_LT_PRED(_Pred, _Val, *--_First1);
                    _Next1 = _First1)
                    {
                    *_Next1 = _STD move(*_First1);  // move hole down
                    }

                *_Next1 = _STD move(_Val);  // insert element in hole
                }
            }
        }

    return (_Last);
    }

可以很明显的在代码中看到判断来使用插入和堆排序算法。其它的几个排序基本原理是相似的,不再阐述分析。

三、例程

应用例程就很简单了(cppreference.com例程):

#include <algorithm>
#include <functional>
#include <array>
#include <iostream>
#include <string_view>

int main()
{
    std::array<int, 10> s = {5742861903};

    auto print = [&s](std::string_view const rem) {
        for (auto a : s) {
            std::cout << a << ' ';
        }
        std::cout << ": " << rem << '\n';
    };

    std::sort(s.begin(), s.end());
    print("sorted with the default operator<");

    std::sort(s.begin(), s.end(), std::greater<int>());
    print("sorted with the standard library compare function object");

    struct {
        bool operator()(int a, int b) const return a < b; }
    } customLess;
    std::sort(s.begin(), s.end(), customLess);
    print("sorted with a custom function object");

    std::sort(s.begin(), s.end(), [](int a, int b) {
        return a > b;
    });
    print("sorted with a lambda expression");
}

运行结果如下:

0 1 2 3 4 5 6 7 8 9 : sorted with the default operator<
9 8 7 6 5 4 3 2 1 0 : sorted with the standard library compare function object
0 1 2 3 4 5 6 7 8 9 : sorted with a custom function object
9 8 7 6 5 4 3 2 1 0 : sorted with a lambda expression

partial_sort是对指定的位置和范围的部分数据进行排序,如果这个函数能满足你的需求,就尽量不要使用全排序。堆排序sort_heap()和书本上的没啥本质不同,同样看代码:

#include <algorithm>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> v = {314159}; 

    std::make_heap(v.begin(), v.end());

    std::cout << "heap:\t";
    for (const auto &i : v) {
        std::cout << i << ' ';
    }   

    std::sort_heap(v.begin(), v.end());

    std::cout << "\nsorted:\t";
    for (const auto &i : v) {                                                   
        std::cout << i << ' ';
    }   
    std::cout << '\n';
}

执行结果是:

heap:   9 4 5 1 1 3 
sorted: 1 1 3 4 5 9

库存在的目的就是减少使用的麻烦,不过减少使用的麻烦不代表没坑,如果排序数据中有重复的数据结果会是如何呢?同学们可以自己试试,再结合网上的说明资料就明白原因了。

四、总结

知其然,然后知其所然,是知也。很多人编程很多年,也不知道上学时学过的数据结构和算法用处在哪儿。当你透过应用的层层封装的迷雾之后,就会发现,原来自己只是一只“勤劳”的工蜂,不断的用别人提供的工具在干着一些简单而又重复的劳动,为什么不自己去做一些有意义的事情呢?适当的重复的搞一两个轮子,未必不是好事。


以上是关于跟我学c++中级篇——STL算法之排序的主要内容,如果未能解决你的问题,请参考以下文章

各大算法专题-STL篇

浅谈 C++之 STL

C++ STL中排序算法的工作

acm的STL应用之vector篇

STL源代码剖析——STL算法之set集合算法

FCC上的javascript算法题之中级篇