STL中rotate演算法之_rotate_cycle函數詳解

來源:互聯網
上載者:User

這裡的rotate操作,也就是指迴圈移位。比如將串“ABCDEFG”以D為中心旋轉,就相當將該串向左迴圈移位,直到第一個元素為D為止,最後 得到新串“DEFGABC”。


要想方便的完成rotate操作,一個常見的技巧是這樣的:先將前半部分反轉,再將後半部分反轉,最後再將整個串反轉即可 (這裡的前半部分與後半部分是以旋轉中心來劃分的)。還是以串“ABCDEFG”以D為中心旋轉為例,以D為分割點,將先半部分與後半部分分別反轉後,得 “CBAGFED”,最後將整個串反轉即得“DEFGABC”。


這個演算法在很多書上都提到過,相信大家一定都很熟悉了。那麼其效率如何呢,假設串的長度為 t,那麼完成整個旋轉過程需要約t次swap操作,也即是說需要大概3t次賦值,同時只需要常量的臨時空間。因為其實現簡單,所以還是差強人意。所以 SGI STL在處理雙向迭代器容器時也正是使用了該演算法。


但是進一步觀察STL源碼可以發現,在處理儲存在擁有隨機訪問能力的容器中的串時,SGI STL卻是採用了另外一種演算法,而這個演算法的原理在《STL源碼剖析》中恰恰被hjj無視了,所以我在這裡再簡單地梳理一下。

首先,先帖出這個演算法的原始碼:

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 -- )          __rotate_cycle(first, last, frist  +  n, middle  -  first, value_type(first)); } template < class  EuclideanRingElement >  EuclideanRingElement __gcd(EuclideanRingElement m, EuclideanRingElement n) {      while  (n  !=   0 )      {        EuclideanRingElement t  =  m  %  n;        m  =  n;        n  =  t;      }      return  m; } 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; }


上面只涉及到三個函數:__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節),這裡不再詳述。

瞭解這個引理後,就很容易看出__rotate函數的內涵了。若第一步求得的最大公約數n為1,那麼只需一次__rotate_cycle就可以遍 曆到所有的元素,並將每個元素正確的替換為其後相距某個距離的元素,於是也就完成了迴圈左移操作。若n大於1,那麼每一次__rotate_cycle只 能將t/n的元素正確的左移,其中t為串的總長度,而這些被移動的元素是以d為等間距的,所以迴圈n次,並分別以串的前n個元素為起點進行 __rotate_cycle操作,就能保證將所有的元素都移動到正確的位置上。


在這個新的演算法中,每次__rotate_cycle需要t/n+1次賦值,n次迴圈,所以總共只需要t+n次賦值操作,顯然是要比前面所說的三次反轉的演算法快上許多。


比如考慮當串a = { 1, 2, 3, 4, 5} 迴圈左移2位,即期望得到串{ 3, 4, 5, 1, 2},那麼該演算法的賦值過程如下:

tmp = a[0] -> tmp = 1 
a[0] = a[2] ->{ 3, 2, 3, 4, 5} 
a[2] = a[4] ->{ 3, 2, 5, 4, 5} 
a[4] = a[1] ->{ 3, 2, 5, 4, 2} 
a[1] = a[3] ->{ 3, 4, 5, 4, 2} 
a[3] = tmp ->{ 3, 4, 5, 1, 2}

這裡因為2與5互素,所以6次賦值就已搞定,而如果是用三次翻轉的演算法則需要大約3*5次賦值(實際上為12次)。

 

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.