STL源码剖析:STL算法

news2024/11/20 23:14:11
STL 算法总览

质变算法 mutating algorithms—会改变操作对象之值

所有的 STL算法都作用在由迭代器(first,last)所标示出来的区间上。所谓“质变算法”,是指运算过程中会更改区间内(迭代器所指)的元素内容。诸如拷贝(copy)、互换(swap)、替换(replace)、填写(fill)、删除(remove)、排列组合(permutation)、分割(partition)、随机重排(random shuffling)、排序(sort)等算法,都属此类。

int ia[] ={ 22,30,30,17,33,40,17,23,22,12,20 };
vector<int> iv(ia, ia+sizeof(ia)/sizeof(int));
vector<int>::const_iterator cite1 = iv.begin();
vector<int>::const_iterator cite2 = iv.end();
sort(cite1,cite2);

对常迭代器指向的元素进行sort 操作,编译器会报错。

非质变算法(不改变操作对象之值)

所有的STL算法都作用在由迭代器[first,last)所标示出来的区间上。非质变算法”,是指运算过程中不会更改迭代器所指的元素内容。查找(find),匹配(search),计数(count),巡访(for_each),比较,寻找极值(max, min)等算法。

STL 算法的一般形式

所有泛型算法的前两个参数都是一对迭代器(iterators),通常称为 first 和last,用以标示算法的操作区间。STL 习惯采用前闭后开区间表示法。当 first==last时,上述所表现的便是一个空区间。

这个[first,last)区间的必要条件是,必须能够经由累加操作符的反复运用从 first到达 last。编译器本身无法强求这一点。如果这个条件不成立,会导致未可预期的结果。

质变算法(mutating algorithms,6.1.3节)通常提供两个版本:一个是 in-place (就地进行)版,就地改变其操作对象;另一个是 copy(另地进行)版,将操作 对象的内容复制一份副本,然后在副本上进行修改并返回该副本。

不是所有质变算法都有 copy 版,例如 sort()就没有。如果我们希望以这类“无 copy 版本”之质变算法施行于某一段区间元素的副本身上,我们必须自行制作并传递那一份副本。

算法的泛化过程

如何将算法独立于其所处理的数据结构之外,不受数据结构的羁绊,思想层面就不是那么简单了。如何设计一个算法,适用于大多数数据结构呢?只要把操作对象的型别加以抽象化,把操作对象的标示法和区间目标的移动行为抽象化,整个算法也就在一个抽象层面上工作了,整个过程称为算法的泛型化,简称泛化。如写一个find()函数,在 array 中寻找特定值。

int* find(int* arrayHead,int arraySize, int value){
  for(int i=0;i<arraySize; ++i)
     if(arrayHead[i]==value)break;
  return &(arrayHead[i]);
}

"最后元素的下一位置"称为 end。返回end以表示"查找无结果"似乎是个可笑的做法。为什么不返回 null? 因为,一如稍后即将见到的,end 指针可以对其他种类的容器带来泛型效果,这是 null 所无法达到的。

int* find(int* begin,int* end,int value){
  while(begin != end && *begin != value) ++begin;
   return begin;
}

这个函数在“前闭后开”区间[begin, end)内(end 指向 array最后元素的下一位置)查找 value,并返回一个指针,指向它所找到的第一个符合条件的元素;如果没有找到,就返回end。

template<typename T>//关键词 typename 也可改为关键词 class
T* find(T* begin,T* end, const T& .value){
  //以下用到了 operator!=, operator*, operator++
  while(begin != end && *begin != value)++begin;
  return begin;
}

传统的find函数可能设计为接受原生指针作为参数,用于在数组或其他线性结构中寻找特定元素。然而,这限制了find只能用于那些可以直接用指针遍历的数据结构。
如果我们想要让find函数能够处理更复杂的数据结构,比如链表(链表是自定义的类,不能使用指针递增递减),原生指针不能简单地通过递增(++)来指向下一个元素。

STL引入了迭代器,迭代器是一种行为类似于指针的对象,但它提供了丰富的接口来适应不同的数据结构。在链表中一个迭代器可以设计成当执行递增操作,会移动到链表中的下一个节点。迭代器比喻为"智能指针",迭代器不仅模仿了指针的基本功能(如解引用和递增/递减),还加入了额外的功能。使用迭代器find函数就可以被设计为接受任何类型的迭代器,从而使得这个函数可以用来搜索不同种类的容器,包括但不限于数组,向量(vector)、列表(list)、集合(set)等。

数值算法
accumulate

主要功能是将一个范围内的所有元素累加起来,并返回累加的结果。

template <class InputIterator, class T>
T accumulate(InputIterator first, InputIterator last, T init);
template <class InputIterator, class T, class BinaryOperation>
T accumulate(InputIterator first, InputIterator last, T init, BinaryOperation binary_op);
adjacent_difference
template <class InputIterator, class OutputIterator>
OutputIterator adjacent_difference(InputIterator first, InputIterator last, OutputIterator result);
template <class InputIterator, class OutputIterator, class BinaryOperation>
OutputIterator adjacent_difference(InputIterator first, InputIterator last, OutputIterator result, BinaryOperation binary_op);

