/*
Name: 向量旋轉算法集錦
Copyright: 始發于goal00001111的專欄;允許自由轉載,但必須注明作者和出處
Author: goal00001111
Date: 28-12-08 23:28
Description:
向量旋轉算法:將具有n個元素的向量a向左旋轉r個位置。
例如 :將字符串"abcdefghij"旋轉成"defghjabc",其中n=10,r=3。
其實就是將 AB 轉換成 BA 的過程,這里A ="abc", B="defghij"。
本文總共采用了四種方法來解決向量旋轉問題,依次是:
方法一:最簡單的直接移動向量旋轉算法;
方法二:簡明的的逆置數組旋轉算法;
方法三:傳說中的雜耍旋轉算法;
方法四:一個類似于歐幾里得算法的旋轉算法;
其中方法一需要一個輔助數組,空間復雜度較高;方法二每個元素都要移動兩次,效率相對較低;
方法三和方法四都是極牛的算法,空間和時間復雜度都很低。
這是牛書《編程珠璣》中的一個例題,在書的網站上有詳細的源碼,我把它改成了我所熟悉的樣子。
源碼的網站是:http://www.cs.bell-labs.com/cm/cs/pearls/code.html
*/
#include<iostream>
#include <time.h>
using namespace std;
template <class T> //最簡單的直接移動向量旋轉算法
void SlideRotate(T a[], int n, int r);
template <class T> //逆置數組的原子操作
void Reverse(T a[], int left, int right);
template <class T> //簡明的的逆置數組旋轉算法
void ReverseRotate(T a[], int n, int r);
int Gcd(int m, int n); //歐幾里德算法求最大公約數
template <class T> //傳說中的雜耍旋轉算法
void JuggleRotate(T a[], int n, int r);
template <class T> //交換兩個長度均為len的向量塊a[left_1..left_1+len) 和 a[left_2..left_2+len)
void Swap(T a[], int left_1, int left_2, int len);
template <class T> //一個類似于歐幾里得算法的旋轉算法
void GcdRotate(T a[], int n, int r);
template <class T> //創建一個向量
void InitVector(T a[], int n);
template <class T> //輸出一個向量
void PrintVector(const T a[], int n);
template <class T> //判斷向量旋轉是否成功
bool CheckRotate(const T a[], int n, int r);
int main()
{
const int N = 601; //測試次數
const int MAX = 500000; //向量長度
int a[MAX] = {0};
int rotateDistance = 100000;
time_t startTime, endTime;
////////最簡單的直接移動向量旋轉算法///////////////////////////////
time(&startTime);
InitVector(a, MAX);
// PrintVector(a, MAX);
for (int i=0; i<N; i++)
SlideRotate(a, MAX, rotateDistance);
// PrintVector(a, MAX);
if (CheckRotate(a, MAX, rotateDistance))
cout << "True" << endl;
else
cout << "False" << endl;
time(&endTime);
cout << "time = " << difftime(endTime, startTime) << endl << endl;
///////////////////////////////////////////////////////////////////////////////////////////
//////////簡明的的逆置數組旋轉算法//////////////////////////////////////////////////
time(&startTime);
InitVector(a, MAX);
// PrintVector(a, MAX);
for (int i=0; i<N; i++)
ReverseRotate(a, MAX, rotateDistance);
// PrintVector(a, MAX);
if (CheckRotate(a, MAX, rotateDistance))
cout << "True" << endl;
else
cout << "False" << endl;
time(&endTime);
cout << "time = " << difftime(endTime, startTime) << endl << endl;
///////////////////////////////////////////////////////////////////////////////////////////
////////////傳說中的雜耍旋轉算法 //////////////////////////////////////
time(&startTime);
InitVector(a, MAX);
// PrintVector(a, MAX);
for (int i=0; i<N; i++)
JuggleRotate(a, MAX, rotateDistance);
// PrintVector(a, MAX);
if (CheckRotate(a, MAX, rotateDistance))
cout << "True" << endl;
else
cout << "False" << endl;
time(&endTime);
cout << "time = " << difftime(endTime, startTime) << endl << endl;
///////////////////////////////////////////////////////////////////////////////////////////
/////////////一個類似于歐幾里得算法的旋轉算法///////////////////////////////////
time(&startTime);
InitVector(a, MAX);
// PrintVector(a, MAX);
for (int i=0; i<N; i++)
GcdRotate(a, MAX, rotateDistance);
// PrintVector(a, MAX);
if (CheckRotate(a, MAX, rotateDistance))
cout << "True" << endl;
else
cout << "False" << endl;
time(&endTime);
cout << "time = " << difftime(endTime, startTime) << endl << endl;
///////////////////////////////////////////////////////////////////////////////////////////
system("pause");
return 0;
}
////////////方法一:創建一個長度為min{r, n-r)的輔助數組,以幫助完成旋轉任務//////////////////
/*
函數名稱:SlideRotate
函數功能:最簡單的直接移動向量旋轉算法:先利用一個輔助數組將較短的那一段元素存儲起來,
再移動原向量中未另外存儲的那部分元素,最后將輔助數組中的元素再復制到正確位置
輸入參數:T a[]:需要被旋轉的向量
int n:向量的長度
int r:向量左半段長度
輸出參數:T a[]:旋轉后的向量
返回值:無
*/
template <class T>
void SlideRotate(T a[], int n, int r)
{
if (r < n - r) //如果左半段小于右半段,存儲左半段的元素
{
T *buf = new T[r];
for (int i=0; i<r; i++)//存儲左半段的元素
buf[i] = a[i];
for (int i=r; i<n; i++)//移動右半段的元素
a[i-r] = a[i];
for (int i=0; i<r; i++)//復制左半段的元素到右半段
a[n-r+i] = buf[i];
delete []buf;
}
else //否則存儲右半段的元素
{
T *buf = new T[n-r];
for (int i=r; i<n; i++)//存儲右半段的元素
buf[i-r] = a[i];
for (int i=r-1; i>=0; i--)//移動左半段的元素
a[i+n-r] = a[i];
for (int i=0; i<n-r; i++)//復制右半段的元素到左半段
a[i] = buf[i];
delete []buf;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////方法二:使用一個逆置數組的原子操作////////////////////////////////////
/*
函數名稱:Reverse
函數功能:逆置數組的原子操作
輸入參數:T a[]:需要被逆置的向量
int left: 數組的左界
int right:數組的右界
輸出參數:T a[]:逆置后的數組
返回值:無
*/
template <class T>
void Reverse(T a[], int left, int right)
{
T t;
while (left < right)
{
t = a[left];
a[left] = a[right];
a[right] = t;
left++;
right--;
}
}
/*
函數名稱:ReverseRotate
函數功能:簡明的的逆置數組旋轉算法:構造一個逆置數組的原子操作子函數,然后設置不同的數組左右界,
分三次調用子函數就行了。因為每個元素都需要移動兩次,所以效率不是很高。
輸入參數:T a[]:需要被旋轉的向量
int n:向量的長度
int r:向量左半段長度
輸出參數:T a[]:旋轉后的向量
返回值:無
*/
template <class T>
void ReverseRotate(T a[], int n, int r)
{
Reverse(a, 0, r-1); //逆置左半段數組
Reverse(a, r, n-1); //逆置右半段數組
Reverse(a, 0, n-1); //逆置整段數組
}
////////////////////////////////////////////////////////////////////////////////////////////////
//////////////方法三:傳說中的雜耍旋轉算法////////////////////////////////////////////////
/*
函數名稱:Gcd
函數功能:歐幾里德算法求最大公約數
輸入參數:int m:正整數之一
int n:正整數之二
輸出參數:無
返回值:正整數m和n的最大公約數
*/
int Gcd(int m, int n)
{
int t;
while (m > 0)
{
t = n % m;
n = m;
m = t;
}
return n;
}
/*
函數名稱:JuggleRotate
函數功能:傳說中的雜耍旋轉算法:
先將a[0]存儲到臨時變量t中,然后將a[r]移動到a[0],將a[2r] 移動到 a[r],
依此類推(數組中所有的下標都要對數組的長度n取模),直到(k*r)%n == 0,
此時我們不能在a[0]中取數,而是在臨時變量t中取數,然后結束該過程。
如果該過程不能移動所有的元素,那么我們從a[1]開始移動,一直依次下去,
直到移動了所有的元素為止。
那么總共要重復開始移動幾次呢?數學證明是Gcd(r, n)次。
此算法每個元素只需移動一次就可以到達正確位置,理論上效率是最高的,
但由于要做求最大公約數和求余運算等準備工作,所以沒有顯示出優勢。
輸入參數:T a[]:需要被旋轉的向量
int n:向量的長度
int r:向量左半段長度
輸出參數:T a[]:旋轉后的向量
返回值:無
*/
template <class T>
void JuggleRotate(T a[], int n, int r)
{
int i, j, k;
int cyc = Gcd(r, n); //用r和n的最大公約數作為循環周期
for (i=0; i<cyc; i++) //總共需要重復開始移動cyc次,才能使得所有的元素都移動到正確位置
{
T t = a[i]; //臨時變量t存儲a[i]
j = i;
while (1)//依次移動元素,直到 (k*r)%n == 0
{
k = j + r;
if (k >= n) //用除法運算替代模運算
k -= n;
if (k == i)
break;
a[j] = a[k];
j = k;
}
a[j] = t;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
//////////////方法四:一個類似于歐幾里得輾轉相除算法的旋轉算法/////////////////////////////////
/*
函數名稱:Swap
函數功能:交換兩個長度均為len的向量塊a[left_1..left_1+len) 和 a[left_2..left_2+len)
輸入參數:T a[]:需要被處理的向量
int left_1:向量塊a[left_1..left_1+len)的左界
int left_2:向量塊a[left_2..left_2+len)的左界
int len: 兩個向量塊的長度
輸出參數:T a[]:交換了部分元素的向量
返回值:無
*/
template <class T>
void Swap(T a[], int left_1, int left_2, int len)
{
T t;
while (len > 0)
{
t = a[left_1];
a[left_1] = a[left_2];
a[left_2] = t;
left_1++;
left_2++;
len--;
}
}
/*
函數名稱:JuggleRotate
函數功能:一個類似于歐幾里得輾轉相除算法的旋轉算法:
就像一個做阻尼振動的彈簧振子一樣,按照由兩邊到中間的順序,整段的交換向量塊,
并且被交換的向量塊長度不斷縮減,直到lLen == rLen。
由于重復移動的元素較少,所以效率比逆置數組旋轉算法要高。
輸入參數:T a[]:需要被旋轉的向量
int n:向量的長度
int r:向量左半段長度
輸出參數:T a[]:旋轉后的向量
返回值:無
*/
template <class T>
void GcdRotate(T a[], int n, int r)
{
if (r == 0 || r == n) //特殊情況不用旋轉
return;
int lLen, rLen, pos;
lLen = pos = r;
rLen = n - r;
while (lLen != rLen)
{
if (lLen > rLen) //左半段大于右半段,移動右半段
{
Swap(a, pos-lLen, pos, rLen);
lLen -= rLen; //減少移動范圍
}
else
{
Swap(a, pos-lLen, pos+rLen-lLen, lLen);
rLen -= lLen;
}
}
Swap(a, pos-lLen, pos, lLen); //最后交換中間段
}
////////////////////////////////////////////////////////////////////////////////////////////////
/*
函數名稱:InitVector
函數功能:創建一個向量
輸入參數:T a[]:需要被賦值的向量
int n:向量的長度
輸出參數:T a[]:創建好的向量
返回值:無
*/
template <class T>
void InitVector(T a[], int n)
{
for (int i=0; i<n; i++) //創建一個向量
a[i] = i;
}
/*
函數名稱:PrintVector
函數功能:輸出一個向量
輸入參數:const T a[]:需要被輸出的向量
int n:向量的長度
輸出參數:無
返回值:無
*/
template <class T>
void PrintVector(const T a[], int n)
{
for (int i=0; i<n; i++)
cout << a[i] << ' ';
cout << endl;
}
/*
函數名稱:CheckRotate
函數功能:判斷向量旋轉是否成功
輸入參數:T a[]:需要被旋轉的向量
int n:向量的長度
int r:向量左半段長度
輸出參數:無
返回值:旋轉成功返回true,否則返回false
*/
template <class T>
bool CheckRotate(const T a[], int n, int r)
{
for (int i=0; i<n-r; i++) //判斷左半段
{
if (a[i] != i+r)
return false;
}
for (int i=0; i<r; i++)//判斷右半段
{
if (a[n-r+i] != i)
return false;
}
return true;
}