STL源码剖析学习十七:算法之其他算法
lower_bound 二分查找的一个版本,在已排序区间中查找value
如果区间中有该元素,则返回迭代器,指向第一个该元素
如果没有改元素,则返回一个不小于value的元素
返回值为:在不破坏排序的情况下,可插入value的位置
__lower_bound(first, last, value, Distance*, forward_iterator_tag) { Distance len = 0; distance(first, last, len); Distance half; ForwardIterator middle; while(len > 0) { half = len >> 1; middle = first; //对于前向迭代器才需要用这种方式寻找位置,对于随机访问迭代器可以直接+ first = advance(middle, half); if(*middle < value) { first = middle + 1; ++first; len = len - half - 1; } else //即使是相等,还需要迭代,因为要找出相等的元素中最前一个的位置 { len = half; } } return first; }
upper_bound 和上述函数类似,寻找的是符合条件的位置的上限
__upper_bound(first, last, value, Distance*, forward_iterator_tag) { Distance len = 0; distance(first, last, len); Distance half; ForwardIterator middle; while(len > 0) { half = len >> 1; middle = first; //和lower的区别在于,如果是相等,一个是向前搜索要查找到最前的位置 //一个是向后搜索想要查找到最后的位置 if(*middle > value) { len = half; } else { first = middle; ++first; len = len - half - 1; } } return first; }
binary_search 在有序区间中查找某个元素
binary_search(first, last, value) { ForwardIterator i = lower_bound(first, last, value); return i!=last && !(value<*i);//要学会运用这种逻辑运算 }
函数实现原理如下:
在当前序列中,从尾端往前寻找两个相邻元素,前一个记为*i,后一个记为*ii,并且满足*i < *ii。然后再从尾端寻找另一个元素*j,如果满足*i < *j,即将第i个元素与第j个元素对调,并将第ii个元素之后(包括ii)的所有元素颠倒排序,即求出下一个序列了。
next_permutation/prev_permutation 返回下一个/上一个全排列
next_permutation(first, last) { if(first == last) return false; BidirectionalIterator i = first; ++i; if(i == last) return false; i = last; --i; for(;;) { BidirectionalIterator ii = i; --i; if(*i < *ii) { BidirectionalIterator j = last; while(!(*i < *--j)); iter_swap(i, j); reverse(ii, last); } if(i == last) { reverse(first, last); return false; } } }
prev_permutation(first, last) { if(first == last) return false; BidirectionalIterator i = first; ++i; if(i == last) return false; i = last; --i; for(;;) { BidirectionalIterator ii = i; --i; if(*ii < *i) { BidirectionalIterator j = last; while(!(*i < *--j)); iter_swap(i, j); reverse(ii, last); } if(i == last) { reverse(first, last); return false; } } }
random_shuffle 将区间内的元素随机重排,获得N!种全排列中的任意一种
__random_shuffle(first, last) { if(first == last) return; for(RandomAccessIterator i=first+1; i!=last; ++i) { iter_swap(i, first+Distance(rand()%((i-first)+1))); //不理解这样就一定能产生随机化的结果。。 } }
partial_sort/partial_sort_copy 将 first到last区间中的元素重新排列,使前middle-first 小的数按序排列在前面的区间中
其余的元素排列在后面的区间中,但是不保证顺序
前半序列采用堆排序,然后遍历后半序列,如果发现有比堆顶元素小的数则和堆顶交换,并且重新调整堆
遍历完后最小的前n 个数都在前半部分序列中,将该序列堆排序即可
partial_sort(first, middle, last) { make_heap(first, middle); for(RandomAccessIterator i=middle; i<last; ++i) { if(*i < *first) __pop_heap(first, middle, i, T(*i), distance_type(first)); //pop_heap中调用了adjust_heap将堆顶的元素放到i的位置上,并把i位置上的元素插入到堆里 } sort_heap(first, middle); }
sort
STL 中的sort: 数据量大时采用快排,分段递归排序
一旦分段后的数据量小于某个门槛,为避免快排的递归调用带来的过大的额外负荷,就改用插入排序,
如果递归深度过大,就会改用堆排序
__insertion_sort(first, last) { if(first == last) return; for(RandomAccessIterator i=first+1; i!=last; ++i) { __linear_insert(first, i, value_type(first)); } } __linear_insert(first, last, T*) { T value = *last; if(value < *first) { copy_backward(first, last, last+1); *first = value; } else __unguarded_linear_insert(last, value); } __unguarded_linear_insert(last, value) { RandomAccessIterator next = last; --next; while(value < *next) { *last = *next; last = next; --next; } *last = value; }
一般的插入排序在内层循环中要进行两个判断,判断两者大小和判断是否越界
但是采用上面这种方式可以保证最小值必定在内循环区间的边缘,因此不需要判断越界,可以提高效率
快排的步骤:
1.如果s的元素是0或者1,结束
2.取s 中任何一个元素,当做枢纽v
3.将s 分割为l r两部分,使l内的元素都小于等于v,r内的元素都大于v
4.对l,r递归执行快排
media-of-three partitioning 取头尾中央三个位置的值的中值作为v
__ungarded_partition(first, last, pivot) { while(1) { while(*first < pivot) ++first; --last; while(pivot < *last) --last; if(!(first<last)) return first; iter_swap(first, last); ++first; } }
快排的关键在于v 的选择,如果选择不当,会导致性能恶化
introsort 混合式排序方法 内省式排序
在一般情况下都和快排一样,但是当有分割行为有恶化的趋势时会自动检测,改成堆排序
综合性能又比一开始就用堆排序来的好
SGI STL的sort
sort(first, last) { if(first != last) { __introsort_loop(first, last, value_type(first), __lg(last-first)*2); __final_insertion_sort(first, last); } }
其中__lg 用来控制分割恶化的情况
__introsort_loop(first, last, T*, depth_limit) { while(last - first > __stl_threshold)//__stl_threshold = 16 16个元素以下就不用快排改用插入排序 { //当分割恶化时,改用堆排序 if(depth_limit == 0) { partial_sort(first, last, last); return; } --depth_limit; RandomAccessIterator cut = __unguarded_partition(first, last, T(__media(*first, *(first+(last-first)/2), *(last-1)))); __introsort_loop(cut, last, value_type(first), dept_limit); last = cut; } }
当上述函数操作完成,整个区间内有多个元素个数小于16的子区间,其中的元素部分排序但是没有完全排序
继续调用插入排序对这些子区间分别进行排序。
equal_range返回一个pair是lower_bound和upper_bound的组合
__equal_range(first, last, value, Distance*, random_access_iterator_tag) { Distance len = last - first; Distance half; RandomAccessIterator left, right, middle; while(len > 0) { half = len >> 1; middle = first + half; if(*middle < value) { first = middle + 1; len = len - half -1; } else if(value < *middle) { len = half; } else { left = lower_bound(first, middle, value); right = upper_bound(++middle, last, value); return pair<RandomAccessIterator, RandomAccessIterator>(left, right); } } return pair<RandomAccessIterator, RandomAccessIterator>(first, first); //如果没有找到value 则指向第一个大于value的值 }
inplace_merge 应用于有序区间,把两个连接在一起且各自有序的序列合并成一个序列,仍保持有序
稳定操作,保持相对次序不变,如果有相同元素,第一个序列的排在前面
内部实现时根据有无缓冲不同处理
有缓冲区的情况下的辅助函数
__merge_adaptive(first, middle, last, len1, len2, buffer, buffer_size) { if(len1 <= len2 && len1 <= buffer_size)//缓冲区可以容纳序列1 { Pointer end_buffer = copy(first, middle, last); merge(buffer, end_buffer, middle, last, first); } else if(len2 <= buffer_size)//缓冲区可以容纳序列2 { Pointer end_buffer = copy(middle, last, buffer); __merge_backward(first, middle, buffer, end_buffer, last); } else//缓冲区两个都不能容纳:分割递归,让缓冲区长度减半,看是否能容纳 { BidirectionalIterator first_cut = first; BidirectionalIterator second_cut = middle; Distance len11 = 0; Distance len22 = 0; if(len1 > len2) { len11 = len1/2; advance(first_cut, len11); second_cut = lower_bound(middle, last, *first_cut); distance(middle, second_cut, len22); } else { len22 = len2 / 2; advance(second_cut, len22); first_cut = upper_bound(first, middle, *second_cut); distance(first, first_cut, len11); } BidirectionalIterator new_middle = __rotate_adaptive(first_cut, middle, second_cut, len1 - len11, len22, buffer, buffer_size); __merge_adaptive(first, first_cut, new_middle, len11, len22, buffer, buffer_size); __merge_adaptive(new_middle, second_cut, last, len1-len11, len2-len22, buffer, buffer_size); } }
nth_element 重新排列区间,使迭代器nth 所指向的元素与整个区间内排序后,同一位置的元素同值
保证nth-last 中没有任何一个元素不大于 first-nth 中的元素
比较近似于partition
不断用首尾中央三点中值为枢纽之分割法,将序列分割成更小的子序列,
如果nth 落入左子序列, 就继续分割左子序列,如果落在右子序列就分割右子序列,最后对小于3的序列进行排序
nth_element(first, nth, last, T*) { while(last - first > 3)//当区间中元素过少时就改用插入排序 { RandomAccessIterator cut = __ungarded_partition(first, last, T(__media(*first, *(first+(last-first)/2),*(last-1)))); if(cut <= nth) first = cut; else last = cut; } __insertion_sort(first, last); }
merge_sort 归并排序,需要额外的内存,内存之间的拷贝需要时间,但是实现简单
mergesort(first, last) { n = distance(first, last); if(n == 0 || n == 1) return; else { BidirectionalIter mid = first + n / 2; mergesort(first, mid); mergesort(mid, last); inplace_merge(first, mid, last); } }
浙公网安备 33010602011771号