binary_op 是一个二元操作符,它可以是你自定义的函数对象或lambda表达式,用来替代默认的减法操作。

inner_product
template <class InputIterator1, class InputIterator2, class T>
T inner_product(InputIterator1 first1, InputIterator1 last1,
                InputIterator2 first2, T init);
template <class InputIterator1, class InputIterator2, class T,
          class BinaryOperation1, class BinaryOperation2>
T inner_product(InputIterator1 first1, InputIterator1 last1,
                InputIterator2 first2, T init,
                BinaryOperation1 binary_op1, BinaryOperation2 binary_op2);

主要功能是计算两个序列的内积(点积)。内积通常用于向量运算,它是两个向量对应元素相乘后的和。

std::vector<int>vec1={1, 2, 3};
std::vector<int>vec2={4, 5, 6};
//使用默认的乘法和加法操作
int product_sum = std::inner_product(vec1.begin(), vec1.end(), vec2.begin(), 0); // 结果是 1*4 + 2*5 + 3*6 = 32
partial_sum

对于输入序列 [a0, a1, a2, ..., an],partial_sum 会生成一个新的序列 [a0, a0 + a1, a0 + a1 + a2, ..., a0 + a1 + ... + an]。

template <class InputIterator,class OutputIterator>
OutputIterator partial_sum(InputIterator first, 
 InputIterator last, OutputIterator result);

template <class InputIterator,class OutputIterator,class BinaryOperation>
OutputIterator partial_sum(InputIterator first, 
InputIterator last, OutputIterator result,BinaryOperation binary_op);
fill

将[first,last)内的所有元素改填新值。

template <class ForwardIterator, class T>
void fill(ForwardIterator first,ForwardIterator last, const T& value) //迭代走过整个区间 
for(;first!=last; ++first) //设定新值 
   *first=value;
}
fill_n

将[ first,last)内的前n个元素改填新值,返回的迭代器指向被填人的最后一个元素的下一位置。

template <class OutputIterator, class Size, class T>
OutputIterator fil1_n(OutputIterator first, Size n, const T& value){ 
//经过n个元素,设定新值 
for(; n>0; --n,++first) *first = value;
  return first;
}
lexicographical_compare

以“字典排列方式”对两个序列[first1,last1)和[first2,last2)进行比较。

max

取两个对象中的较大值。有两个版本,版本一使用对象型别T所提供的greater-than操作符来判断大小,版本二使用仿函数 comp来判断大小。

template <class T>
inline const T& max(const T& a, const T& b)
return a < b ? b:a;
}
template <class T,class Compare>
inline const T& max(const T& a, const T& b, Compare comp)(
// 由 comp 决定“大小比较”标准 
return comp(a,b)? b:a;
}
min

取两个对象中的较小值。有两个版本,版本一使用对象型别 T 所提供的less-than 操作符来判断大小,版本二使用仿函数 comp来判断大小。

template <class T>
inline const T& min(const TE a, const T& b)
return b< a ? b:a;
}
template <class T,class Compare>
inline const T& min(const T& a,const. T& b,Compare comp){
  return comp(b,a)? b:a; // 由 comp 决定“大小比较”标准
}
mismatch

用来平行比较两个序列,指出两者之间的第一个不匹配点,返回一对迭代器,分别指向两序列中的不匹配点。

swap

该函用交换(对调)两的 容。

template <class T>
inline void swap(T& a, T& b){
   T tmp=a; a=b; b=tmp;
}
copy

阅读资料:STL源码剖析——copy函数-CSDN博客(写的比较详细附有源码)

copy函数原型

template<class InputIterator, class OutputIterator>  
inline OutputIterator copy(  
      InputIterator first,   
      InputIterator last,   
      OutputIterator result  
);
//first last拷贝到result的位置

copy算法第一个参数和第二个迭代器是输入迭代器,第三个迭代器是输出迭代器。

copy函数针对不同类型的迭代器采取不同的策略。

对于字符串(const char*const wchar_t*),使用全特化版本直接通过 memmove 复制。

一般的迭代器,使用泛化版本并通过__copy_dispatch结构体根据迭代器类型(类型萃取)选择到合适的实现,这使用函数重载实现。

对于随机访问迭代器(由 std::random_access_iterator_tag 表示),可以利用迭代器的随机访问特性进行优化。在 __copy 的随机访问迭代器版本中,使用 last - first 计算距离,并使用循环来减少迭代器的操作次数,从而提高效率。

(在copy的时候随机迭代器可以计算copy的长度,所以for循环的长度确定,可以使用循环展开进行优化)

阅读材料:CMake中开启编译器代码优化加速_cmakelist 设置编译加速-CSDN博客

对于其他迭代器类别(如输入迭代器、前向迭代器、双向迭代器), 使用通用的 __copy 实现。这些迭代器类别不支持随机访问,因此每次循环都需要显式地递增迭代器。

对于指针迭代器,__copy_dispatch 会检查指针指向的类型是否支持 trivial assignment,如果是,则使用 memmove;否则调用非 trivial 的复制逻辑,逐个元素进行拷贝。

