《stl源码解析》中rotate函数为了追求效率,将其分了三种情况进行讨论:forwardIterator, bidirectionalIterator, randomInterator
forwardIterator:
template <class ForwardIterator, class Distance>
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)bidirectional iterator
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)random Iterator
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--)
// 需要执行__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;
}
如下的m个数:0 mod m, n mod m, 2n mod m, ... , (m - 1)n mod m 正好包含d次的m/d个数:0,d,2d,..., m - d,其中d = gcd(m, n)。比如当m = 12, n = 8时,d = 4,这个排列就为0,8,4,0,8,4,0,8,4,0,8,4
从数学意义上将要将一个数组进行rotate,每个元素应该满足如下公式:
i := (i - len(A) + len(X)) % len(X) (1)
或者是:
i := (i + len(B)) % len(X) (2)
其中A表示数组前段,B表述数组后段,X表示整个数组。
从第一个公式看,实际上就是将每个元素往前移len(A)个位置。根据定理1可知,移动了m / d个元素后就回到原点了,这时候需要再选择下一个未移动的数作为起始点。所以一共需要有d次的移动才能达到m个数全部移动一遍。
这三个算法的效率粗略地说都是O(n)算法。
forward iterator大致需要进行n次swap操作,可看成是O(3n),但是在其循环体内还有一些判断。
bidirectional iterator也需要n次swap操作,可看成是O(3n),除了循环条件外没有判断。
random iterator只需要n次赋值即可,可看成是O(n)。
赋值比交换快。。
最后感慨一下stl为了效率真是无所不用其极。
参考文献:
[1] http://blog.chinaunix.net/uid-10647744-id-3274208.html
[2] http://blog.csdn.net/sicofield/article/details/8730197