1. 程式人生 > >1008 陣列元素迴圈右移問題 ——C及C++實現

1008 陣列元素迴圈右移問題 ——C及C++實現

題目

1008 陣列元素迴圈右移問題 (20 point(s))

一個數組A中存有N(>0)個整數,在不允許使用另外陣列的前提下,將每個整數迴圈向右移M(≥0)個位置,即將A中的資料由(A​0​​A​1​​⋯A​N−1​​)變換為(A​N−M​​⋯A​N−1​​A​0​​A​1​​⋯A​N−M−1​​)(最後M個數迴圈移至最前面的M個位置)。如果需要考慮程式移動資料的次數儘量少,要如何設計移動的方法?

輸入格式:

每個輸入包含一個測試用例,第1行輸入N(1≤N≤100)和M(≥0);第2行輸入N個整數,之間用空格分隔。

輸出格式:

在一行中輸出迴圈右移M位以後的整數序列,之間用空格分隔,序列結尾不能有多餘空格。

輸入樣例:

6 2
1 2 3 4 5 6

輸出樣例:

5 6 1 2 3 4

演算法

此類題解題的通法

這道題屬於模擬運動與計算的題目,是一類常見的題。比如模擬圓餅移動、乘除法、連結串列等等。解決這類問題的關鍵在於熟練掌握常見運動與計算的過程,並具有較熟練的程式設計能力。

演算法

標準:陣列元素迴圈右移只需要每次移動一位數字,然後移動幾位就有幾個迴圈即可。每一個移動過程中,將最後一位數字取出來放在tmp中,然後左邊的數字都往右移動一位,最後再把tmp的數字放回到左邊下標為0的位置。見程式碼1、2~

其他思路:這道題也可以採用這樣一種思路

實際上就是把需要移動的全部存到一個新的陣列中。然後再重新填回去a[]就行。見程式碼3。這裡要注意M=0時,無法開闢長度為0 的陣列,因此要單獨將M>0拿出來。

填回去之後提交併不能完全AC,這裡要注意題目只說了M>=0,沒說M會小於N,因此不能AC的兩個測試樣例可能是M>N了,因此要將程式碼3改一改。也比較容易,因為如果陣列完全移動N個位置的話會變回原陣列,也就是說週期為N,因此只要將M%N,取餘數就可以是最後的移動效果。見程式碼4。

採用這種方式,可以在M很大的時候,減小移動的時間複雜度,相比於程式碼2這種移動效率也會比較高。

那麼問題來了,能不能將標準演算法的程式碼2的移動效率在M>N時提高呢?顯然也是可以的,只需要將第二個for迴圈裡的i<M改為i<M%N即可。

修改之後的程式碼見程式碼5。程式碼最後的註釋部分可以看出優化後的效果,時間上縮小了70倍左右。

對於程式碼4,實際上可以偷懶,將前後兩部分取出來之後,可以不用再放回a[]陣列,直接輸出就行。由於AC時只看輸出結果,因此並不影響,而且時間複雜度可以減少一半。見程式碼6.

網上見到了一個使用庫函式的好方法。既然有對應的函式,就省了很多事啦~。見程式碼7.

reverse()函式包含在標頭檔案<algorithm>中。該標頭檔案還有常用的sort()函式。

C++ < algorithm > 中定義的reverse函式用於反轉在[first,last)範圍內的順序

template <class BidirectionalIterator>
void reverse (BidirectionalIterator first,BidirectionalIterator last);

例如,交換vector容器中元素的順序

vector<int> v={1,2,3,4,5};
reverse(v.begin(),v.end());//v的值為5,4,3,2,1

當然,你也可以通過它方便的反轉string類的字串

string str="C++REVERSE";
reverse(str.begin(),str.end());//str結果為ESREVER++C

該函式等價於通過呼叫iter_swap來交換元素位置

template <class BidirectionalIterator>
void reverse (BidirectionalIterator first, BidirectionalIterator last)
{
    while ((first!=last)&&(first!=--last))
    {
        std::iter_swap (first,last);
        ++first;
    }
}

標準庫的begin()和end()函式是C++11新標準引入的函式,可以對陣列型別進行操作,返回其首尾指標,對標準庫容器操作,返回相應迭代器。

標準庫容器的begin()和end()成員函式屬於對應類的成員,返回的是物件容器的首尾迭代器。


新標準庫的begin()和end()函式可以讓我們更容易的獲取陣列的首尾指標(注意尾指標是最後一個元素的下一個地址)

試了一下,begin和end與vector容器搭配起來,再與reverse在一起使用,超好用!!!

程式碼

程式碼1,這是以前的程式碼

//PAT1008V1
#include <stdio.h>

