STLのrotate関数解析
6549 ワード
STLのrotateは実は循環シフトの関数と見なすことができる.
まず、循環シフト操作について以下のいくつかの考え方がある.
1)最も簡単な考え方は,1つ移動するたびに容器全体を循環し,アルゴリズムの複雑さはO(n*m)である.
2)パケット交換(可能な限り配列の前の連続数を所望の結果とする):a長がbより大きい場合、abをa 0 a 1 bに分割し、a 0とbを交換し、ba 1 a 0を得、a 1とa 0を反復的に交換するだけである.アルゴリズムの複雑さはO(n)である.
3)r=abのため,3回の反転操作(reverse)を用いる.アルゴリズムの複雑さはO(n)である.
4)ループ操作チェーンを構築し,チェーン内の要素をループシフトする.アルゴリズムの複雑さはO(n)である.
ここで鎖数はgcd(m,n)である.チェーン内の要素個数はn/gcd(m,n)である.
注:スキーム4は数論の中の1つの小さい定理に関連します:もし2つの正の整数m、nがあって、しかもgcd(m、n)=dがあれば、シーケンス{m%n、2 m%n、3 m%n、...、nm%n}はきっと{0,d,2 d,...,n-d}のある配列でd回繰り返し現れて、その中で%号は型を求める操作を表します.例えば、m=6,n=8,d=gcd(m,n)=2であれば、{6%8,12%8,18%8,...,48%8}は{0,2,4,6}のいずれかの配列で繰り返される.特に、m,n相互素,d=1であれば、配列{m%n,2 m%n,3 m%n,…,(n−1)m%n}は、実際には{1,2,3,…,n−1}のいずれかの配列である.この定理の証明過程は多くの本の中で見つけることができる(例えば具体的な数学4.8節)、ここでは詳しく述べない.
STLのrotate関数は、異なるタイプの反復器に対して異なる効率を有し、3つのバージョン(ForwardIterator,BidirectionalIterator,RandomAccessIterator)に分けられる.
BidirectionIteratorは両端移動の機能があるため、reverse関数を呼び出すことができます.シナリオ3を選択します.
RandomAccessIteratorは+n操作能力を有するため、シナリオ4を選択する.
現在の問題は,後の3つのアルゴリズムの複雑さがO(n)であるのに,なぜ3つの異なるアルゴリズムを用いるのかということである.
以前の問題の解析が可能になり,ForwardIteratorバージョンでは反復が必要であるため,より多くの判断(前後セグメントの終了順序)が導入された.
BidirectionalIteratorバージョンでは、n回の交換を反復する必要はありません.RandomAccessIteratorバージョンとBidirectionalIteratorバージョンの最大の違いは交換方法です.Bバージョンの交換は2つの交換(iter_swap)を用い,Rバージョンの交換はループ割り当てを用いた.したがって、Bバージョンの操作は一度に3回必要です
割り当て操作は、Rバージョンでは1回のみ必要です.Bバージョン複雑度はO(3 tn)、Rバージョン複雑度はO(tn)ともいえる.
まず、循環シフト操作について以下のいくつかの考え方がある.
1)最も簡単な考え方は,1つ移動するたびに容器全体を循環し,アルゴリズムの複雑さはO(n*m)である.
2)パケット交換(可能な限り配列の前の連続数を所望の結果とする):a長がbより大きい場合、abをa 0 a 1 bに分割し、a 0とbを交換し、ba 1 a 0を得、a 1とa 0を反復的に交換するだけである.アルゴリズムの複雑さはO(n)である.
3)r=abのため,3回の反転操作(reverse)を用いる.アルゴリズムの複雑さはO(n)である.
4)ループ操作チェーンを構築し,チェーン内の要素をループシフトする.アルゴリズムの複雑さはO(n)である.
ここで鎖数はgcd(m,n)である.チェーン内の要素個数はn/gcd(m,n)である.
注:スキーム4は数論の中の1つの小さい定理に関連します:もし2つの正の整数m、nがあって、しかもgcd(m、n)=dがあれば、シーケンス{m%n、2 m%n、3 m%n、...、nm%n}はきっと{0,d,2 d,...,n-d}のある配列でd回繰り返し現れて、その中で%号は型を求める操作を表します.例えば、m=6,n=8,d=gcd(m,n)=2であれば、{6%8,12%8,18%8,...,48%8}は{0,2,4,6}のいずれかの配列で繰り返される.特に、m,n相互素,d=1であれば、配列{m%n,2 m%n,3 m%n,…,(n−1)m%n}は、実際には{1,2,3,…,n−1}のいずれかの配列である.この定理の証明過程は多くの本の中で見つけることができる(例えば具体的な数学4.8節)、ここでは詳しく述べない.
STLのrotate関数は、異なるタイプの反復器に対して異なる効率を有し、3つのバージョン(ForwardIterator,BidirectionalIterator,RandomAccessIterator)に分けられる.
BidirectionIteratorは両端移動の機能があるため、reverse関数を呼び出すことができます.シナリオ3を選択します.
RandomAccessIteratorは+n操作能力を有するため、シナリオ4を選択する.
現在の問題は,後の3つのアルゴリズムの複雑さがO(n)であるのに,なぜ3つの異なるアルゴリズムを用いるのかということである.
// (dispatch function)
template <class BidirectionalIterator>
inline void reverse(BidirectionalIterator first, BidirectionalIterator last) {
__reverse(first, last, iterator_category(first));
}
// reverse bidirectional iterator
template <class BidirectionalIterator>
void __reverse(BidirectionalIterator first, BidirectionalIterator last,
bidirectional_iterator_tag) {
while (true)
if (first == last || first == --last) // 2
return;
else
iter_swap(first++, last); //iter_swap()
}
// reverse random access iterator
template <class RandomAccessIterator>
void __reverse(RandomAccessIterator first, RandomAccessIterator last,
random_access_iterator_tag) {
// , first < last , random iterators.
while (first < last) iter_swap(first++, --last);
}
//_copy revese
template <class BidirectionalIterator, class OutputIterator>
OutputIterator reverse_copy(BidirectionalIterator first,
BidirectionalIterator last,
OutputIterator result) {
while (first != last) { //
--last;
*result = *last;
++result; }
return result;
}
// rotate forward iterator
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; // 1
++i;
// [first, middle) [middle,last)
if (first == middle) { //
if (i == last) return; // ,
middle = i; // ,
}
else if (i == last) //
i = middle; // ,
}
}
// rotate bidirectional iterator
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);
}
// ,
// __gcd() __rotate() random access iterator
template <class EuclideanRingElement>
EuclideanRingElement __gcd(EuclideanRingElement m, EuclideanRingElement n)
{
while (n != 0) { //
EuclideanRingElement t = m % n;
m = n;
n = t;
}
return m;
}
// rotate random access iterator
template <class RandomAccessIterator, class Distance>
void __rotate(RandomAccessIterator first, RandomAccessIterator middle,
RandomAccessIterator last, Distance*,
random_access_iterator_tag) {
// RandomAccessIterator
// ( )
Distance n = __gcd(last - first, middle - first);
// gcd(m,n) 。 n/gcd(m,n)。
while (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; //ptr1 “ ”
if (last - ptr2 > shift) //
ptr2 += shift;
else
// ! while(true), break??
ptr2 = first + (shift - (last - ptr2));
}
*ptr1 = value;
}
// (dispatch function)
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));
}
//_copy rotate
template <class ForwardIterator, class OutputIterator>
OutputIterator rotate_copy(ForwardIterator first, ForwardIterator middle,
ForwardIterator last, OutputIterator result) {
return copy(first, middle, copy(middle, last, result));
}
以前の問題の解析が可能になり,ForwardIteratorバージョンでは反復が必要であるため,より多くの判断(前後セグメントの終了順序)が導入された.
BidirectionalIteratorバージョンでは、n回の交換を反復する必要はありません.RandomAccessIteratorバージョンとBidirectionalIteratorバージョンの最大の違いは交換方法です.Bバージョンの交換は2つの交換(iter_swap)を用い,Rバージョンの交換はループ割り当てを用いた.したがって、Bバージョンの操作は一度に3回必要です
割り当て操作は、Rバージョンでは1回のみ必要です.Bバージョン複雑度はO(3 tn)、Rバージョン複雑度はO(tn)ともいえる.