V.3 - Mảng 2 chiều
V.3.1 - Định nghĩa mảng hai chiều
Mảng hai chiều có thể hiểu như bảng gồm các dòng các cột, các phần tử thuộc cùng một kiểu dữ liệu nào đó. Mảng hai chiều được định nghĩa như sau.
Cú pháp Ki ểu_mảng tên_mảng [sd][sc];
Trong đó:
- Kiểu_mảng: đây là kiểu của mảng, là tên một kiểu dữ liệu đã tồn tại, có thể là kiểu chuẩn hoặc kiểu dữ liệu do người lập trình định nghĩa.
- tên_mảng : là tên của mảng, do người lập trình đặt, theo quy tắc về tên của C.
- sd, sc : là hằng (hoặc biểu thức hằng) nguyên, dương tương ứng là số dòng và số cột mảng, số phần tử của mảng sẽ là sd*sc.
Ví dụ:
int a[2][5]; // a là mảng số nguyên có 2 dòng, 5 cột (có 10 phần tử)
float D[3][10]; // D là mảng số thực có 3 dòng, 10 cột (có 30 phần tử)
char DS[5][30]; // DS là mảng kí tự có 5 dòng, 30 cột
Khi gặp một định nghĩa mảng, chương trình dịch sẽ cấp phát một vùng nhớ liên tiếp có kích thước là sd*sc*sizeof (Kiểu_mảng) cho mảng. Có thể coi mảng 2 chiều n dòng, m cột là mảng 1 chiểu có n phần tử, mỗi phần tử lại là 1 mảng một chiều có m phần tử (mảng của mảng).
Ví dụ với float D[3][10] có thể xem D là mảng có 3 phần tử D[0], D[1], D[2], mỗi phần tử này là mảng có 10 phần tử.
V.3.2 – Truy xuất các phần tử mảng hai chiều
Một phần tử của mảng 2 chiều được xác định qua tên (tên của mảng) và chỉ số dòng, chỉ số cột của nó trong mảng theo cú pháp sau:
tên_mảng [csd][csc]
Với csd là số nguyên xác định chỉ số dòng và csc là số hiệu cột cũng như trong mảng 1 chiều các chỉ số được tính từ 0. Tức là 0 ≤ csd ≤ sd-1 và 0 ≤ csc ≤ sc-1.
Lưu ý: Các phần tử của mảng 2 chiều cũng được dùng như các biến đơn, trừ trường hợp khi nhập giá trị cho các phần tử mảng kiểu float bằng hàm scanf thì bạn nên sử dụng biến (đơn) trung gian, sau đó gán giá trị của biến đó vào phần tử mảng chứ không nên sử dụng toán tử & để nhập trực tiếp phần tử của mảng.
V.3.3 – Khởi đầu giá trị các phần tử mảng hai chiều
Các phần tử mảng hai chiều cũng có thể được khởi đầu giá trị theo cú pháp (4 dạng sau):
1. Kiểu_mảng tên_mảng [sd][sc] = {{kđ_dòng_1},{ kđ_dòng_2},..,{ kđ_dòng_k}};
2. Kiểu_mảng tên_mảng [ ][sc] = {{kđ_dòng_1},{ kđ_dòng_2},..,{ kđ_dòng_k}};
3. Kiểu_mảng tên_mảng [sd][sc] = { gt_1, gt_2,...,gt_n };
4. Kiểu_mảng tên_mảng [ ][sc] = { gt_1, gt_2,...,gt_n }; Cú pháp trên có thể giải thích như sau:
• dạng 1: có k bộ giá trị sẽ được gán cho k dòng đầu tiên của mảng (k ≤ sd ), với mỗi dòng (được coi như mảng một chiều) được khởi tạo giá trị như mảng một chiều: dòng thứ nhất được khởi đầu bởi {kđ_dòng_1}, dòng thứ hai được khởi đầu bởi {kđ_dòng_1},.., dòng thứ k được khởi đầu bởi {kđ_dòng_k}. Yêu cầu k ≤ sd, ngược lại chương trình sẽ báo lỗi. Các dòng cuối của mảng nếu không có bộ khởi đầu tương ứng thì sẽ được tự động gán giá trị 0 (hoặc NULL nếu là con trỏ).
• dạng 2: (không xác định số dòng) chương trình dịch sẽ tự động ấn định số dòng của mảng bằng số bộ khởi đầu ( = k), sau đó thực hiện khởi đầu như dạng 1.
• dạng 3: n giá trị trong bộ khởi đầu được gán cho các phần tử mảng theo cách: sc giá trị đầu tiên trong các giá trị khởi đầu (gt_1,..,gt_sc) được gán tuần tự cho các phần tử của dòng thứ nhất trong mảng, sc phần tử kế tiếp sẽ gán cho các phần tử ở dòng thứ 2,... nếu phần tử nào của mảng không có giá trị khởi đầu sẽ được gán 0 (con trỏ là NULL) - với điều kiện n ≤ sd*sc, ngược lại là lỗi.
• dạng 4: số dòng của mảng sẽ được chương trình tự tính theo số giá trị trong bộ khởi đầu theo công thức sd = n +1, và khởi đầu như dạng 3.
Ví dụ:
int a[3][2] = {{1,2},{3},{4,5}}; thì các phần tử của a như sau:
a[0][0]=1, a[0][1]=2, a[1][0]=3, a[1][1]= 0, a[2][0]=4,a[2][1]=5; à int b[ ][2] = {{1,2},{3},{4,5}}; thì là mảng 3 dòng, 2 cột các phần tử của a như sau:
b[0][0]=1, b[0][1]=2, b[1][0]=3,b[1][1]= 0, b[2][0]=4,b[2][1]=5; à int c[ ][2] = {1,2,3,4,5}; thì số dòng của c là mảng 5/2 +1 =3 dòng, các phần tử của a như sau:
c[0][0]=1, c[0][1]=2, c[1][0]=3,c[1][1]= 4, b[2][0]=5,b[2][1]=0; V.3.3
- Một số ví dụ về mảng hai chiều
Ví dụ V.5: Chương trình nhập mảng A(n,m), 1≤ n,m ≤ 5, các số nguyên từ bàn phím, in mảng ra màn hình theo yêu cầu các phần tử cùng một hàng được in trên một dòng của màn hình, các phần tử cách nhau một dấu trống.
#include <stdio.h>
#include <conio.h>
void main){ <; //xóa màn hình
const int max =5; // kích thước tối đa
int A[max][max];
int n,m,i,j;
do{
printf("\nNhap so dong cua mang = ");
scanf"; printf("\nNhap so cot cua mang = ");
scanf";
} while(n<1 || n>max|| m<1 || m>max);
printf"\nNhapmangco;
for0
for0
{
printf"A[;
scanf";
} printf("\nCac phan tu mang la \n");
for0
{
printf("\n");
for0
printf";
} getch);<, B(n,m), 1≤ n,m ≤ 5, các số thực từ bàn phím, tính in ra màn hình ma trận C = A+B. Giải: Trước khi viết chương trình chúng ta lưu ý đến mấy vấn đề: à C = A+B có nghĩa là các phần của C được tính C[i][j] = A[i][j] + B[i][j] à chỉ có thể cộng hai ma trận A,B cùng kích thước và C cũng cùng kích thước với A,B à do các phần tử mảng có kiểu là float vì vậy khi nhập ta nên dùng biến phụ.
#include <stdio.h>
#include <conio.h>
void main){<;
const int max =5; //số dòng, cột tối đa
float A[max][max],B[max][max],C[max][max]; int n,m,i,j;
float x;
do{
printf("\nNhap so dong cua ma tran = ");
scanf";
printf("\nNhap so cot cua ma tran = ");
scanf";
} while(n<1 || n>max|| m<1 || m>max);
for0 for0
{ printf"A[;
scanf";A[i][j]=x;
}
printf"\nNhapBco;
for0 for0
{ printf"B[;
scanf";B[i][j]=x;
}
for0
for0
C[i][j]=A[i][j]+B[i][j];
printf("\nCac phan tu ma tran C la \n");
for0 { printf("\n");
for0
printf0;
}
getch0, 1≤ n,n ≤ 5, các số nguyên từ bàn phím, sau đó kiểm tra và thông báo ma trận đó có đối xứng hay không .
Giải: Một ma trận A là đối xứng trước hết nó phải là ma trận vuông và các phần tử của nó đối xứng nhau qua đường chéo chính, tức là A[i][j] =A[j][i]. Vậy chúng ta kiểm tra một ma trận đối xứng theo cách sau: V ới mỗi i,j -1 nếu tồn tại i,j mà A[i][j] != A[j][i] thì ta kết luận A không đối xứng, ngược lại A là đối xứng. Tất nhiên chúng ta không cần phải xét mọi cặp (i,j) có thể, vì nếu như vậy thì: - cặp (A[i][j] A[j][i]), và (A[j][i], A[i][j]) thực chất là một nhưng lại hai lần so sánh - phần tử trên đường chéo chính A[i][i] được so sánh với chính nó. Vì thế chúng ta chỉ xét các chỉ số (i,j) mà phần tử A[i][j] nằm thực sự phía trên của đường chéo chính. tức là chỉ cần xét các cặp phần tử (A[i][j], A[j][i]) với i chạy từ 0 tới n-1 và với j chạy từ i+1 tới n-1 là đủ. Hơn nữa chúng ta chỉ duyệt các cặp (A[i][j], A[j][i]) nếu chưa phát hiện được cặp nào khác nhau. V ậy ta có thể mô tả như sau: d=0; // d là biến để đánh dấu ghi nhận có gặp một cặp (A[i][j]!= A[j][i]) thì d=1 for(i=0; (i<n) && (d= =0); i++) for1 && (d= =0); j++) if(A[i][j]!=A[j][i]) d=1; Kết thúc đoạn lệnh lặp trên chỉ có hai khả năng - nếu d=1 tức là có cặp (A[i][j]!=A[j][i]) tức là ma trận không đối xứng. - ngược lại,(d=0) thì ma trận là đối xứng.
#include <stdio.h>
#include <conio.h>
void main){<;
const int max =5;
int A[max][max];
int n,d,i,j;
do{
printf("\nNhap so dong, so cot cua ma tran = ");
scanf";
} while(n<1 || n>max);
printf"\nNhapmatranvuongcap;
for0
for0
{ printf"A[;
scanf";
}
for(i=0,d=0; (i<n)&&(d==0); i++)
for(j=0; (j<n)&&(d==0); j++)
if(A[i][j]!=A[j][i]) d=1;
if(d) printf("\nMa tran khong doi xung");
else printf("\nMa tran doi xung");
getch();
}