这里涉及到模板全特化和偏特化:
阅读资料: https://harttle.land/2015/10/03/cpp-template.html(这个博客和博主非常强,可以向他学习。以后考虑做一个自己的博客,并多阅读,多参与开源项目扩大自己的影响力)
set相关算法

STL 一共提供了四种与 set(集合)相关的算法,分别是并集(union)、交集(intersection)、差集(difference)、对称差集(symmetric difference)。

heap 算法

make_heap(),pop_heap(),push_heap(),sort_heap()

数据处理相关
adjacent_find

查找范围内相邻且相等的元素对,返回指向第一个这样的元素的迭代器。

template <class ForwardIterator> ForwardIterator 
  adjacent_find(ForwardIterator first, ForwardIterator last);
count

计算范围内等于给定值的元素个数

 template <class InputIterator, class T> typename iterator_traits<InputIterator>::difference_type 
count(InputIterator first, InputIterator last, const T& value);
count_if

计算范围内满足特定条件的元素个数

template <class InputIterator, class Predicate> typename iterator_traits<InputIterator>::difference_type 
count_if(InputIterator first, InputIterator last, Predicate pred);
find

查找范围内第一个等于给定值的元素

template <class InputIterator, class T> InputIterator 
find(InputIterator first, InputIterator last, const T& value);
find_if

查找范围内第一个满足特定条件的元素

template <class InputIterator, class Predicate> InputIterator 
find_if(InputIterator first, InputIterator last, Predicate pred);
find_end

查找一个范围在另一个范围内的最后一次出现位置

template <class ForwardIterator1, class ForwardIterator2> ForwardIterator1 
find_end(ForwardIterator1 first1, ForwardIterator1 last1, 
ForwardIterator2 first2, ForwardIterator2 last2);
find_first_of

查找一个范围在另一个范围内的第一次出现位置。

template <class ForwardIterator1, class ForwardIterator2> ForwardIterator1 
find_first_of(ForwardIterator1 first1, ForwardIterator1 last1, 
              ForwardIterator2 first2, ForwardIterator2 last2);
for_each

对范围内的每个元素应用一个函数或仿函数。

template <class InputIterator, class Function> Function 
for_each(InputIterator first, InputIterator last, Function f);
generate

使用生成器填充范围内的元素

template <class ForwardIterator, class Generator> void 
generate(ForwardIterator first, ForwardIterator last, Generator gen);
generate_n

使用生成器为指定数量的元素赋值

template <class OutputIterator, class Size, class Generator> 
OutputIterator 
generate_n(OutputIterator first,Size n,Generator gen);
remove

移除所有等于给定值的元素,但不调整容器大小。

template <class ForwardIterator, class T> ForwardIterator 
remove(ForwardIterator first, ForwardIterator last, const T& value);
remove_copy

将不等于给定值的元素复制到另一区间,并移除原区间中等于该值的元素。

template <class InputIterator, class OutputIterator, class T> OutputIterator 
remove_copy(InputIterator first, InputIterator last, OutputIterator result, 
const T& value);
remove_if

根据条件移除元素,但不调整容器大小

template <class ForwardIterator, class Predicate>ForwardIterator 
remove_if(ForwardIterator first, ForwardIterator last, Predicate pred);
remove_copy_if

根据条件将元素复制到另一区间,并移除原区间中满足条件的元素。

template<class InputIterator, class OutputIterator, class Predicate> 
OutputIterator 
remove_copy_if(InputIterator first, InputIterator last, 
OutputIterator result, Predicate pred);
replace

将范围内所有等于旧值的元素替换为新值。

template <class ForwardIterator, class T1, class T2> void 
replace(ForwardIterator first, ForwardIterator last, 
const T1& old_value, const T2& new_value);
replace_copy

将范围内所有等于旧值的元素替换为新值,并复制到另一区间。

template <class InputIterator, class OutputIterator, class T1, class T2> 
OutputIterator replace_copy(InputIterator first, InputIterator last, 
OutputIterator result, const T1& old_value, const T2& new_value);
replace_if

根据条件将元素替换为新值。

template <class ForwardIterator, class Predicate, class T> void 
replace_if(ForwardIterator first, ForwardIterator last, 
Predicate pred, const T& new_value);
replace_copy_if

根据条件将元素替换为新值,并复制到另一区间

template <class InputIterator, class OutputIterator, class Predicate, class T> 
OutputIterator replace_copy_if(InputIterator first, InputIterator last, 
OutputIterator result, Predicate pred,const T& new_value);
reverse

反转范围内的元素顺序

template <class BidirectionalIterator> void 
reverse(BidirectionalIterator first, BidirectionalIterator last);
reverse_copy

反转并复制范围内的元素到另一区间。

template <class BidirectionalIterator, class OutputIterator> OutputIterator 
reverse_copy(BidirectionalIterator first, 
BidirectionalIterator last, OutputIterator result);
rotate

循环移动范围内的元素

template <class ForwardIterator> void 
rotate(ForwardIterator first,ForwardIterator middle,ForwardIterator last);
rotate_copy

