天天看點

c++ 向量的值逆序輸出_C++實作一維向量旋轉算法

這篇文章主要介紹了C++實作一維向量旋轉算法,非常實用的經典算法,需要的朋友可以參考下

在《程式設計珠玑》一書的第二章提到了n元一維向量旋轉算法(又稱數組循環移位算法)的五種思路,并且比較了它們在時間和空間性能上的差別和優劣。本文将就這一算法做較為深入的分析。具體如下所示:

一、問題描述

将一個n元一維向量向左旋轉i個位置。例如,假設n=8,i=3,向量abcdefgh旋轉為向量defghabc。簡單的代碼使用一個n元的中間向量在n步内可完成該工作。你能否僅使用幾十個額外位元組的記憶體空間,在正比于n的時間内完成向量的旋轉?

二、解決方案

思路一:将向量x中的前i個元素複制到一個臨時數組中,接着将餘下的n-i個元素左移i個位置,然後再将前i個元素從臨時數組中複制到x中餘下的位置。

性能:這種方法使用了i個額外的位置,如果i很大則産生了過大的存儲空間的消耗。

C++代碼實作如下:

#include

#include

using namespace std;

int main()

{

string s = "abcdefghijklmn";

cout << "The origin is: " << s << endl;

// 左移個數

int i;

cin >> i;

if(i > s.size())

{

i = i%s.size();

}

// 将前i個元素臨時儲存

string tmp(s, 0, i);

// 将剩餘的左移i個位置

for(int j=i; j

{

s[j-i] = s[j];

}

s = s.substr(0, s.size()-i) + tmp;

cout << "The result is: "<< s << endl;

return 0;

}

思路二:定義一個函數将x向左旋轉一個位置(其時間正比于n),然後調用該函數i次。

性能:這種方法雖然空間複雜度為O(1),但産生了過多的運作時間消耗。

C++代碼實作如下:

#include

#include

using namespace std;

void rotateOnce(string &s)

{

char tmp = s[0];

int i;

for(i=1; i

{

s[i-1] = s[i];

}

s[i-1] = tmp;

}

int main()

{

string s = "abcdefghijklmn";

cout << "The origin is: " << s << endl;

// 左移個數

int i;

cin >> i;

if(i > s.size())

{

i = i%s.size();

}

// 調用函數i次

while(i--)

{

rotateOnce(s);

}

cout << "The result is: "<< s << endl;

return 0;

}

思路三:移動x[0]到臨時變量t中,然後移動x[i]到x[0]中,x[2i]到x[i],依次類推,直到我們又回到x[0]的位置提取元素,此時改為從臨時變量t中提取元素,然後結束該過程(當下标大于n時對n取模或者減去n)。如果該過程沒有移動全部的元素,就從x[1]開始再次進行移動,總共移動i和n的最大公約數次。

性能:這種方法非常精巧,像書中所說的一樣堪稱巧妙的雜技表演。空間複雜度為O(1),時間複雜度為線性時間,滿足問題的性能要求,但還不是最佳。

C++代碼實作如下:

#include

#include

using namespace std;

// 歐幾裡德(輾轉相除)算法求最大公約數

int gcd(int i, int j)

{

while(1)

{

if(i > j)

{

i = i%j;

if(i == 0)

{

return j;

}

}

if(j > i)

{

j = j%i;

if(j == 0)

{

return i;

}

}

}

}

int main()

{

string s = "abcdefghijklmn";

cout << "The origin is: "<< s << endl;

// 左移個數

int i;

cin >> i;

if(i > s.size())

{

i = i%s.size();

}

// 移動

char tmp;

int times = gcd(s.size(), i);

for(int j=0; j

{

tmp = s[j];

int pre = j; // 記錄上一次的位置

while(1)

{

int t = pre+i;

if(t >= s.size())

t = t-s.size();

if(t == j) // 直到tmp原來的位置j為止

break;

s[pre] = s[t];

pre = t;

}

s[pre] = tmp;

}

cout << "The result is: "<< s << endl;

return 0;

}

思路四:旋轉向量x實際上就是交換向量ab的兩段,得到向量ba,這裡a代表x的前i個元素。假設a比b短。将b分割成bl和br,使br的長度和a的長度一樣。交換a和br,将ablbr轉換成brbla。因為序列a已在它的最終位置了,是以我們可以集中精力交換b的兩個部分了。由于這個新問題和原先的問題是一樣的,是以我們以遞歸的方式進行解決。這種方法可以得到優雅的程式,但是需要巧妙的代碼,并且要進行一些思考才能看出它的效率足夠高。

//實作代碼(略)

思路五:(最佳)将這個問題看做是把數組ab轉換成ba,同時假定我們擁有一個函數可以将數組中特定部分的元素逆序。從ab開始,首先對a求逆,得到arb,然後對b求逆,得到arbr。最後整體求逆,得到(arbr)r,也就是ba。

reverse(0, i-1)

reverse(i, n-1)

reverse(0, n-1)

性能:求逆序的方法在時間和空間上都很高效,而且代碼非常簡短,很難出錯。

C++代碼實作如下:

#include

#include

using namespace std;

void reverse(string &s, int begin, int end)

{

while(begin < end)

{

char tmp = s[begin];

s[begin] = s[end];

s[end] = tmp;

++begin;

--end;

}

}

int main()

{

string s = "abcdefghijklmn";

cout << "The origin is: "<< s << endl;

int i;

cin >> i;

if(i > s.size())

{

i = i%s.size();

}

reverse(s, 0, i-1);

reverse(s, i, s.size()-1);

reverse(s, 0, s.size()-1);

cout << "The result is: "<< s << endl;

return 0;

}

三、擴充延伸

如何将向量abc旋轉變成cba?

和前面的問題類似,此向量旋轉對應着非相鄰記憶體塊的交換模型。解法很相似,即利用恒等式:cba = (arbrcr)r

注意:在面試或筆試時,如若出現向量旋轉(記憶體塊交換)問題,建議最好使用思路五答題,不僅高效而且簡潔。