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

来源:互联网 发布:微信炸群软件 编辑:程序博客网 时间:2024/05/16 03:36


今天看 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 稍有不同。
0 0