循环移动并复制范围内的元素到另一区间。

template <class ForwardIterator, class OutputIterator> OutputIterator rotate_copy(ForwardIterator first, ForwardIterator middle, ForwardIterator last, OutputIterator result);
search

查找一个范围在另一个范围内的第一次出现位置

template <class ForwardIterator1, class ForwardIterator2> 
ForwardIterator1 
search(ForwardIterator1 first1,  ForwardIterator1 last1, 
ForwardIterator2 first2, ForwardIterator2 last2);
search_n

查找连续出现给定次数的值。

template <class ForwardIterator, class Size, class T> 
ForwardIterator 
search_n(ForwardIterator first, ForwardIterator last, 
Size count, const T& value);
swap_ranges

交换两个相同长度区间的元素。

template <class ForwardIterator1, class ForwardIterator2> 
ForwardIterator2 
swap_ranges(ForwardIterator1 first1,ForwardIterator1 last1, 
ForwardIterator2 first2);
transform

对范围内的每个元素应用一元操作。

template <class InputIterator, class OutputIterator, class UnaryOperation>
OutputIterator 
transform(InputIterator first1,InputIterator last1,
OutputIterator result,UnaryOperation op);

对两个范围内的元素应用二元操作,并将结果存储在输出迭代器所指向的位置。

template <class InputIterator1, class InputIterator2, class OutputIterator, 
class BinaryOperation> 
OutputIterator 
transform(InputIterator1 first1, InputIterator1 last1, 
InputIterator2 first2, OutputIterator result, 
BinaryOperation binary_op);
max_element

找出范围内最大元素的位置

template <class ForwardIterator> ForwardIterator 
max_element(ForwardIterator first, ForwardIterator last);
min_element

找出范围内最小元素的位置

template <class ForwardIterator> ForwardIterator 
min_element(ForwardIterator first, ForwardIterator last);
includes

检查一个已排序的序列是否包含另一个已排序序列的所有元素。

template <class InputIterator1, class InputIterator2> bool 
includes(InputIterator1 first1,InputIterator1 last1, 
InputIterator2 first2,InputIterator2 last2);
merge

合并两个已排序的序列到一个有序序列

template <class InputIterator1, class InputIterator2, class OutputIterator> 
OutputIterator 
merge(InputIterator1 first1, InputIterator1 last1, 
InputIterator2 first2,InputIterator2 last2,OutputIterator result);
partition

重新排列范围内的元素,使得满足条件的元素位于不满足条件的元素之前。

template <class BidirectionalIterator, class Predicate> 
BidirectionalIterator partition
(BidirectionalIterator first, BidirectionalIterator last,
Predicate pred);
unique

移除相邻重复元素,但不调整容器大小

template <class ForwardIterator> ForwardIterator 
unique(ForwardIterator first, ForwardIterator last);
unique_copy

移除相邻重复元素,并将唯一元素复制到另一区间。

template<class InputIterator,class OutputIterator>OutputIterator 
unique_copy(InputIterator first,InputIterator last,
OutputIterator result);
lower_bound和upper_bound

lower_bound 返回的是“不小于”给定值的第一个位置。

upper_bound 返回的是“大于”给定值的第一个位置。

例如,在一个包含 {1, 2, 4, 4, 6, 7} 的有序数组中,对于值 4:

lower_bound 将返回指向第一个 4 的迭代器。

upper_bound 将返回指向第一个大于 4 的元素(即 6)的迭代器。

如果你想要找到等于给定值的所有元素的范围,你可以结合使用 lower_bound 和 upper_bound 来获取这个范围。比如上面的例子中,[lower_bound, upper_bound) 将给出所有 4 的范围。

random_shuffle

将(first,last)的元素次序随机重排

partial_sort/partial_sort_copy
  • 首先,partial_sort使用make_heap()将区间 [first, middle) 构造成一个最大堆(max-heap)。
  • 筛选较小元素:然后,它遍历 [middle, last) 中的每一个元素,将其与堆顶元素进行比较。如果新元素小于堆顶元素,则替换堆顶元素,并重新调整堆的结构以保持最大堆的性质。
  • 堆排序:遍历结束后,[first, middle) 中已经包含了最小的 N 个元素,但顺序并未完全排序,因此最后调用 sort_heap() 对该区间进行排序,使其按升序排列。

[first, last)partial_sort 会将这个序列中的前 N 个最小元素放入 [first, middle) 中,其中 middle 是一个指向序列中间位置的迭代器,通常定义为 first + N。排序后,[first, middle) 中的元素是按升序排列的最小元素,而 [middle, last) 中的元素则不保证有序。

std::vector<int> data = {7, 3, 5, 8, 1, 9, 2, 6, 4, 0};
int N = 5; 
//希望找到并排序前5个最小的元素
//使用 partial_sort 将前 N 个最小的元素排好序    
std::partial_sort(data.begin(), data.begin() + N, data.end());
sort

快速排序(Quick Sort)

  • sort() 的主要部分是基于快速排序的。快速排序是一种分治的排序算法,通过选择一个基准元素,然后将数据分区使得所有小于基准的元素放在它的左边,大于基准的元素放在它的右边。对于每一个分区,它递归地进行排序,直到所有元素都被排序完成。

