C++實(shí)現(xiàn)一維向量旋轉(zhuǎn)算法
在《編程珠璣》一書的第二章提到了n元一維向量旋轉(zhuǎn)算法(又稱數(shù)組循環(huán)移位算法)的五種思路,并且比較了它們在時(shí)間和空間性能上的區(qū)別和優(yōu)劣。本文將就這一算法做較為深入的分析。具體如下所示:
一、問題描述
將一個n元一維向量向左旋轉(zhuǎn)i個位置。例如,假設(shè)n=8,i=3,向量abcdefgh旋轉(zhuǎn)為向量defghabc。簡單的代碼使用一個n元的中間向量在n步內(nèi)可完成該工作。你能否僅使用幾十個額外字節(jié)的內(nèi)存空間,在正比于n的時(shí)間內(nèi)完成向量的旋轉(zhuǎn)?
二、解決方案
思路一:將向量x中的前i個元素復(fù)制到一個臨時(shí)數(shù)組中,接著將余下的n-i個元素左移i個位置,然后再將前i個元素從臨時(shí)數(shù)組中復(fù)制到x中余下的位置。
性能:這種方法使用了i個額外的位置,如果i很大則產(chǎn)生了過大的存儲空間的消耗。
C++代碼實(shí)現(xiàn)如下:
/*************************************************************************
> File Name: vector_rotate.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s = "abcdefghijklmn";
cout << "The origin is: " << s << endl;
// 左移個數(shù)
int i;
cin >> i;
if(i > s.size())
{
i = i%s.size();
}
// 將前i個元素臨時(shí)保存
string tmp(s, 0, i);
// 將剩余的左移i個位置
for(int j=i; j<s.size(); ++j)
{
s[j-i] = s[j];
}
s = s.substr(0, s.size()-i) + tmp;
cout << "The result is: "<< s << endl;
return 0;
}
思路二:定義一個函數(shù)將x向左旋轉(zhuǎn)一個位置(其時(shí)間正比于n),然后調(diào)用該函數(shù)i次。
性能:這種方法雖然空間復(fù)雜度為O(1),但產(chǎn)生了過多的運(yùn)行時(shí)間消耗。
C++代碼實(shí)現(xiàn)如下:
/*************************************************************************
> File Name: vector_rotate_1.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<string>
using namespace std;
void rotateOnce(string &s)
{
char tmp = s[0];
int i;
for(i=1; i<s.size(); ++i)
{
s[i-1] = s[i];
}
s[i-1] = tmp;
}
int main()
{
string s = "abcdefghijklmn";
cout << "The origin is: " << s << endl;
// 左移個數(shù)
int i;
cin >> i;
if(i > s.size())
{
i = i%s.size();
}
// 調(diào)用函數(shù)i次
while(i--)
{
rotateOnce(s);
}
cout << "The result is: "<< s << endl;
return 0;
}
思路三:移動x[0]到臨時(shí)變量t中,然后移動x[i]到x[0]中,x[2i]到x[i],依次類推,直到我們又回到x[0]的位置提取元素,此時(shí)改為從臨時(shí)變量t中提取元素,然后結(jié)束該過程(當(dāng)下標(biāo)大于n時(shí)對n取?;蛘邷p去n)。如果該過程沒有移動全部的元素,就從x[1]開始再次進(jìn)行移動,總共移動i和n的最大公約數(shù)次。
性能:這種方法非常精巧,像書中所說的一樣堪稱巧妙的雜技表演??臻g復(fù)雜度為O(1),時(shí)間復(fù)雜度為線性時(shí)間,滿足問題的性能要求,但還不是最佳。
C++代碼實(shí)現(xiàn)如下:
/*************************************************************************
> File Name: vector_rotate_2.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<string>
using namespace std;
// 歐幾里德(輾轉(zhuǎn)相除)算法求最大公約數(shù)
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;
// 左移個數(shù)
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<times; ++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;
}
思路四:旋轉(zhuǎn)向量x實(shí)際上就是交換向量ab的兩段,得到向量ba,這里a代表x的前i個元素。假設(shè)a比b短。將b分割成bl和br,使br的長度和a的長度一樣。交換a和br,將ablbr轉(zhuǎn)換成brbla。因?yàn)樾蛄衋已在它的最終位置了,所以我們可以集中精力交換b的兩個部分了。由于這個新問題和原先的問題是一樣的,所以我們以遞歸的方式進(jìn)行解決。這種方法可以得到優(yōu)雅的程序,但是需要巧妙的代碼,并且要進(jìn)行一些思考才能看出它的效率足夠高。
//實(shí)現(xiàn)代碼(略)
思路五:(最佳)將這個問題看做是把數(shù)組ab轉(zhuǎn)換成ba,同時(shí)假定我們擁有一個函數(shù)可以將數(shù)組中特定部分的元素逆序。從ab開始,首先對a求逆,得到arb,然后對b求逆,得到arbr。最后整體求逆,得到(arbr)r,也就是ba。
reverse(0, i-1) /*cbadefgh*/ reverse(i, n-1) /*cbahgfed*/ reverse(0, n-1) /*defghabc*/
性能:求逆序的方法在時(shí)間和空間上都很高效,而且代碼非常簡短,很難出錯。
C++代碼實(shí)現(xiàn)如下:
/*************************************************************************
> File Name: vector_rotate.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<string>
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;
}
三、擴(kuò)展延伸
如何將向量abc旋轉(zhuǎn)變成cba?
和前面的問題類似,此向量旋轉(zhuǎn)對應(yīng)著非相鄰內(nèi)存塊的交換模型。解法很相似,即利用恒等式:cba = (arbrcr)r
注意:在面試或筆試時(shí),如若出現(xiàn)向量旋轉(zhuǎn)(內(nèi)存塊交換)問題,建議最好使用思路五答題,不僅高效而且簡潔。
相關(guān)文章
C語言實(shí)現(xiàn)時(shí)間戳轉(zhuǎn)日期的算法(推薦)
下面小編就為大家?guī)硪黄狢語言實(shí)現(xiàn)時(shí)間戳轉(zhuǎn)日期的算法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-06-06
C語言數(shù)據(jù)結(jié)構(gòu)之雙鏈表&循環(huán)鏈表&靜態(tài)鏈表詳解
這篇文章主要為大家詳細(xì)介紹了C語言數(shù)據(jù)結(jié)構(gòu)中雙鏈表&循環(huán)鏈表&靜態(tài)鏈表的原理與使用,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-09-09
C語言實(shí)現(xiàn)關(guān)機(jī)小程序
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)關(guān)機(jī)小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
C++數(shù)據(jù)結(jié)構(gòu)之哈希算法詳解
這篇文章主要為大家詳細(xì)介紹了C++數(shù)據(jù)結(jié)構(gòu)中哈希算法的相關(guān)資料,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,希望對大家有所幫助2022-12-12
Dijkstra算法最短路徑的C++實(shí)現(xiàn)與輸出路徑
今天小編就為大家分享一篇關(guān)于Dijkstra算法最短路徑的C++實(shí)現(xiàn)與輸出路徑,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02

