STL源码之rotate函数结合图和实例分析


今天看 STL 源码看到 rotate() 函数这一块,该函数就是将 [first, middle) 的元素和 [middle, last) 的元素互换。middle 的元素会成为容器的第一个元素。如果有个数字序列 {1, 2, 3, 4, 5, 6, 7},对元素 3 做旋转操作,会形成 {3, 4, 5, 6, 7, 1, 2}。其实这就是我们平时说的左旋转字符串,只不过泛型化了而已。它可以旋转的内容不止字符串,其他迭代器类型都可以。


三种方法的分析: 

算法1(分组交换):(来自网友:雁过无痕)
若a长度大于b,将ab分成a0a1b,交换a0和b,得ba1a0,只需再交换a1 和a0。若a长度小于b,将ab分成ab0b1,交换a和b0,得b0ab1,只需再交换a 和b1。不断将数组划分和交换,直到不能再划分为止。分组过程与求最大公约数很相似。

代码如下:

emplate <class ForwardIterator, class Distance>
// Distance类型仅仅对于random iterator的实现版本有意义,但为了便于上层代码便于调用,所以使用// 了同样的签名。
void __rotate(ForwardIterator first, ForwardIterator middle, ForwardIterator last, Distance*, forward_iterator_tag)
{
    for (ForwardIterator i = middle; ;) {
        // iter_swap用于交换两个iterator所指向的内容。
        // 也可以这样写:swap(*first, *i);
        iter_swap(first, i);
        ++first;
        ++i;
        if (first == middle) {
            // first和i同时到达末尾,元素交换结束,返回。
            if (i == last)
                return;
            // first首先到达末尾,说明A的长度小于B。
            middle = i;
        }
        // i首先到达末尾,说明A的长度大于B。
        else if (i == last)
            i = middle;
    }
}

 
算法2 (三次反转)
利用ba=(br)r(ar)r=(arbr)r,先分别反转a、b,最后再对所有元素进行一次反转。

代码如下:

template <class BidirectionalIterator, class Distance>
void __rotate(BidirectionalIterator first, BidirectionalIterator middle, BidirectionalIterator last, Distance*, bidirectional_iterator_tag)
{
    // 翻转A
    reverse(first, middle);
    // 翻转B
    reverse(middle, last);
    // 翻转A'B'
    reverse(first, last);
}

算法3 (使用gcd)(分析来自网友:陈覃)
__gcd是求两个数的最大公约数,也是循环位移的遍数。
举个例子来说明算法过程,数组123456789,把123翻转到右边,*first=1,*last=9,*middle=4;
要旋转字符串(123)的长度为3,字符串长度为9,3和9的最大公约数为3,因此需要翻转3遍;
第一遍从*(initial+shift)=6开始,6移到3的位置,9移到6的位置,下一个位置是ptr2 = first + (shift - (last - ptr2))=0+(3-(8-8))=3,不满足ptr2 != initial的条件,退出循环,然后*ptr1 = value,即把数字3移动到数字9的位置,从而完成了3,6,9三个数字的位移,下面的2遍循环则分别完成2,5,8和1,4,76个数字的位移,最后得到最终结果456789123。

对于辗转相除法更详细的证明可以参考我以前的博客:辗转相除法、埃拉托色尼筛选法、牛顿迭代法证明与C++实现

整个算法过程可用下图表示:      


代码如下:

template <class RandomAccessIterator, class Distance>
void __rotate(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, Distance*, random_access_iterator_tag)
{
    // gcd是求最大公约数的函数。
    Distance n = __gcd(last - first, middle - first);

    while (n--)   //注意这里是n--,我因为没看见这个n--,时间浪费了半天
        // 需要执行__rotate_cycle n次。
        __rotate_cycle(first, last, first + n, middle - first, value_type(first));
}

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;
}

template <class EuclideanRingElement>
EuclideanRingElement __gcd(EuclideanRingElement m, EuclideanRingElement n)
{
    while (n != 0) {
        EuclideanRingElement t = m % n;
        m = n;
        n = t;
    }

    return m;
}

由于前两种比较简单,在这里我仅实现了第三种作为练习,一次性AC:)

#include <iostream>
#include <assert.h>

int calc_gcd(int m, int n)
{
    if(m < n)
        std::swap(m, n); 
    if(n == 0)
        return m;
    calc_gcd(n, m%n);
}

template <typename T>
void cycle_rotate(T *arr, int *first, int *last, int *initial, int rotate_num)
{
    T value = *initial;
    T *ptr1 = initial, *ptr2 = ptr1 + rotate_num;
    while(ptr2 != initial){
        *ptr1 = *ptr2;
        ptr1 = ptr2;
        if(last - ptr2 >= rotate_num)  //可以等于,因为是下标
            ptr2 += rotate_num;
        else
            ptr2 = first + (rotate_num - (last - ptr2)) - 1; //注意要减一,因为我这里用的是下标
    }   
    *ptr1 = value;
}

template <typename T>
void rotate(T* arr, int start, int end, int rotate_num)
{
    assert(start >= 0 && rotate_num > start && rotate_num <= end+1);
  int gcd = calc_gcd(end-start+1, rotate_num);
    while(gcd--)
        cycle_rotate(arr, arr+start, arr+end, arr+start+gcd, rotate_num);
}

int main()
{
    int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int len = sizeof(array) / sizeof(int);
    rotate(array, 0, len-1, 5);
    for(auto i : array)
        std::cout<<i<<' ';
    std::cout<<std::endl;

    return 0;
}

输出:


注:我的代码中,输入分别是开始下标,结束下标,旋转个数,所以和 STL 稍有不同。
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值