插入排序(Insertion Sort)

  • 当递归排序的分区规模较小(通常在 STL 的实现中,这个阈值为 16 或更小)时,为了避免递归调用的开销,sort() 会切换到插入排序。
  • 插入排序在小规模数据上性能优异,因为它的比较和交换操作相对较少,特别是在数据接近有序的情况下。

堆排序(Heap Sort)

  • 如果在执行快速排序时递归深度过大,导致效率下降(例如,数据接近有序的情况可能导致快速排序的最坏情况发生),sort() 会自动切换到堆排序。
  • 堆排序是一种时间复杂度为 O(n log n) 的算法,可以有效避免快速排序在最坏情况下的退化问题。它使用堆结构来排序数据,确保算法在最坏情况下仍然能保持较好的性能。

为什么要求 RandomAccessIterator

STL sort() 算法要求容器提供 RandomAccessIterator(随机访问迭代器),原因如下:

  • 快速排序和堆排序都需要频繁访问数据,并且会对数据进行大量的索引操作。随机访问迭代器能够在常数时间内访问任意位置的元素。
  • 如果迭代器不支持随机访问(如双向迭代器或前向迭代器),则每次访问元素时都可能需要线性时间,这会极大降低排序的效率。

不适用 sort() 的情况

  • 关联容器(Associative Containers)
    • set, map 这类关联容器,底层是红黑树(RB-tree)实现,元素在插入时就会被自动排序,因此不需要使用 sort() 算法。
  • 序列容器
    • 对于 vectordeque,它们支持随机访问迭代器,因此可以使用 sort() 算法。
    • 对于 listslist,它们的迭代器分别是双向迭代器(BidirectionalIterator)和单向迭代器(ForwardIterator),不支持随机访问。对于这种情况,应使用它们的成员函数 sort(),这些函数针对容器的特点进行了优化。

SGI STL 的sort()算法实现是一个混合排序算法,主要基于快速排序,并结合了堆排序和插入排序。这种排序算法被称 Introsort,它根据不同的情况自动选择最合适的排序算法,从而在性能和稳定性之间取得平衡。

下面是对这个sort()实现细节的解析:

1. sort()函数入口

template <class RandomAccessIterator>
inline void sort(RandomAccessIterator first, RandomAccessIterator last) {
   if(first!=last){
       introsort_loop(first, last, value_type(first), lg(last - first) * 2);
       final_insertion_sort(first, last);
    }
}

sort() 通过调用 introsort_loop() 函数执行主要的排序逻辑。

lg(last - first) * 2计算出最大允许的递归层数。lg()计算的是二进制对数的值(即log2),并乘以 2,这是控制分割深度的手段,以防止快速排序出现性能退化的情况。

final_insertion_sort()用于在排序完成后对较小规模的元素进行最终的插入排序。

2. lg()函数

template <class Size>
inline Size __lg(Size n) {
    Size k;
    for (k = 0; n > 1; n >>= 1) ++k;
    return k;
}

lg()函数计算一个整数n 的二进制对数。它返回 n 的对数 k,其中2^k ≤ n。

这是为了限制快速排序的最大递归深度,避免出现最坏情况的 O(n²) 时间复杂度。

3. introsort_loop()函数

template <class RandomAccessIterator, class T, class Size>
void introsort_loop(RandomAccessIterator first, RandomAccessIterator last, T*, Size depth_limit) {
    const int stl_threshold = 16;  // 定义阈值常量,16 个元素以下使用插入排序
    while (last - first > stl_threshold) {  // 如果元素个数大于阈值,继续快速排序
        if (depth_limit == 0) {  // 分割深度用尽时,改用堆排序
            partial_sort(first, last, last);  // 堆排序处理剩下的元素
            return;
        }
        --depth_limit;  // 递归深度减一
        // median-of-3 partition:选择三个元素的中位数作为基准
        RandomAccessIterator cut = unguarded_partition(
            first, last, T(median(*first, *(first + (last - first) / 2), *(last - 1)))
        );
        // 对右半部分递归排序
        introsort_loop(cut, last, value_type(first), depth_limit);
        // 现在回到 while 循环,准备对左半部分递归排序
        last = cut;
    }
}

快速排序阶段

当前区间的元素个数大于一个预设阈值(stl_threshold=16),它会继续进行快速排序。使用 median-of-3 partition(三数取中法)来选择枢轴元素,以避免出现最坏情况(即所有元素几乎相同或完全有序)。median-of-3选择first、middle和 last - 1这三个元素的中位数作为枢轴元素,能较好地避免性能退化。

递归深度限制

depth_limit用于限制快速排序的递归深度。它的初始值是 lg(n) * 2,每次递归时递减。如果递归深度达到 depth_limit == 0,快速排序的递归过深,算法会改用堆排序(Heap Sort)来避免快速排序最坏情况下的性能退化。

