zoukankan      html  css  js  c++  java
  • STL中rotate算法的理解

    STL中rotate针对不同的迭代器有3种不同的实现,在http://blog.csdn.net/v_JULY_v/article/details/6322882中也有描述,这里以学习为目的,将这STL中rotate的RandomAccessIterator的算法整理一下

    以下内容参考自:http://www.cnblogs.com/atyuwen/archive/2009/11/08/rotate.html

    这里的rotate操作,也就是指循环移位。比如将串“ABCDEFG”以D为中心旋转,就相当将该串向左循环移位,直到第一个元素为D为止,最后得到新串“DEFGABC”。要想方便的完成rotate操作,一个常见的技巧是这样的:先将前半部分反转,再将后半部分反转,最后再将整个串反转即可(这里的前半部分与后半部分是以旋转中心来划分的)。还是以串“ABCDEFG”以D为中心旋转为例,以D为分割点,将先半部分与后半部分分别反转后,得“CBAGFED”,最后将整个串反转即得“DEFGABC”。这个算法在很多书上都提到过,相信大家一定都很熟悉了。那么其效率如何呢,假设串的长度为t,那么完成整个旋转过程需要约t次swap操作,也即是说需要大概3t次赋值,同时只需要常量的临时空间。因为其实现简单,所以还是差强人意。所以SGI STL在处理双向迭代器容器时也正是使用了该算法。但是进一步观察STL源码可以发现,在处理存储在拥有随机访问能力的容器中的串时,SGI STL却是采用了另外一种算法,而这个算法的原理在《STL源码剖析》中恰恰被hjj无视了,所以我在这里再简单地梳理一下。

    上面只涉及到三个函数:__rotate、__gcd、__rotate_cycle。后两个函数都比较容易理解:__gcd没什么好说的,XXX嘛,当然是求最大公约数了。__rotate_cycle,是从某个初始元素开始,依次将其替换成其后相隔固定距离的元素。如果后面没有足够的偏移距离了,则又返回头部继续计算(相当于求模)。直到最后形成一个置换圈为止。

    现在来仔细观察函数__rotate,这个函数实际上也不复杂,才区区三行:先是求出偏移距离和串总长的最大公约数,然后循环n次,分别以串的前n个元素为起点进行__rotate_cycle操作,over。但这怎么就保证了能正确地完成对输入串的rotate操作呢?

    这就涉及到数论中的一个小定理:若有两个正整数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}的某个排列并重复两次,事实上也正是{6,4,2,0, 6,4,2, 0}。特别地,若m、n互素,d=1,那么序列{m%n,2m%n,3m%n,...,(n-1)m%n}实际上就是{1, 2, 3,..., n-1}的某个排列。这个定理的证明过程可以很多书中找到(比如具体数学4.8节),这里不再详述。

    对这个排列的理解是比较重要的,如果我们设置其排列从3开始那这个排列就是3,4,5,n-1,n, 0,1,2。所以STL中的rotate就是利用了这个原理,通过使用不同的起始值来,产生不同的序列。序列的共同点就是这个序列构成了一个环,并且相邻元素的index差距是gcd。 模仿的一个对数组进行rotate的实现: 

    int gcd(int m, int n)
    {
        int big, small;
        if(m>n) {big = m; small = n;}
        else {big = n; small=m;}
        while(small !=0)
        {
            int t = big%small;
            big = small;
            small = t;
        }

        return big;
    }

    /*
    initial is the starting point of the cycle;
    shift is the distance of the replacement of element from its current position. 也就是说,shift是元素要移动的距离 
    */
    void myrotate_cycle(int *iarray, int first, int last, int initial, int shift)
    {
        int t = iarray[initial];
        int ptr1 = initial;
        int ptr2 = initial+shift;
        while(ptr2!=initial)
        {
            iarray[ptr1] = iarray[ptr2];
            ptr1 = ptr2;
            if(ptr2<(last-shift))
                ptr2+=shift;
            else
                ptr2=first + shift- (last -ptr2); // roll back
        }
        iarray[ptr1] = t;
    }

    void myrotate(int *iarray, int first, int middle, int last)
    {
        int n = gcd(last-first, middle-first);
        while(n--)
            myrotate_cycle(iarray,first,last,first+n,middle-first); // only the starting point is changing
    }


    int main(int argc, char **argv)
    {

        int a[]= {01,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
        myrotate(a,0,3,16);
    }

    了解这个引理后,就很容易看出__rotate函数的内涵了。若第一步求得的最大公约数n为1,那么只需一次__rotate_cycle就可以遍历到所有的元素,并将每个元素正确的替换为其后相距某个距离的元素,于是也就完成了循环左移操作。若n大于1,那么每一次__rotate_cycle只能将t/n的元素正确的左移,其中t为串的总长度,而这些被移动的元素是以d为等间距的,所以循环n次,并分别以串的前n个元素为起点进行__rotate_cycle操作,就能保证将所有的元素都移动到正确的位置上。

     

    背景知识:

    gcd,即辗转相除法,又称欧几里得算法,是求最大公约数的算法,即求两个正整数之最大公因子的算法。

    Reference:http://www.cnblogs.com/atyuwen/archive/2009/11/08/rotate.html

  • 相关阅读:
    封装成帧、帧定界、帧同步、透明传输(字符计数法、字符串的首尾填充法、零比特填充的首尾标志法、违规编码法)
    计算机网络之数据链路层的基本概念和功能概述
    物理层设备(中继器、集线器)
    计算机网络之传输介质(双绞线、同轴电缆、光纤、无线电缆、微波、激光、红外线)
    计算机网络之编码与调制
    0953. Verifying an Alien Dictionary (E)
    1704. Determine if String Halves Are Alike (E)
    1551. Minimum Operations to Make Array Equal (M)
    0775. Global and Local Inversions (M)
    0622. Design Circular Queue (M)
  • 原文地址:https://www.cnblogs.com/whyandinside/p/2616199.html
Copyright © 2011-2022 走看看