算法要求:試設(shè)計(jì)一個(gè)算法,將數(shù)組A[0..n-1]中的元素循環(huán)右移K位,并要求只用一個(gè)元素大小的存儲(chǔ)
空間。元素移動(dòng)或交換的次數(shù)為O(n)
【分析】:本題的應(yīng)用環(huán)境很廣泛,在實(shí)用中有很重要意義,如:實(shí)現(xiàn)高階乘除法,在游戲選關(guān)和各種
特效中經(jīng)常用到。目前手機(jī)等移動(dòng)設(shè)備應(yīng)用開發(fā)方興未艾,對此常用算法做一個(gè)深入的探討很有必要。
作者: 天津大學(xué)計(jì)算機(jī)系 常興龍 MSN:cxl82116@msn.com
本題的難點(diǎn)在于同時(shí)對空間復(fù)雜度與時(shí)間復(fù)雜度都做了很高的要求,但就本題來講,仍有很多種解
法。這里,探討常見的三種解法:
1.由于每個(gè)元素,跟與它相距r×k位形成了一個(gè)“循環(huán)”,因此算法保證每次都有一至
二個(gè)元素正確歸位,設(shè)一個(gè)計(jì)數(shù)變量,當(dāng)計(jì)數(shù)變量到達(dá)n時(shí),算法結(jié)束
2.對1解法求精,1解中可能使一個(gè)元素多于一次的移動(dòng),但如果找到n與k的最大公約數(shù)
,就可以解決此問題,從而保證每個(gè)元素只移動(dòng)一次。
3.第三種思想有一定的技巧,直觀理解比較困難,但手有有一副撲克牌,就能清楚地看
到整個(gè)過程(把整個(gè)排列看成是一個(gè)環(huán)形)。它的算法是:先逆轉(zhuǎn)前n-k個(gè)元素,再逆轉(zhuǎn)后K個(gè)元素,再
把整個(gè)序列逆轉(zhuǎn)。
void EMove(int A[n] , int k)


{
for(int i = 0 ; i < n;) //i為到位的元素個(gè)數(shù),做為算法出口

{

/**//**//**//* t是需要到位的元素的靜態(tài)指針 s 指向下一個(gè)K步到達(dá)的位置 ,
r初值為1 ,在每一循環(huán)圈中置初值一次,做為步進(jìn)值*/
s = ( t + r * k ) % n ;
A[t]<-->A[s]; //用A[t]為緩沖空間 , 使此鏈上一個(gè)記錄到位,到位位置的記錄緩存到緩沖區(qū)中
r++ ; i++;
s = ( t +r *k) %n;
if(s == t) //使兩個(gè)記錄到位,一圈結(jié)束

{
t++; //選擇親的起始點(diǎn)
r=1;
i++;
}//if
}//for
}
[解二]
void EMove(int A[n] , int k)


{
for ( int i = 1; i <= k; i++ )
if ( n%i == 0 && k%i ==0 ) p = j;

/**//**//**//*上面為求最大公約數(shù)的O(n)寫法之一,還可以這么寫:
int p= int(); //這也是一個(gè)C++新奇的寫法,int = 0,稍用一下構(gòu)造函數(shù)
while( n !=0 && k !=0)
{
n>k ? n%=k:k%=n;
}
p = n==0?k:n;
p為n與k的最大公約數(shù)
比較性能:注釋中的log(N)性能更好,但是破壞原來數(shù)據(jù),
如果保留原數(shù)據(jù),輾轉(zhuǎn)一下,得不償失,這也是棄用的理由
*/
for ( i = 0 ; i < p ; i++) //最大公約數(shù),實(shí)質(zhì)為保證每個(gè)元素都到位的子圈數(shù)目

{ //極限情況,若 n與k最大公約為1,則一圈就能成功,無需另先起點(diǎn)
j = i;
m = ( i + k ) % n;
temp = A[i]; //緩存
while( m != i)

{
//利用A[j],交換 temp 與A[m]的值
//A[j]為本次要到位的元素
//A[m]存本次己位的元素
//temp 緩存下次要到位的元素
A[j] = temp ;
temp = A[m] ;
A[m] = A[j] ;
//前進(jìn)一步
j = m ;
m = ( j + k ) % n ;
}//while
}//for
}//Emove

[解三]
void Emove ( int A[n] , int k )


{
for ( i=0 ; i < ( n - k ) / 2 ; i++ )

{
A[i]<-->A[ n-k-1-i];
}//逆轉(zhuǎn)前n-k
for( i = n - k ; i < ( 2*n - k) /2 ; i++)

{
A[n-k+i]<-->A[k-1+i];
} //逆轉(zhuǎn)后K項(xiàng)
for ( i = 0 ; i < n/2 ; i++ )

{
A[i]<-->A[n-1-i];
}//逆轉(zhuǎn)整個(gè)鏈
}
補(bǔ)充:
[1]反轉(zhuǎn)鏈的下標(biāo)確定方法,可以通俗的描寫
從 『鏈開始下標(biāo)』到 (『鏈終止下標(biāo)』-『鏈開始下標(biāo)』)/2 ,反復(fù)做
交換 『鏈開始下標(biāo)+i』,『鏈終止下標(biāo)-i』
[2]定義符號(hào)<-->為
Swap( int & i , int & j )


{
int temp = i ;
i = j ;
j = temp
}

關(guān)于交換算法,還有一種線性代數(shù)矩陣的寫法
//交換x,y
Swap( int & x , int & y )


{
x=x+y;
y=x-y;
x=x-y;
}
【思考】如果把本題擴(kuò)充一下,改為把A中的一個(gè)子序列擴(kuò)右移K位(其余要求不變)的算法?(進(jìn)一步
要求能夠處理K>子序列長度的情況?)(下次提供答案)