final_insertion_sort() 函数
template <class RandomAccessIterator>
void final_insertion_sort(RandomAccessIterator first, RandomAccessIterator last) {
    if (last - first > stl_threshold) {
        insertion_sort(first, first + stl_threshold);
        unguarded_insertion_sort(first + stl_threshold, last);
    } else {
        insertion_sort(first, last);
    }
}

final_insertion_sort()在快速排序阶段完成后用于处理剩下的较小区间(通常小于16 个元素)。当区间较小时,插入排序的效率较高。

Introsort算法:SGI STL 的 sort() 实现采用的是 Introsort 算法,它在处理大规模数据时结合了快速排序的高效性、堆排序的稳定性以及插入排序在小规模数据时的优势。

递归深度控制:通过depth_limit限制快速排序的递归深度,防止性能退化为 O(n²),一旦递归过深,会自动切换到堆排序来保证 O(n log n) 的最坏情况性能。

混合排序:它根据数据的规模和当前的排序状态选择最合适的排序策略,既能保证效率,又能避免最坏情况的出现。

这种实现方式使得STL sort()能够高效处理不同规模和特性的输入数据,并提供稳定的O(nlogn)性能。inplace_merge 的作用是将两个相邻的、有序的序列合并成一个有序的序列。例如,假设有两个有序序列 [1, 3, 5] 和 [2, 4, 6],它们排列在一起形成 [1, 3, 5, 2, 4, 6]。inplace_merge 可以将它们合并成 [1, 2, 3, 4, 5, 6]。

inplace_merge(应用于有序区间)

inplace_merge 使用了一个辅助缓冲区来提高效率。当缓冲区不足时,它会采用递归分治的策略。

初始化和检查

算法首先会检查两个序列是否为空,如果其中任何一个为空,则不需要任何操作。

if(first==middle||middle==last)return;

这一步判断两个区间是否为空,若是,则直接返回,无需合并。inplace_merge 调用辅助函数 inplace_merge_aux 来处理合并过程:

inplace_merge_aux(first, middle, last, value_type(first), distance_type(first));

辅助函数根据序列长度和缓冲区的大小,选择不同的合并策略。

inplace_merge_aux中,如果缓冲区的大小足够容纳其中一个序列,它会将其中一个序列拷贝到缓冲区中,然后用简单的合并算法完成整个合并过程:

if (len1 <= len2 && len1 <= buffer_size) {
    Pointer end_buffer = copy(first, middle, buffer);
    merge(buffer, end_buffer, middle, last, first);
}else if(len2 <= buffer_size) {
    Pointer end_buffer = copy(middle, last, buffer);
    merge_backward(first, middle, buffer, end_buffer, last);
}
  • Case 1: 如果缓冲区足够容纳序列一,则将序列一复制到缓冲区中,之后将缓冲区中的数据与序列二进行合并。
  • Case 2: 如果缓冲区足够容纳序列二,则将序列二复制到缓冲区中,之后将序列一与缓冲区中的数据合并。

当缓冲区不足以容纳任何一个序列时,inplace_merge 采用递归分治的策略。它将较长的序列一分为两半,然后递归调用自己来继续处理。

BidirectionalIterator first_cut = first;
BidirectionalIterator second_cut = middle;
Distance len11 = 0;
Distance len22 = 0;
// 序列二较长if (len2 > len1) {
    len22 = len2 / 2;
    advance(second_cut, len22);
    first_cut = upper_bound(first, middle, *second_cut);
    distance(first, first_cut, len11);
}

算法根据较长的序列找到分割点,然后对分割后的两部分递归地调用 merge_adaptive

在找到分割点后,算法会调用 rotate_adaptive 将两个部分旋转合并。这使得下一个递归调用可以在原地继续合并操作。

BidirectionalIterator new_middle = rotate_adaptive(first_cut, middle, second_cut, len1 - len11, len22, buffer, buffer_size);

rotate_adaptiverotate 的一种优化版本,它会尝试使用缓冲区,如果缓冲区不够大,则直接调用 rotate

( 这个地方看不懂 )

nth_elem ent

nth_element是找到一个第 n 大元素,并且重新排列序列,使得这个元素在正确的位置上,同时满足以下条件:

  • 左侧子区间:所有元素都小于或等于这个“第 n 大”元素。
  • 右侧子区间:所有元素都大于或等于这个“第 n 大”元素。
  • 次序无关:它不会保证两个子区间内部的元素顺序,这与 sortpartial_sort 不同。

这使得 nth_element 更像是一个改进版的 partition,而不是 sort

假设有一个序列:{22, 30, 30, 17, 33, 40, 17, 23, 22, 12, 20}

执行以下操作:

nth_element(iv.begin(), iv.begin() + 5, iv.end());

目标是将序列重新排列,使得第 5 个位置上的元素(即索引 5)是这个序列排序后应该在的位置。算法执行完成后,结果可能是:

{20, 12, 22, 17, 17, 22, 23, 30, 30, 33, 40}

iv[5] 上的元素是 22,并且它与完全排序后的序列中同一位置上的元素值相同。然而,左侧和右侧部分的元素顺序并没有完全排序,只保证满足“左侧所有元素小于等于 22,右侧所有元素大于等于 22”。