int main(){
	int a[100],temp,i,j,n,m;
	scanf("%d %d",&n,&m);
	
	for(i=0;i<n;i++)
		scanf("%d",&a[i]);	//init
	
//	for(i=0;i<n;i++)
//		printf("%d",a[i]);	//print
		
	for(i=0;i<m;i++){
		temp=a[n-1];
		for(j=n-1;j>0;j--){
			 
			a[j]=a[j-1];
		}
		a[0]=temp;
	}
	
	for(i=0;i<n;i++)
		printf("%d%c",a[i],i==(n-1)?'\0':' ');	//print
	
	return 0;
}

可以看出來具有初步的演算法思想,但是程式碼簡潔性和凝練度需要提高。另外從除錯的註釋能夠看出來,程式碼能力還不夠。

程式碼2,現在的程式碼

#include <iostream>
using namespace std;

int main(){
	int N,M,tmp;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];
	for(int i=0;i<M;i++){
		tmp=a[N-1];
		for(int j=N-1;j>0;j--)	a[j]=a[j-1];	//從倒數第二位開始依次後移一位 
		a[0]=tmp;	//挪出來的最後一位放到第一位上去 
	}
	for(int i=0;i<N;i++)	printf("%d%c",a[i],i==N-1?'\0':' ');
	return 0;
} 

程式碼3,重新開闢陣列,將對應移動部分存起來,但不能完全AC

#include <iostream>
using namespace std;

int main(){
	int N,M,tmp;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];	
	if(M>0){
		int b[M]={0},c[N-M]={0};
		for(int i=0;i<M;i++)	b[i]=a[N-M+i];	//將a[]後面需要移到前面的部分取出來 
		for(int i=0;i<N-M;i++)	c[i]=a[i]; 	//將a[]前面需要移到後面的部分取出來 
		for(int i=0;i<M;i++)	a[i]=b[i];	//將b[]放到a[]的前面來  
		for(int i=M;i<N;i++) 	a[i]=c[i-M];	//將c[]放到a[]的後面來  
	}
	for(int i=0;i<N;i++)	printf("%d%c",a[i],i==N-1?'\0':' ');
	return 0;
} 

程式碼4,注意M>N的情況,提高M>N時的移動效率

#include <iostream>
using namespace std;

int main(){
	int N,M;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];	
	if(M>0){
		int b[M%N]={0},c[N-M%N]={0};
		for(int i=0;i<M%N;i++)	b[i]=a[N-M%N+i];	//將a[]後面需要移到前面的部分取出來 
		for(int i=0;i<N-M%N;i++)	c[i]=a[i]; 	//將a[]前面需要移到後面的部分取出來 
		for(int i=0;i<M%N;i++)	a[i]=b[i];	//將b[]放到a[]的前面來  
		for(int i=M%N;i<N;i++) 	a[i]=c[i-M%N];	//將c[]放到a[]的後面來  
	}
	for(int i=0;i<N;i++)	printf("%d%c",a[i],i==N-1?'\0':' ');
	return 0;
} 

程式碼5,通法的優化

#include <iostream>
using namespace std;

int main(){
	int N,M,tmp;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];
	for(int i=0;i<M%N;i++){
		tmp=a[N-1];
		for(int j=N-1;j>0;j--)	a[j]=a[j-1];	//從倒數第二位開始依次後移一位 
		a[0]=tmp;	//挪出來的最後一位放到第一位上去 
	}
	for(int i=0;i<N;i++)	printf("%d%c",a[i],i==N-1?'\0':' ');
	return 0;
}

/*
output:
當採用M時
6 20000
1 2 3 4 5 6
5 6 1 2 3 4

耗時 14.32s 

當採用M%N時
6 20000
1 2 3 4 5 6
5 6 1 2 3 4

耗時 0.4798s 
*/ 

程式碼6,偷懶做法,並沒有移動……

#include <iostream>
using namespace std;

int main(){
	int N,M;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];	
	if(M>0){
		int b[M%N]={0},c[N-M%N]={0};
		for(int i=0;i<M%N;i++)	cout<<a[N-M%N+i]<<" ";	//將a[]後面需要移到前面的部分直接輸出 
		for(int i=0;i<N-M%N-1;i++)	cout<<a[i]<<" "; 	//將a[]前面需要移到後面的部分直接輸出
	}
	cout<<a[N-M%N-1];	//最後一個元素由於其後沒有空格,單獨輸出,省事 
	return 0;
} 

程式碼7。使用庫函式

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
    int n, m;
    cin >> n >> m;
    vector<int> a(n);
    for (int i = 0; i < n; i++)
        cin >> a[i];
    m %= n;
    if (m != 0) {
        reverse(begin(a), begin(a) + n);
        reverse(begin(a), begin(a) + m);
        reverse(begin(a) + m, begin(a) + n);
    }
    for (int i = 0; i < n - 1; i++)
        cout << a[i] << " ";
    cout << a[n - 1];
    return 0;
}