《STL源码分析》 rotate函数的random access iterator版 分析上篇

《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},本质就是交换两个区间。

旋转操作

源码中针对不同地迭代器类型给出了三种不同的旋转操作

  1. forward iterator版
  2. bidirectional iterator版
  3. 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 = 3shift = 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 = 2initial = first + n = 2shift = 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 = 1initial = first + n = 1shift = 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));
}

接下一篇文章《STL源码分析》 rotate函数的random access iterator版 分析下篇

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值