nth_element 的实现思路借鉴了快速排序中的分区算法(partitioning),采用了median-of-3 partitioning 技术。具体来说:

  1. Median-of-3 partitioning:算法从序列的开头、中间和结尾选取三个元素,计算它们的中值(即三者中居中的那个值),并将该中值作为“枢轴(pivot)”。
  2. 分割序列:以枢轴为中心,将序列分为左(小于枢轴)和右(大于枢轴)两部分。
  3. 递归分治:判断 nth 元素在左侧还是右侧,并递归地对相关部分继续执行相同操作,直到序列长度小于或等于 3。
  4. 插入排序:当序列长度减小到 3 或以下时,使用插入排序(insertion sort)完成排序,因为对于小规模的数据,插入排序更加高效。这种方法的效率高于完全排序,因为它只对涉及的部分进行重新排列。
merge sort

可以使用inplace_merge来实现merge sort

template <class BidirectionalIter>
void mergesort(BidirectionalIter first, BidirectionalIter last) {
    // 计算序列长度
    typename iterator_traits<BidirectionalIter>::difference_type n = distance(first, last);
    // 如果序列长度为 0 或 1,则序列已经有序,无需进一步操作
    if (n <= 1)
        return;
    // 找到序列的中点,将序列对半分割
    BidirectionalIter mid = first + n / 2
    // 对左半部分递归进行归并排序
    mergesort(first, mid);
    // 对右半部分递归进行归并排序
    mergesort(mid, last);
    // 将两个已经排序的子序列合并成一个有序序列
    inplace_merge(first, mid, last);
}

归并排序和快速排序的时间复杂度都是 O(Nlog⁡N),但是它们的性能在实际应用中会有所不同:

  • 空间复杂度:归并排序通常需要额外的内存空间来存储合并过程中的中间结果,而快速排序可以在原地进行,不需要额外的存储。这使得快速排序在内存管理上更有优势。
  • 数据移动成本:由于归并排序需要将元素复制到临时缓冲区中,这会增加数据移动的成本。而快速排序由于在原地进行,因此数据移动较少。

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

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

相关文章

吐槽kotlin之垃圾设计

本文重点在于吐槽垃圾设计&#xff0c;基本直只说缺点。 一.没有static关键字 static其实不是很面向对象&#xff0c;但是是很有必要和方便的。 kotlin为了实现java的static功能&#xff0c;必须使用伴生类&#xff0c;一般情况下没啥问题&#xff0c;但是反编译之后的class多…

数据结构与算法——Java实现 36.求数据流中位数

就借着月光&#xff0c;再与你对望 —— 24.10.14 295. 数据流的中位数 中位数是有序整数列表中的中间值。如果列表的大小是偶数&#xff0c;则没有中间值&#xff0c;中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。例如 arr [2,3] 的中位数是 (2 3) / 2 …

Java jconsole.exe 调试工具

寻找JDK所在路径&#xff1a; 打开 jconsole.exe 启动之前确保idea程序已经运行了有的同学需要用管理员方式运行&#xff0c;即 右键 jconsole.exe 选择用管理员方式运行 上面的折线图&#xff0c;表示随着时间有哪些线程 下面的左边的线程中&#xff1a;Thread tnew MyThre…

【Java数据结构】链表面试题

【Java数据结构】链表面试题 一、移除链表元素二、反转链表三、链表的中间结点四、删除链表的倒数第 N 个结点五、合并两个有序链表六、链表分割七、链表的回文结构八、相交链表九、环形链表 此篇博客希望对你有所帮助&#xff08;帮助你更加了解链表&#xff09;&#xff0c;不…

stm32 为什么有2个晶振?8mhz+32.768k

1个是系统时钟晶振是单片机内部系统的主时钟源&#xff0c;它负责控制整个系统的时钟频率。这个晶振的频率一般比较高&#xff0c;通常在几十MHz到几百MHz不等。它和CPU以及各种总线之间相互配合&#xff0c;从而协同工作。 另外一个是外设时钟晶振则通常用于单片机的内部外设…

pnpm报错 cannot find package xxx,有的电脑正常运行,只有这个的电脑报错

pnpm build报错 cannot find package xxx&#xff0c;有的电脑正常运行&#xff0c;只有这一个报错 在网上查找各种资料发现是项目在电脑里的目录层级比较深导致的。 问题&#xff1a;在 Windows 系统上&#xff0c;文件路径过长&#xff08;超过 260 个字符&#xff09;可能…

2024年10月14日读书笔记第三篇-

我们继续学习 自下而上思考&#xff0c;总结概括 如果你将所有的信息进行归类分组、抽象概括&#xff0c;并以自上而下的方式表达出来&#xff0c;那么你的文章结构会如图1-3所示。每个方框代表你希望表达的一个思想。 你的思维从最底部的层次开始构建&#xff0c;将句子按照某…

CUDA 事件计时

CUDA 事件 可以为主机代码和设备代码计时。 基本的语法 // 定义事件变量 cudaEvent_t start, stop; // 初始化 cudaEventCreate(&start); cudaEventCreate(&stop); // 记录代表时间开始的事件&#xff0c;注意不是地址 cudaEventRecord(start); // 在TCC的驱动下可以…

