Codeforces Round #775 (Div. 1, based on Moscow Open Olympiad in Informatics)
二維陣列
一、二維陣列的定義
當一維陣列元素的型別也是一維陣列時,便構成了“陣列的陣列”,即二維陣列。二維陣列定義的一般格式:
資料型別 陣列名[常量表達式1] [常量表達式2] ;
例如:int a[4][10];
a陣列實質上是一個有4行、10列的表格,表格中可儲存40個元素。第1行第1列對應a陣列的a[0][0],第n行第m列對應陣列元素a[n-1][m-1]。
說明:當定義的陣列下標有多個時,我們稱為多維陣列,下標的個數並不侷限在一個或二個,可以任意多個,如定義一個三維陣列a和四維陣列b:
int a[100][3][5];
int b[100][100][3][5];
多維的陣列引用賦值等操作與二維陣列類似。
二、二維陣列元素的引用
二維陣列的陣列元素引用與一維陣列元素引用類似,區別在於二維陣列元素的引用必須給出兩個下標。
引用的格式為:
<陣列名>[下標1][下標2]
說明:顯然,每個下標表達式取值不應超出下標所指定的範圍,否則會導致致命的越界錯誤。
例如,設有定義:int a[3][5];
則表示a是二維陣列(相當於一個3*5的表格),共有3*5=15個元素,它們是:
a[0][0] a[0][1] a[0][2] a[0][3] a[0][4]
a[1][0] a[1][1] a[1][2] a[1][3] a[1][4]
a[2][0] a[2][1] a[2][2] a[2][3] a[2][4]
因此可以看成一個矩陣(表格),a[2][3]即表示第3行第4列的元素。
三、二維陣列的初始化
二維陣列的初始化和一維陣列類似。可以將每一行分開來寫在各自的括號裡,也可以把所有資料寫在一個括號裡。
例如:
int direct[4][2] = {{1,0},{0,1},{-1,0},{0,-1}}
int direct[4][2] = {1,0,0,1,-1,0,0,-1} //儘量不要用
四、二維陣列程式設計
例5.8 設有一程式
#include<cstdio> #include<iostream> #include<iomanip> const int n=3; using namespace std; int a[n+1][n+1]; int main() { for (int i=1; i<=n; ++i) { for (int j=1; j<=n; ++j) cin>>a[i][j]; } for (int i=1; i<=n; ++i) { for (int j=1; j<=n; ++j) cout<<setw(5)<<a[j][i]; cout<<endl; } return 0; }
程式的輸入:
2 1 3
3 3 1
1 2 1
程式的輸出:
2 3 1
1 3 2
3 1 1
例5.9 已知一個6*6的矩陣(方陣),把矩陣二條對角線上的元素值加上10,然後輸出這個新矩陣。
【分析】 矩陣即表格,是一個二維陣列,有6行6列共36個元素,每個矩陣都有二條對角線,本題難點在於對角線的元素怎麼確定。
#include<iostream> #include<iomanip> using namespace std; int a[7][7]; int main() { for (int i=1; i<=6; ++i) //輸入矩陣元素 for (int j=1; j<=6; ++j) cin>>a[i][j]; for (int i=1; i<=6; ++i) //更改對角線上元素的值 for (int j=1; j<=6; ++j) if ((i==j)||(i+j==7)) a[i][j]+=10; //尋找對角線的特徵 for (int i=1; i<=6; ++i) //輸出6行6列的矩陣元素 { for (int j=1; j<=6; ++j) cout<<setw(5)<<a[i][j]; cout<<endl; } return 0; }
例5.10 大部分元素是0的矩陣稱為稀疏矩陣,假設有k個非0元素,則可把稀疏矩陣用K*3的矩陣簡記之,其中第一列是行號,第二列是列號,第三列是該行、該列下的非0元素的值。如:
0 0 0 5 寫簡記成: 1 4 5 //第1行第4列有個數是5
0 2 0 0 2 2 2 //第2行第2列有個數是2
0 1 0 0 3 2 1 //第3行第2列有個數是1
試程式設計讀入一稀疏矩陣,轉換成簡記形式,並輸出。
【分析】 本題中需要解決的主要問題是查詢非零元素並記憶位置。將原始矩陣存於陣列a。轉換後的矩陣存於陣列b,當然b陣列的行數可以控制在一個小範圍內。
#include<iostream> #include<iomanip> const int n=3,m=5; using namespace std; int main() { int a[n+1][m+1],b[101][4],k=0; for (int i=1; i<=n; ++i) //矩陣初始 for (int j=1; j<=m; ++j) cin>>a[i][j]; for (int i=1; i<=n; ++i) for (int j=1; j<=m; ++j) if (a[i][j]!=0) //找到非零值,儲存 { ++k; b[k][1]=i; b[k][2]=j; b[k][3]=a[i][j]; } for (int i=1; i<=k; ++i) //輸出 { for (int j=1; j<=3; ++j) cout<<setw(3)<<b[i][j]; cout<<endl; } return 0; }
執行結果:
輸入: 0 0 0 0 5
0 0 4 0 0
1 0 0 0 1
輸出: 1 5 5
2 3 4
3 1 1
3 5 1
例5.11 列印楊輝三角形的前10行。楊輝三角形如下圖:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
[圖5-1]
【問題分析】觀察圖5-1,大家不容易找到規律,但是如果將它轉化為圖5-2,不難發現楊輝三角形其實就是一個二維表的小三角形部分,假設通過二維陣列yh儲存,每行首尾元素為1,且其中任意一個非首位元素yh[i][j]的值其實就是yh[i-1][j-1]與yh[i-1][j]的和,另外每一行的元素個數剛好等於行數。有了陣列元素的值,要列印楊輝三角形,只需要控制好輸出起始位置就行了。
#include<iostream> #include<iomanip> using namespace std; int main() { int a[11][11]; a[1][1]=1; //設定第一行的值 for (int i=2; i<=10; ++i) //從第二行開始推 a[i][1]=1; a[i][i]=1; //設定每一行的首尾值為1 for (int j=2; j<=i-1; ++j) //當前行非首尾的數 a[i][j]=a[i-1][j-1]+a[i-1][j]; //每個數等於上一行的二個數之和 } for (int i=1; i<=10; i++) { if (i!=10) cout<<setw(30-3*i)<<" "; //控制每行的起始位置,即空格數量 for (int j=1; j<=i; j++) cout<<setw(6)<<a[i][j]; cout<<endl; } return 0; }
例5.12 輸入一串字元,字元個數不超過100,且以“.”結束。 判斷它們是否構成迴文。
【分析】所謂迴文指從左到右和從右到左讀一串字元的值是一樣的,如12321,ABCBA,AA等。先讀入要判斷的一串字元(放入陣列letter中),並記住這串字元的長度,然後首尾字元比較,並不斷向中間靠攏,就可以判斷出是否為迴文。
程式如下:
#include<iostream> using namespace std; int main() { char ch,letter[101]; int i=0,j=1; cout<<"Input a string:"; cin>>ch; while (ch!='.') //讀入一個字串以'.'號結束 { ++i; letter[i]=ch; cin>>ch; } while ((j<i)&&(letter[j]==letter[i])) //判斷它是否是迴文 { --i; ++j; } if (j>=i) cout<<"Yes"<<endl; else cout<<"No"<<endl; return 0; }
例5.13 蛇形填數
在n*n方陣裡填入1,2,3,…,n*n,要求填成蛇形。例如n=4時方陣為:
10 11 12 1
9 16 13 2
8 15 14 3
7 6 5 4
上面的方陣中,多餘的空格只是為了便於觀察規律,不必嚴格輸出,n<=8。
【分析】:
類比數學中的矩陣,我們可以用一個所謂的二維陣列來儲存題目中的方陣。只需宣告一個int a[MAXN][MAXN],就可以獲得一個大小為MAXN×MAXN的方陣。在宣告時,兩維的大小不必相同,因此也可以宣告int a[30][50]這樣的陣列,第一維下標範圍是0,1,2,…,29,第二維下標範圍是0,1,2,…,49。
讓我們從1開始依次填寫。設“筆”的座標為(x,y),則一開始x=0,y=n-1,即第0行,第n-1列(別忘了行列的範圍是0到n-1,沒有第n列)。“筆”的移動軌跡是:下,下,下,左,左,左,上,上,上,右,右,下,下,左,上。總之,先是下,到不能填了為止,然後是左,接著是上,最後是右。“不能填”是指再走就出界(例如4→5),或者再走就要走到以前填過的格子(例如12→13)。如果我們把所有格子初始為0,就能很方便地加以判斷。
二維陣列 例5.13 蛇形填數 在n*n方陣裡填入1,2,3,…,n*n,要求填成蛇形。例如n=4時方陣為: 10 11 12 1 9 16 13 2 8 15 14 3 7 6 5 4 上面的方陣中,多餘的空格只是為了便於觀察規律,不必嚴格輸出,n<=8。 【分析】: 類比數學中的矩陣,我們可以用一個所謂的二維陣列來儲存題目中的方陣。只需宣告一個int a[MAXN][MAXN],就可以獲得一個大小為MAXN×MAXN的方陣。在宣告時,兩維的大小不必相同,因此也可以宣告int a[30][50]這樣的陣列,第一維下標範圍是0,1,2,…,29,第二維下標範圍是0,1,2,…,49。 讓我們從1開始依次填寫。設“筆”的座標為(x,y),則一開始x=0,y=n-1,即第0行,第n-1列(別忘了行列的範圍是0到n-1,沒有第n列)。“筆”的移動軌跡是:下,下,下,左,左,左,上,上,上,右,右,下,下,左,上。總之,先是下,到不能填了為止,然後是左,接著是上,最後是右。“不能填”是指再走就出界(例如4→5),或者再走就要走到以前填過的格子(例如12→13)。如果我們把所有格子初始為0,就能很方便地加以判斷。 #include<cstdio> #include<cstring> #define MAXN 10 int a[MAXN][MAXN]; int main() { int n,x,y,tot=0; scanf("%d",&n); memset(a,0,sizeof(a)); tot=a[x=0][y=n-1]=1; while (tot<n*n) { while (x+1<n && !a[x+1][y]) a[++x][y]=++tot; while (y-1>=0 && !a[x][y-1]) a[x][--y]=++tot; while (x-1>=0 && !a[x-1][y]) a[--x][y]=++tot; while (y+1<n && !a[x][y+1]) a[x][++y]=++tot; } for(x=0;x<n;++x) { for (y=0;y<n;++y) printf("%3d",a[x][y]); printf("\n"); } return 0; }
【說明】:
這段程式充分利用了C++語言簡潔的優勢。首先,賦值x=0和y=n-1後馬上要把它們作為a陣列的下標,因此可以合併完成;tot和a[0][n-1]都要賦值1,也可以合併完成。這樣,我們用一條語句完成了多件事情,而且並沒有犧牲程式的可讀性,這段程式碼的含義顯而易見。
那4條while語句有些難懂,不過十分相似,因此只需介紹其中的第一條:不斷向下走,並且填數。我們的原則是:先判斷,再移動,而不是走一步以後發現越界了再退回來。這樣,我們需要進行“預判”,即是否越界,以及如果繼續往下走會不會到達一個已經填過的格子。越界只需判斷x+1<n,因為y值並沒有修改;下一個格子是(x+1,y),因此只需a[x+1][y]==0,簡寫成!a[x+1][y](其中!是“邏輯非”運算子)。
細心的讀者也許會發現這裡的一個“潛在bug”;如果越界,x+1會等於n,a[x+1][y]將訪問非法記憶體!幸運的是,這樣的擔心是不必要的。&&是短路運算子。如果x+1<n為假,將不會計算!a[x+1][y],也就不會越界了。
至於為什麼是++tot而不是tot++,留給讀者思考。