《STL源码分析》 rotate函数的random access iterator版 分析
函数描述
《STL源码分析》那本书里介绍的rotate函数的作用是交换[first, middle)和[middle, last)内的元素,比如序列{1, 2, 3, 4, 5, 6, 7, 8, 9}在元素4那做旋转操作,序列就变为了{4, 5, 6, 7, 8, 9, 1, 2, 3},本质就是交换两个区间。
旋转操作
源码中针对不同地迭代器类型给出了三种不同的旋转操作
- forward iterator版
- bidirectional iterator版
- random access iterator版
第一版使用最简单的元素一一交换的方式;
第二版使用三次reverse反转来实现(reverse第一个区间、reverse第二个区间、reverse总区间);
第三版是最难理解的,下面详细讲解一下。
第三版源码
template<class RandomAccessIterator,class Distance>
void __rotate(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, Distance*, random_access_iterator_tag)
{
Distance n = __gcd(last - first, middle, first);
while (n--)
__rotate_cycle(first, last, first + n, middle - first, value_type(first));
}
template<class EuclideanRingElement>
EuclideanRingElement __gcd(EuclideanRingElement m, EuclideanRingElement n)
{
while (n != 0)
{
EuclideanRingElement t = m%n;
m = n;
n = t;
}
return m;
}
template<class RandomAccessIterator,class Distance,class T>
void __rotate_cycle(RandomAccessIterator first, RandomAccessIterator last, RandomAccessIterator initial, Distance shift, T*)
{
T value = *initial;
RandomAccessIterator ptr1 = initial;
RandomAccessIterator ptr2 = ptr1 + shift;
while (ptr2 != initial)
{
*ptr1 = *ptr2;
ptr1 = ptr2;
if (last - ptr2 > shift)
ptr2 += shift;
else
ptr2 = first + (shift - (last - ptr2));
}
*ptr1 = value;
}
这里的__gcd是为了求取最大公因数,我们先不管。
重点看__rotate_cycle函数。是从某个初始元素开始,依次将其替换成其后相隔固定距离的元素。如果后面没有足够的偏移距离 了,则又返回头部继续计算(相当于求模)。直到最后形成一个置换圈为止。
例子
如:1, 2, 3, 4, 5, 6, 7, 8, 9 以 4 为基准旋转,全长为 9,前段长为 3,最大公约数 n = 3;
(一)、n = 3 时,initial = first + n = 3,shift = middle - first = 3;然后进入__rotate_cycle函数
( 1 ) ptr1 = 3,ptr2 = 6,因为 ptr2 != initial,所以 ptr1 的元素变为 *ptr2,即 4 变为 7 ,然后 ptr1 挪到 ptr2 处。ptr 进入else ,更新到 1 处。原 ptr2 现 ptr1 处变为 1 。此时序列变为 {1,2,3,7,5,6,4,8,9}。
( 2 ) ptr2 != initial,所以 ptr1 的元素变为 *ptr2,即 1 变为 4 。此时序列变为{4,2,3,7,5,6,1,8,9}。
( 3 ) 此时ptr2 == initial,所以内层循环结束 。进入外层循环。
(二)、n - -,此时 n = 2 ,initial = first + n = 2,shift = middle - first = 3;然后进入__rotate_cycle函数
( 1 ) ptr1 = 2,ptr2 = 5 。因为 ptr2 != initial,所以 ptr1 的元素变为 *ptr2,即 3 变为 6 ,然后 ptr1 挪到 ptr2 处。ptr 进入if,更新到 9 处。原 ptr2 现 ptr1 处变为 3 。此时序列变为 {4,2,6,7,5,3,1,8,9}。
( 2 ) ptr1 = 5,ptr2 = 8 。因为 ptr2 != initial,所以 ptr1 的元素变为 *ptr2,即 3 变为 9 ,然后 ptr1 挪到 ptr2 处。ptr 进入if,更新到 6 处。原 ptr2 现 ptr1 处变为 3 。此时序列变为 {4,2,6,7,5,9,1,8,3}。
( 3 ) 此时ptr2 == initial,所以内层循环结束 。进入外层循环。
(三)、n - -,此时 n = 1 ,initial = first + n = 1,shift = middle - first = 3;然后进入__rotate_cycle函数
( 1 ) ptr1 = 1,ptr2 = 4 。因为 ptr2 != initial,所以 ptr1 的元素变为 *ptr2,即 2 变为 5 ,然后 ptr1 挪到 ptr2 处。ptr 进入if,更新到 8 处。原 ptr2 现 ptr1 处变为 2 。此时序列变为 {4,5,6,7,2,9,1,8,3}。
( 2 ) ptr1 = 4,ptr2 = 7 。因为 ptr2 != initial,所以 ptr1 的元素变为 *ptr2,即 2 变为 8 ,然后 ptr1 挪到 ptr2 处。ptr 进入else,更新到 5 处。原 ptr2 现 ptr1 处变为 2 。此时序列变为 {4,5,6,7,8,9,1,2,3}。
( 3 ) 此时ptr2 == initial,所以内层循环结束 。进入外层循环。
(四)、n - -,此时 n = 0 ,结束外层循环。此时的序列已完成位置交换。
原理
涉及到数论中的一个小定理:若有两个正整数m、n,且gcd(m,n)=d,那么序列
{m%n, 2m%n, 3m%n,…, nm%n}
一定是
{0, d, 2d,…, n-d}
的某个排列并重复出现d次,其中%号代表求模操作。
比如若m=6, n=8,d=gcd(m,n)=2,那么
{6%8, 12%8, 18%8,…, 48%8}
即为{0,2,4,6}
的某个排列并重复。
特别地,若m、n互素,d=1,那么
序列{m%n,2m%n,3m%n,…,(n-1)m%n}
实际上就是{1, 2, 3,…, n-1}的某个排列。
算法分析
3种算法的时间复杂度都是O(n),那么为什么要分3种来算呢?
这是因为,
forward iterator版本需要进行迭代,所以引入了更多的判断(前后段的结束次序)。
bidirectional iterator版本不需要进行迭代进行n次交换就可以了。
random access iterator版本与BidirectionalIterator版本最大的区别是交换方式。
bidirectional iterator版本的交换是使用两两交换(iter_swap),
random access iterator版本的交换是使用循环赋值。
所以B版本操作一次需要3次赋值操作,而R版本只需要一次。
也可以说B版本复杂度为O(3tn),R版本复杂度为O(tn)。
测试
看到一篇博客,他做了测试,发现R版本的并不一定比F版本的快,博客地址,对此,仿照他的博客也做了一次测试。
创建2个vector,分别用R版本和F版本进行序列互换,做十次实验,统计并比较两者时间。
代码
//决定迭代器的distance_type
template<class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&) {
return static_cast<typename iterator_traits<Iterator>::difference_type*>(0);
}
R版本代码
// 最大公因数,利用辗转相除法
template <class EuclideanRingElement>
EuclideanRingElement __Gcd(EuclideanRingElement m, EuclideanRingElement n)
{
while (n != 0)
{ //这里是一个迭代
EuclideanRingElement t = m % n;
m = n;
n = t;
}
return m;
}
template <class RandomAccessIterator, class Distance, class T>
void __rotate_cycle(RandomAccessIterator first, RandomAccessIterator last,
RandomAccessIterator initial, Distance shift, T *)
{
T value = *initial; //记下链首元素的值,接下来链首元素“出列”留下一个“槽”
RandomAccessIterator ptr1 = initial;
RandomAccessIterator ptr2 = ptr1 + shift; //指向链中下一元素
while (ptr2 != initial)
{
*ptr1 = *ptr2;
ptr1 = ptr2; //ptr1指向“槽”的位置
if (last - ptr2 > shift) //还没有到达最后一个元素
ptr2 += shift;
else
//为了跳出循环!真有点不明白为什么不用while(true),然后此处直接break??
ptr2 = first + (shift - (last - ptr2));
}
*ptr1 = value;
}
// rotate 的 random access iterator 版
template <class RandomAccessIterator, class Distance>
void __rotate_a(RandomAccessIterator first, RandomAccessIterator middle,
RandomAccessIterator last, Distance *
)
{
// 以下迭代器的相减操作只使用于RandomAccessIterator
// 取全长和前段(移动的位数)的最大公因数
Distance n = __Gcd(last - first, middle - first);
// 链数为gcd(m,n) 。链中元素个数为n/gcd(m,n)。
while (n--) //为了书写方便,先从最后一条链开始循环
__rotate_cycle(first, last, first + n, middle - first,
value_type(first));
}
//random版本
template <class ForwardIterator>
inline void rotate_a(ForwardIterator first, ForwardIterator middle,
ForwardIterator last) {
if (first == middle || middle == last) return; //交换的中间点在首尾,返回
__rotate_a(first, middle, last, distance_type(first));
}
F版本代码
//Forward版本
template <class ForwardIterator, class Distance>
void __rotate_f(ForwardIterator first, ForwardIterator middle,
ForwardIterator last, Distance *)
{
for (ForwardIterator i = middle;;)
{
iter_swap(first, i); // 前后段元素一一交换
++first; // 双双前进1
++i;
// 以下判断是前段[first, middle)先结束还是后段[middle,last)先结束
if (first == middle)
{ // 前段先结束
if (i == last)
return; // 如果后段也结束了,那么就结束了
middle = i; // 否则进行调整,之后再进行迭代
}
else if (i == last) // 后段先结束
i = middle; // 否则进行调整,之后再进行迭代
}
}
template <class ForwardIterator>
inline void rotate_f(ForwardIterator first, ForwardIterator middle, ForwardIterator last) {
// typedef typename iterator_traits<ForwardIterator>::difference_type difference_type;
if (first == middle || middle == last) return; //交换的中间点在首尾,返回
__rotate_f(first, middle, last, distance_type(first));
}