【网络安全】1,600$:Auth0 错误配置

未经许可,不得转载。 文章目录 前言正文漏洞案例修复建议前言 Auth0 是一个广泛用于网站和应用程序的身份验证平台,负责管理用户身份并确保其服务的安全访问。该平台提供了多种工作流程,以无缝集成登录和注册流程。 在 Auth0 中创建新应用时,注册选项默认启用。当系统禁用…

Go 1.19.4 命令调用、日志、包管理、反射-Day 17

1. 系统命令调用 所谓的命令调用&#xff0c;就是通过os&#xff0c;找到系统中编译好的可执行文件&#xff0c;然后加载到内存中&#xff0c;变成进程。 1.1 exec.LookPath&#xff08;寻找命令&#xff09; 作用&#xff1a; exec.LookPath 函数用于在系统的环境变量中搜索可…

【每日刷题】Day139

51. N 皇后 - 力扣&#xff08;LeetCode&#xff09; //思路&#xff1a;DFS哈希。 //本题的难点就在于剪枝操作。 class Solution { public: int n; vector<vector<string>> ans; vector<string> path; bool cols[10], dig1[20], dig2[20]; void dfs(int ro…

【无标题】基于情境依赖因果影响的多智能体协作强化学习

、文章探讨了大型语言模型&#xff08;LLMs&#xff09;&#xff0c;例如GPT-4&#xff0c;是否以及在何种意义上拥有知识。作者认为&#xff0c;这些模型展现了一种称为“工具性知识”的能力&#xff0c;这种知识允许它们根据输入上下文推断任务结构&#xff0c;并在此基础上进…

appium启动hbuild打包的apk异常解决

目录 一、错误信息 二、问题解决 2.1 通过以下命令获取安装包名称&#xff1a; 2.2 这个launcher状态下的安装包名称和active&#xff0c;替换原先的安装包名称 一、错误信息 通过adb shell dumpsys activity | findstr "mResume" 命令获取的安装包信息&#xff…

2024年公路水运工程施工企业主要负责人证模拟考试题库及公路水运工程施工企业主要负责人理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年公路水运工程施工企业主要负责人证模拟考试题库及公路水运工程施工企业主要负责人理论考试试题是由安全生产模拟考试一点通提供&#xff0c;公路水运工程施工企业主要负责人证模拟考试题库是根据公路水运工程施…

芯课堂 | 使用 SWM341 系列 MCU 环境配置

SWM341系列MCU调试环境配置 SWM341 是华芯微特的其中一款 MCU&#xff0c;341 和 341内置 SDRAM 的 34S 系列&#xff0c;其内核相同。 芯片使用安谋科技“星辰”STAR-MC1 内核,基于 Arm-V8 指令集架构&#xff0c;下载烧录选 M33&#xff0c;对应的工具需要升级; 1、使用 KE…

MySQL中库的操作

目录 前言 一、查看数据库 1.1、语法 二、创建数据库 2.1、语法 2.2、自定义⼀个数据库 2.2.1、创建一个名为“aokey”的数据库 三、字符集编码和校验(排序)规则 3.1、查看数据库支持的字符集编码 3.2、查看数据库支持的排序规则 3.3、不同的字串集与排序规则对数…

芯片供电引脚为什么要放一个104电容?

每个芯片的电源都要接一个104电容是为什么? 这颗电容叫高频旁路电容&#xff0c;作用就是把电源中的高频杂波对地短路防止高频污染&#xff0c;降低电源输入对芯片的影响。 接地滤波电容&#xff0c;如果没有&#xff0c;杂波干扰非常大&#xff0c;有了这个滤波电容&#x…

Json-Rpc框架(项目设计 —— 服务端客户端 模块功能划分简介)

阅读导航 引言一、理解项目功能⭕分布式RPC服务发现/注册/订阅/创建 结构图⭕项目三大核心功能 二、服务端模块划分简介三、客户端模块划分简介 引言 在上一篇文章中&#xff0c;我们深入探讨了Json-Rpc框架中不可或缺的序列化与反序列化利器——JsonCpp库。 在本篇文章中&am…

Python酷玩之旅_如何正确使用pipreqs完成依赖导出(解决UnicodeDecodeError)

导览 前言Q&#xff1a;如何正确使用pipreqs1. 安装库2. 命令详解2.1 options2.2 path 3. 实践与问题 结语系列回顾 前言 使用python开发的应用在迁移部署的时候&#xff0c;你一定会使用pipreqs&#xff08;当然也有其他选择&#xff09;吧&#xff1f;这是一款强大的依赖管理…

【STM32CubeMX开发】-2.1-TIM_生成一个定时器中断

目录 1 Tim定时器的时钟源 2 Tim定时器的配置 2.1 中断配置 3 生成代码 4 测试结果 结尾 1 Tim定时器的时钟源 TIM1的时钟来源自APB1 Timer clocks&#xff0c;时钟树上所有总线频率均设置为了STM32F0能达到的最高频率&#xff0c;此时APB1 Timer clocks 48MHz。 2 Tim…