STL原始碼之rotate函式結合圖和例項分析
今天看 STL 原始碼看到 rotate() 函式這一塊,該函式就是將 [first, middle) 的元素和 [middle, last) 的元素互換。middle 的元素會成為容器的第一個元素。如果有個數字序列 {1, 2, 3, 4, 5, 6, 7},對元素 3 做旋轉操作,會形成 {3, 4, 5, 6, 7, 1, 2}。其實這就是我們平時說的左旋轉字串,只不過泛型化了而已。它可以旋轉的內容不止字串,其他迭代器型別都可以。
三種方法的分析:
演算法1(分組交換):(來自網友:雁過無痕)
若a長度大於b,將ab分成a0a1b,交換a0和b,得ba1a0,只需再交換a1 和a0。若a長度小於b,將ab分成ab0b1,交換a和b0,得b0ab1,只需再交換a 和b1。不斷將陣列劃分和交換,直到不能再劃分為止。分組過程與求最大公約數很相似。
程式碼如下:
emplate <class ForwardIterator, class Distance>
// Distance型別僅僅對於random iterator的實現版本有意義,但為了便於上層程式碼便於呼叫,所以使用// 了同樣的簽名。
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 (三次反轉)
利用ba=(br)r(ar)r=(arbr)r,先分別反轉a、b,最後再對所有元素進行一次反轉。
程式碼如下:
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 (使用gcd)(分析來自網友:陳覃)
__gcd是求兩個數的最大公約數,也是迴圈位移的遍數。
舉個例子來說明演算法過程,陣列123456789,把123翻轉到右邊,*first=1,*last=9,*middle=4;
要旋轉字串(123)的長度為3,字串長度為9,3和9的最大公約數為3,因此需要翻轉3遍;
第一遍從*(initial+shift)=6開始,6移到3的位置,9移到6的位置,下一個位置是ptr2 = first + (shift - (last - ptr2))=0+(3-(8-8))=3,不滿足ptr2 != initial的條件,退出迴圈,然後*ptr1 = value,即把數字3移動到數字9的位置,從而完成了3,6,9三個數字的位移,下面的2遍迴圈則分別完成2,5,8和1,4,76個數字的位移,最後得到最終結果456789123。
對於輾轉相除法更詳細的證明可以參考我以前的部落格:輾轉相除法、埃拉托色尼篩選法、牛頓迭代法證明與C++實現
整個演算法過程可用下圖表示:
程式碼如下:
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--) //注意這裡是n--,我因為沒看見這個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;
}
template <class EuclideanRingElement>
EuclideanRingElement __gcd(EuclideanRingElement m, EuclideanRingElement n)
{
while (n != 0) {
EuclideanRingElement t = m % n;
m = n;
n = t;
}
return m;
}
由於前兩種比較簡單,在這裡我僅實現了第三種作為練習,一次性AC:)
#include <iostream>
#include <assert.h>
int calc_gcd(int m, int n)
{
if(m < n)
std::swap(m, n);
if(n == 0)
return m;
calc_gcd(n, m%n);
}
template <typename T>
void cycle_rotate(T *arr, int *first, int *last, int *initial, int rotate_num)
{
T value = *initial;
T *ptr1 = initial, *ptr2 = ptr1 + rotate_num;
while(ptr2 != initial){
*ptr1 = *ptr2;
ptr1 = ptr2;
if(last - ptr2 >= rotate_num) //可以等於,因為是下標
ptr2 += rotate_num;
else
ptr2 = first + (rotate_num - (last - ptr2)) - 1; //注意要減一,因為我這裡用的是下標
}
*ptr1 = value;
}
template <typename T>
void rotate(T* arr, int start, int end, int rotate_num)
{
assert(start >= 0 && rotate_num > start && rotate_num <= end+1);
int gcd = calc_gcd(end-start+1, rotate_num);
while(gcd--)
cycle_rotate(arr, arr+start, arr+end, arr+start+gcd, rotate_num);
}
int main()
{
int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int len = sizeof(array) / sizeof(int);
rotate(array, 0, len-1, 5);
for(auto i : array)
std::cout<<i<<' ';
std::cout<<std::endl;
return 0;
}
輸出:
相關文章
- canvas translate()、scale()和rotate()方法程式碼例項Canvas
- C++(STL原始碼):37---仿函式(函式物件)原始碼剖析C++原始碼函式物件
- Spring 原始碼分析之 bean 例項化原理Spring原始碼Bean
- redux原始碼分析之四:compose函式Redux原始碼函式
- stl原始碼分析——map/multimap原始碼
- count 函式原始碼分析函式原始碼
- Hive 分析函式lead、lag例項應用Hive函式
- Python簡單函式迴圈綜合例項Python函式
- 函式計算支援 MySQL 例項繫結函式MySql
- 結合例項學習|字元編碼和解碼字元
- 例項物件和函式物件的區別物件函式
- Python 入門之經典函式例項(二)Python函式
- 抽象結合例項 Employee抽象
- 【spring 原始碼】IOC 之bean例項的建立Spring原始碼Bean
- Spring原始碼分析(二)bean的例項化和IOC依賴注入Spring原始碼Bean依賴注入
- 遞迴函式例項大全遞迴函式
- PE結構-合併節(附例項程式碼)
- Dubbo原始碼分析(一)Dubbo與Spring整合例項原始碼Spring
- 學習JUC原始碼(3)——Condition等待佇列(原始碼分析結合圖文理解)原始碼佇列
- 讀 zepto 原始碼之工具函式原始碼函式
- 成品直播原始碼,例項原始碼系列-更改圖片透明度原始碼
- Room是怎樣和LiveData結合使用的?(原始碼分析)OOMLiveData原始碼
- [原始碼解析] PyTorch 分散式(17) --- 結合DDP和分散式 RPC 框架原始碼PyTorch分散式RPC框架
- Netty原始碼解析8-ChannelHandler例項之CodecHandlerNetty原始碼
- Mybatis原始碼分析(三)通過例項來看typeHandlersMyBatis原始碼
- ThreadLocal原始碼和圖文分析thread原始碼
- 【spring原始碼學習】Spring @PostConstruct和@PreDestroy例項Spring原始碼Struct
- 淺析stl仿函式函式
- 結構體三種例項化方法(含成員函式)結構體函式
- 前端建構函式、原型、例項物件之間的關係前端函式原型物件
- Spring原始碼分析(六)SpringAOP例項及標籤的解析Spring原始碼
- Spring Ioc原始碼分析系列--Bean例項化過程(一)Spring原始碼Bean
- Spring Ioc原始碼分析系列--Bean例項化過程(二)Spring原始碼Bean
- Vue原始碼解析:Vue例項Vue原始碼
- Dart語法篇之集合操作符函式與原始碼分析(三)Dart函式原始碼
- PHP原始碼分析-函式array_merge的”BUG”PHP原始碼函式
- Python函式每日一講 - frozenset集合函式入門及例項Python函式
- [8]elasticsearch原始碼深入分析——Node與NodeEnvironment的例項化Elasticsearch原始碼
- Java 容器系列(七):HashMap 原始碼分析01之建構函式、內部類JavaHashMap原始碼函式