STL源码—— rotate算法理解

rotate算法实现了区间内元素的循环移位,将[first, middle)内的元素和[middle, last)内的元素互换,middle所指的元素会成为容器的第一个元素。如下图所示:


由于不同迭代器造成算法的效率有所不同,因此设计为双层架构。

template<class ForwardIterator>
inline void rotate(ForwardIterator first, ForwardIterator middle, ForwardIterator last){
    if(first == middle || middle == last) return;
    __rotate(first, middle, last, distance_type(first), iterator_category(first));
}

下面是根据不同的迭代器类型而完成的三个旋转操作:

①forward iterator:采用分组来进行交换,使用first和i这两个迭代器来表示两段的起始交换位置,其中又有两种情况:

i)前段[first, middle) 长度不大于后段[middle, last)


如图所示,先将前段与后段的部分交换,此时后段的当前迭代器为i,则[middle, i)这部分的元素已经在其最终的位置上了,而[first,i)中的元素仍需与[i, last)交换。故令middle = i,在新区间上继续进行交换操作。

ii)前段[first, middle) 长度大于后段[middle, last)


如图所示,先将前段的部分元素与后段进行交换,此时后段的迭代器i已经达到last,[middle,last)中的元素已经在其最终的位置上,而[first, middle)和[middle, last)的元素仍需交换,而i表示后段迭代器的起始交换位置,因此需将middle的值赋给i,在新区间上继续进行交换操作。

算法对Forward Iterator的处理,复杂度为O(3n)(在STL之父所著的《Elements of Programming》中有提及其证明)。

代码如下:

template<class ForwardIterator, class Distance>
void __rotate(ForwardIterator first, ForwardIterator middle, 
              ForwardIterator last, Distance*, forward_iterator_tag){
    for(ForwardIterator i = middle; ;){
        iter_swap(first, i);        //前段、后段的元素一一交换
        ++first;
        ++i;
        if(first == middle){        //前段先结束
            if(i == last) return;   //后段也结束
            middle = i;             //调整,对新的前、后段再作交换
        }        
        else if(i == last)          //后段先结束
            i = middle;
    }
}

②bidirectional iterator:可以将问题看做是把数组转换成。则从开始,首先对求逆,得到,然后对求逆,得到,最后整体求逆,得到,此时恰好就是。编程珠玑中使用了手掌翻转来简化理解:


因此只需进行三次翻转即可,复杂度为O(3n),代码如下:

template<class BidirectionalIterator, class Distance>
void __rotate(BidirectionalIterator first, BidirectionalIterator middle, 
              BidirectionalIterator last, Distance*, bidirectional_iterator_tag){
        reverse(first, middle);
        reverse(middle, last);
        reverse(first, last);
}

③random access iterator:最简单的移位需要O(n)的空间来达到O(n)的时间复杂度,由于要做到in-place,算法思想是将O(n)的空间降至O(1)。对一个长度为n的数组作k-轮换,每个元素都要向前循环移位k个长度。如下图所示:


称这样一个结构为一个环:第一个元素i移动后,下一个元素i + k移动到i处,接着i + 2k……,如此重复直到没有新的元素加入到环中。此过程中只需O(1)的空间保存起始元素,后面的元素向前填充。下面考虑环的长度,环中元素的索引依次为:i, i + k, i + 2k,……。设环的长度为m,则m是使得的最小正整数,则可得:

,若k和n的最小公倍数比mk小,则存在更小的使得,与m的定义矛盾,因此mk是n,k的最小公倍数。

,可得:,令,则(m与环的起点元素i无关)。环中的元素索引为:,且环中的元素索引无重复。若存在两个索引相同,即:,则存在,与m的定义矛盾,所以一个环中的元素索引无重复。

由于环的长度m与环的起始元素i无关,每条环中的元素个数为,所以共有d个环。下面证明两个环之间的元素索引没有交叉。d个环的起始元素为1,2,……,d。任取两个环,起始元素设为,当两个环有交叉时,即存在:

,则上式右边可整理为:,所以右边整除d而左边,两边相等当且仅当,故两个环之间元素无交叉。则d个长度为的环,覆盖了整个数组的n个元素,轮换完毕。


如上图,对每个环单独处理,进行d次。由于每个元素只交换一次,复杂度为O(n)。代码如下:

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 --){   //处理n个环
        __rotate_cycle(first, last, first + n, middle - first, value_type(first));
    }
}

template<class RandomAccessIterator, class Distance, class T>
void __rotate(RandomAccessIterator first, RandomAccessIterator last, 
              RandomAccessIterator initial, Distance shift, T*){
    T value = *initial;    //O(1)保存环起始元素
    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;    //最后移动起始元素到其正确位置
}


©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页