本文为一篇自己在阅读MyTinySTL过程的一些随笔,分享出来和大家一起加油进步。如有错误/疑惑,感谢大家指正。共勉!
is_permutation
template <class ForwardIter1, class ForwardIter2, class BinaryPred>
bool is_permutation_aux(ForwardIter1 first1, ForwardIter1 last1,
ForwardIter2 first2, ForwardIter2 last2,
BinaryPred pred)
{
constexpr bool is_ra_it = mystl::is_random_access_iterator<ForwardIter1>::value
&& mystl::is_random_access_iterator<ForwardIter2>::value;
//判断输入的ForwardIter1,ForwardIter2是否是random_access_iterator
if (is_ra_it)
{ //长度不同直接false
auto len1 = last1 - first1;
auto len2 = last2 - first2;
if (len1 != len2)
return false;
}
// 先找出相同的前缀段
for (; first1 != last1 && first2 != last2; ++first1, (void) ++first2)
{
if (!pred(*first1, *first2))
break;
}
if (is_ra_it)
{
if (first1 == last1) //说明两段完全相同
return true;
}
else
{
auto len1 = mystl::distance(first1, last1);
auto len2 = mystl::distance(first2, last2);
if (len1 == 0 && len2 == 0)
return true;
if (len1 != len2)
return false;
}
// 判断剩余部分
for (auto i = first1; i != last1; ++i)
{
bool is_repeated = false;
for (auto j = first1; j != i; ++j)//两轮遍历判断是不是有重复值
{
if (pred(*j, *i))
{
is_repeated = true;
break;
}
}
if (!is_repeated)
{
// 计算 *i 在 [first2, last2) 的数目
auto c2 = 0;
for (auto j = first2; j != last2; ++j)
{
if (pred(*i, *j))
++c2;
}
if (c2 == 0)
return false;
// 计算 *i 在 [first1, last1) 的数目
auto c1 = 1;
auto j = i;
for (++j; j != last1; ++j)
{
if (pred(*i, *j))
++c1;
}
if (c1 != c2)
return false;
}
}
return true;
}
问题1:为什么首先判断迭代器的类型?
解答1:MyTinySTL在iterator.h中定义了五种迭代器类型,只有random_access_iterator可以直接用last - first的方式获得长度。
算法思路:
特殊情况:长度不等的两段排列组合,直接返回false。
- 遍历找出相同的前缀段。
- 判断剩余部分的长度,若是遍历结束(长度为0/first迭代器与last迭代器重合)返回true,长度不等返回false。
- 判断剩余部分:先判断某个值有没有重复,有重复的话计算该值在[first1,last1]和[first2,last2]中的重复次数,次数不等,直接返回false。(但我想这里的判断是不是写错了???)
next_permutation
// next_permutation
// 取得[first, last)所标示序列的下一个排列组合,如果没有下一个排序组合,返回 false,否则返回 true
template <class BidirectionalIter>
bool next_permutation(BidirectionalIter first, BidirectionalIter last)
{
auto i = last;
if (first == last || first == --i) //空或只有一个,无下一个排序组合
return false;
for (;;)
{ //从后往前遍历
auto ii = i;
if (*--i < *ii)
{ // 找到第一对小于关系的元素
auto j = last;
while (!(*i < *--j)) {}
mystl::iter_swap(i, j); // 交换 i,j 所指元素
mystl::reverse(ii, last); // 将 ii 之后的所有元素反转
return true;
}
if (i == first) //这个区间就是个逆序区间,从大到小,直接reverse成从小到大即可。
{
mystl::reverse(first, last);
return false;
}
}
}
首先,什么叫下一个排列组合?
下一个排列组合:比当前排列组合更大的排列组合中的最小一个。
简单举个vector例子
[1,2,3] —> [1,3,2] --> [2,1,3] --> [2,3,1] --> [3,1,2] --> [3,2,1]
粗略地把它看做一个三位数,是不是 123 < 132 < 213 < 231 < 312 < 321 ?同理类比。
123 的下一个排列组合就是 132 , 132 的下一个排列组合就是 213 ……
接着,我们以一个稍微复杂的序列来理解算法思路
[1,2,3,6,5,4]
算法思路:
特殊情况,没有元素/只有一个元素,返回false。
-
从后往前遍历该排列,找到第一个可以使当前排列变大的位置:3 < 4
-
交换这两个迭代器所指示的元素:[1,2,4,6,5,3](到这时,我们可以看出来虽然这个排列组合是比原排列组合大的排列组合中的一个,但并不是其中最小的。)
-
剩余部分( [6,5,3] )一定是逆序排列,直接reverse即可,变成从小到大( [3,5,6] )。此时,[1,2,4,3,5,6] 一定是比 [1,2,3,6,5,4] 更大的排列组合中最小的那一个。即函数所需要的下一个排列组合。
[1,2,3,6,5,4] --> [1,2,4,6,5,3] --> [1,2,4,3,5,6]*
merge_without_buffer
template <class BidirectionalIter, class Distance>
void merge_without_buffer(BidirectionalIter first, BidirectionalIter middle,
BidirectionalIter last, Distance len1, Distance len2)
{
if (len1 == 0 || len2 == 0)
return;
if (len1 + len2 == 2)
{
if (*middle < *first)
mystl::iter_swap(first, middle);
return;
}
auto first_cut = first;
auto second_cut = middle;
Distance len11 = 0;
Distance len22 = 0;
//选择一个居中的比较主元,来把[A,B]拆成[A1,A2,B1,B2]
//A1是[first, middle)中小于主元的元素集合,A2是[first, middle)中大于或等于主元的元素集合
//B1是[middle, last) 中小于主元的元素集合,B2是[middle, last) 中大于或等于主元的元素集合。
if (len1 > len2)
{ // 序列一较长,找到序列一的中点
len11 = len1 >> 1;
mystl::advance(first_cut, len11); //拆A
second_cut = mystl::lower_bound(middle, last, *first_cut); //拆B
len22 = mystl::distance(middle, second_cut);
}
else
{ // 序列二较长,找到序列二的中点
len22 = len2 >> 1;
mystl::advance(second_cut, len22);
first_cut = mystl::upper_bound(first, middle, *second_cut);
len11 = mystl::distance(first, first_cut);
}
//通过totate函数,交换[A2,B1],变成[A1,B1,A2,B2]
auto new_middle = mystl::rotate(first_cut, middle, second_cut);
//对左右区间递归调用
mystl::merge_without_buffer(first, first_cut, new_middle, len11, len22);
mystl::merge_without_buffer(new_middle, second_cut, last, len1 - len11, len2 - len22);
}
注意:理解这个函数之前请先跳转理解rotate函数BidirectionalIter重载版本
实际举数组例子来讲解算法思路比较混乱,先用一个抽象化例子 [A,B] 来讲解思路。
A 和 B 分别代表两个有序序列,且满足同一排序法则。
算法思路:
特殊情况:A或B无元素,直接返回;只有两个元素,交换。
- 先根据某一序列长度,选择一个居中元素作为比较主元。假定len1 > len2。
- 根据这个比较主元,把[A,B]拆成[A1,A2,B1,B2]。拆A的方式用advance,迭代器first前进;拆B的方式调用lower_bound寻找不小于比较主元的位置作为拆分点。
- 通过totate函数,交换[A2,B1],变成[A1,B1,A2,B2]。
- 对新划分的左区间[A1,B1]和右区间[A2,B2]递归调用。
粗略地概括就是:把两段序列中,不断地把比主元小的元素前移,比主元大的元素后移。然后拆成子区间,在子区间中执行该操作。子区间拆成更小的子区间,直至特殊情况。
具体化一个简单例子:A[1,3,4,6,9,12] B[2,5,7,8]
第一次比较主元:6
把[A,B]拆成[A1,A2,B1,B2] :A1[1,3,4] A2[6,9,12] B1[2,5] B2[7,8]
交换[A2,B1],变成[A1,B1,A2,B2] : A1[1,3,4] B1[2,5] A2[6,9,12] B2[7,8]
[1,3,4,2,5,6,9,12,7,8] 相比于 [1,3,4,6,9,12,2,5,7,8] 就是所谓的
比主元小的元素前移,比主元大的元素后移
然后对对新划分的左区间[1,3,4,2,5]和右区间[6,9,12,7,8]递归排序
最终生成单一有序序列
partial_sort
// 对整个序列做部分排序,保证较小的 N 个元素以递增顺序置于[first, first + N)中
/*****************************************************************************************/
template <class RandomIter>
void partial_sort(RandomIter first, RandomIter middle,
RandomIter last)
{
mystl::make_heap(first, middle); //构造大顶堆
for (auto i = middle; i < last; ++i) //比较[middle, last)与堆顶元素
{
if (*i < *first)
{
mystl::pop_heap_aux(first, middle, i, *i, distance_type(first)); //first与i对调,重新调整大顶堆
}
}
mystl::sort_heap(first, middle); //将大顶堆按从小到大排序
}
这篇博客讲的非常好:STL之partial_sort算法源码讲解
补充一个自己的疑惑:为什么从小到大的partial_sort()排序,用的是从大到小的大顶堆呢?
解答:这就是算法的高明之处,选用小顶堆的话,在确定[middle,last)中是否还有“前N个较小元素”时,就需要遍历比较小顶堆中的每个元素,方能确定是否要插入,增加了时间复杂度。选用大顶堆,直接比较堆顶最大元素,更快。
------------------------------好像没什么难以理解的了----------------------大家有需要再更新此篇吧-----------------------