L6. Mảng Hai Chiều và Chuỗi Ký Tự¤
Phần 1: Mảng Hai Chiều¤
6. Mảng 2 Chiều¤
6.1. Khai Báo Mảng 2 Chiều¤
Cú pháp:
Ví dụ:
char A[10][20];
// Kiểu dữ liệu: char
// Tên biến mảng: A
// Mảng có 10 dòng và 20 cột
int Mang2Chieu[3][5];
// Kiểu dữ liệu: int
// Tên biến mảng: Mang2Chieu
// Mảng có 3 dòng và 5 cột
Minh họa các mảng 2 chiều:
A[2][4]: B[2][2]: C[2][1]:
┌────┬─────┬─────┬────┐ ┌────┬─────┐ ┌────┐
│ 29 │ 137 │ 50 │ 4 │ │ 29 │ 137 │ │ 29 │
├────┼─────┼─────┼────┤ ├────┼─────┤ ├────┤
│ 5 │ 32 │ 657 │ 97 │ │ 5 │ 32 │ │ 5 │
└────┴─────┴─────┴────┘ └────┴─────┘ └────┘
6.2. Chỉ Số Mảng 2 Chiều¤
Đặc điểm:
- Chỉ số là giá trị số nguyên int
- Gồm chỉ số dòng và chỉ số cột
- 0 ≤ chỉ số dòng ≤ số dòng - 1
- 0 ≤ chỉ số cột ≤ số cột - 1
Ví dụ:
int A[2][3];
// Tên mảng: A
// Kiểu: int
// Số phần tử: 2 × 3 = 6
// Chỉ số dòng: 0, 1
// Chỉ số cột: 0, 1, 2
Cột 0 Cột 1 Cột 2
┌───────┬───────┬───────┐
Dòng 0│ 2 │ 45 │ 7 │
├───────┼───────┼───────┤
Dòng 1│ 73 │ 11 │ 187 │
└───────┴───────┴───────┘
6.3. Truy Xuất Phần Tử Mảng 2 Chiều¤
Cú pháp:
Ví dụ:
int A[2][3] = {
{29, 137, 50},
{3, 78, 943}
};
// Truy xuất hợp lệ
A[0][0] // = 29
A[0][1] // = 137
A[0][2] // = 50
A[1][0] // = 3
A[1][1] // = 78
A[1][2] // = 943
// Truy xuất KHÔNG hợp lệ
A[-1][0] // SAI: chỉ số âm
A[1][4] // SAI: vượt quá số cột
A[2][0] // SAI: vượt quá số dòng
Cột 0 Cột 1 Cột 2
┌───────┬───────┬───────┐
Dòng 0│ 29 │ 137 │ 50 │
├───────┼───────┼───────┤
Dòng 1│ 3 │ 78 │ 943 │
└───────┴───────┴───────┘
Giá trị các phần tử:
- A[0][0] = 29, A[0][1] = 137, A[0][2] = 50
- A[1][0] = 3, A[1][1] = 78, A[1][2] = 943
6.4. Địa Chỉ Các Phần Tử Mảng 2 Chiều¤
Cú pháp:
Ví dụ:
int A[2][4];
// Địa chỉ các phần tử dòng 0:
&A[0][0], &A[0][1], &A[0][2], &A[0][3]
// Địa chỉ các phần tử dòng 1:
&A[1][0], &A[1][1], &A[1][2], &A[1][3]
Cột 0 Cột 1 Cột 2 Cột 3
┌───────┬───────┬───────┬───────┐
Dòng 0│ 76 │ 87 │ 40 │ 331 │
├───────┼───────┼───────┼───────┤
Dòng 1│ 456 │ 23 │ 174 │ 56 │
└───────┴───────┴───────┴───────┘
Địa chỉ:
0x100 0x104 0x108 0x10C
0x110 0x114 0x118 0x11C
6.5. Khái Niệm Liên Quan Đến Ma Trận¤
Cho ma trận vuông 3×3:
┌───┬───┬───┐
│ 3 │ 7 │ 8 │ Dòng 0
├───┼───┼───┤
│ 6 │ 1 │ 4 │ Dòng 1
├───┼───┼───┤
│ 0 │ 9 │ 5 │ Dòng 2
└───┴───┴───┘
0 1 2 ← Cột
Đường chéo chính: {3, 1, 5} - Các phần tử A[i][i]
┌───┬───┬───┐
│ 3 │ 7 │ 8 │ ← A[0][0] = 3
├───┼───┼───┤
│ 6 │ 1 │ 4 │ ← A[1][1] = 1
├───┼───┼───┤
│ 0 │ 9 │ 5 │ ← A[2][2] = 5
└───┴───┴───┘
Đường chéo phụ: {8, 1, 0} - Các phần tử A[i][n-1-i]
┌───┬───┬───┐
│ 3 │ 7 │ 8 │ ← A[0][2] = 8
├───┼───┼───┤
│ 6 │ 1 │ 4 │ ← A[1][1] = 1
├───┼───┼───┤
│ 0 │ 9 │ 5 │ ← A[2][0] = 0
└───┴───┴───┘
Nửa trên đường chéo chính: {3, 7, 8, 1, 4, 5}
┌───┬───┬───┐
│ 3 │ 7 │ 8 │ ← Tất cả
├───┼───┼───┤
│ 6 │ 1 │ 4 │ ← Từ A[1][1] trở đi
├───┼───┼───┤
│ 0 │ 9 │ 5 │ ← Chỉ A[2][2]
└───┴───┴───┘
Nửa dưới đường chéo chính: {3, 6, 1, 0, 9, 5}
┌───┬───┬───┐
│ 3 │ 7 │ 8 │ ← Chỉ A[0][0]
├───┼───┼───┤
│ 6 │ 1 │ 4 │ ← Từ A[1][0] đến A[1][1]
├───┼───┼───┤
│ 0 │ 9 │ 5 │ ← Tất cả
└───┴───┴───┘
6.6. Truyền Mảng 2 Chiều Cho Hàm¤
Khai báo tham số:
int TinhDCheo(int A[50][50], int n, int m);
// Tên hàm: TinhDCheo
// Tham số: mảng A, số dòng n, số cột m
// Giá trị trả về: int
void XuatMang(int A[50][50], int n, int m);
// Tên hàm: XuatMang
// Tham số: mảng A, số dòng n, số cột m
// Giá trị trả về: void
Đặc điểm
- Mảng có thể thay đổi nội dung sau khi thực hiện hàm
- Có thể bỏ số dòng, nhưng phải giữ số cột:
Ví dụ đầy đủ:
#include <stdio.h>
#include <conio.h>
void nhap(int A[][100], int &N, int &M);
void xuat(int A[][100], int N, int M);
void SapXep(int A[][100], int N, int M);
void main() {
int a[100][100], n, m;
nhap(a, n, m);
xuat(a, n, m);
SapXep(a, n, m);
}
7. Các Tác Vụ Trên Mảng 2 Chiều¤
7.1. Nhập Ma Trận¤
#define MAXC 100
void NhapMaTran(int A[][MAXC], int &m, int &n) {
cout << "Nhap so dong, so cot cua ma tran: ";
cin >> m >> n;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
cout << "Nhap A[" << i << "][" << j << "] = ";
cin >> A[i][j];
}
}
}
7.2. Xuất Ma Trận¤
void XuatMaTran(int A[][MAXC], int m, int n) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
printf("%d ", A[i][j]);
}
printf("\n"); // Xuống dòng sau mỗi hàng
}
}
Ví dụ output:
7.3. Tìm Kiếm Phần Tử¤
int TimKiem(int a[][MAXC], int m, int n, int x) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] == x)
return 1; // Tìm thấy
}
}
return 0; // Không tìm thấy
}
7.4. Kiểm Tra Tính Chất Ma Trận¤
Yêu cầu: Ma trận có toàn số chẵn không?
Ý tưởng 1: Đếm số chẵn = m×n?
int KiemTra_YT1(int a[][MAXC], int m, int n) {
int dem = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] % 2 == 0)
dem++;
}
}
if (dem == m * n)
return 1;
return 0;
}
Ý tưởng 2: Đếm số lẻ = 0?
int KiemTra_YT2(int a[][MAXC], int m, int n) {
int dem = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] % 2 != 0)
dem++;
}
}
if (dem == 0)
return 1;
return 0;
}
Ý tưởng 3: Tìm số lẻ (Tối ưu nhất)
int KiemTra_YT3(int a[][MAXC], int m, int n) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] % 2 != 0)
return 0; // Tìm thấy số lẻ
}
}
return 1; // Không có số lẻ
}
7.5. Đếm Số Lượng Phần Tử¤
int Dem(int A[][MAXC], int m, int n) {
int Dem = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
Dem++;
}
}
return Dem;
}
7.6. Tính Tổng Các Phần Tử Chẵn¤
int TongChan(int A[][MAXC], int m, int n) {
int TC = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (A[i][j] % 2 == 0)
TC = TC + A[i][j];
}
}
return TC;
}
7.7. Tính Tổng Đường Chéo Chính¤
int TongDCChinh(int a[][MAXC], int m, int n) {
int tong = 0;
// Chỉ áp dụng cho ma trận vuông
if (m != n) {
cout << "Khong phai ma tran vuong!";
return 0;
}
for (int i = 0; i < n; i++) {
tong = tong + a[i][i]; // Đường chéo chính
}
return tong;
}
Các biến thể:
// Tổng đường chéo phụ
int TongDCPhu(int a[][MAXC], int n) {
int tong = 0;
for (int i = 0; i < n; i++) {
tong += a[i][n-1-i];
}
return tong;
}
// Tổng nửa trên đường chéo chính
int TongNuaTren(int a[][MAXC], int n) {
int tong = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
tong += a[i][j];
}
}
return tong;
}
// Tổng nửa dưới đường chéo chính
int TongNuaDuoi(int a[][MAXC], int n) {
int tong = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= i; j++) {
tong += a[i][j];
}
}
return tong;
}
8. Bài Tập - Mảng 2 Chiều¤
Bài 1: Nhập/Xuất ma trận
Viết hàm nhập và xuất ma trận m×n.
Bài 2: Tìm kiếm
Tìm kiếm một phần tử trong ma trận.
Bài 3: Kiểm tra đối xứng
Kiểm tra ma trận có đối xứng qua đường chéo chính hay không?
Gợi ý: So sánh A[i][j] với A[j][i]
Bài 4: Tính tổng
Tính tổng các phần tử:
- Trên dòng k
- Trên cột k
- Toàn ma trận
- Đường chéo chính
- Nửa trên/nửa dưới đường chéo chính
Bài 5: Tìm min/max
Tìm giá trị nhỏ nhất/lớn nhất của ma trận.
Bài 6: Tính tổng 2 ma trận
Cho 2 ma trận A và B cùng kích thước m×n. Tính C = A + B.
Bài 7: Tính tích 2 ma trận
Cho ma trận A (m×p) và B (p×n). Tính C = A × B (m×n).
Công thức: C[i][j] = Σ(A[i][k] × B[k][j]) với k từ 0 đến p-1
Bài 8: Ma trận đơn vị
Kiểm tra ma trận có phải là ma trận đơn vị không?
Ma trận đơn vị: Đường chéo chính = 1, còn lại = 0
Phần 2: Chuỗi Ký Tự¤
8. Chuỗi Ký Tự¤
8.1. Khái Niệm¤
Vấn đề:
- Kiểu char chỉ chứa được một ký tự
- Để lưu trữ nhiều ký tự (chuỗi) → Sử dụng mảng ký tự
Đặc điểm:
- Chuỗi ký tự kết thúc bằng ký tự '\0' (null)
- Độ dài chuỗi = kích thước mảng - 1
Minh họa:
Chuỗi "HELLO"
┌───┬───┬───┬───┬───┬────┐
│ H │ E │ L │ L │ O │ \0 │
└───┴───┴───┴───┴───┴────┘
0 1 2 3 4 5
8.2. Khai Báo Chuỗi Ký Tự¤
Các cách khai báo:
char sName[100]; // Cách 1: Chỉ định kích thước
char sName[]; // Cách 2: Tự động xác định
char *sName; // Cách 3: Con trỏ ký tự
8.3. Khởi Tạo Chuỗi Ký Tự¤
Cách 1: Khởi tạo từng ký tự (độ dài cụ thể)
┌───┬───┬───┬───┬───┬───┬────┬───┬───┬───┐
│ T │ H │ C │ S │ │ A │ \0 │ │ │ │
└───┴───┴───┴───┴───┴───┴────┴───┴───┴───┘
0 1 2 3 4 5 6 7 8 9
Cách 2: Khởi tạo bằng chuỗi (tự động thêm '\0')
┌───┬───┬───┬───┬───┬───┬────┬───┬───┬───┐
│ T │ H │ C │ S │ │ A │ \0 │ │ │ │
└───┴───┴───┴───┴───┴───┴────┴───┴───┴───┘
0 1 2 3 4 5 6 7 8 9
Cách 3: Tự động xác định độ dài
┌───┬───┬───┬───┬───┬───┬────┐
│ T │ H │ C │ S │ │ A │ \0 │
└───┴───┴───┴───┴───┴───┴────┘
0 1 2 3 4 5 6
8.4. Nhập Xuất Chuỗi¤
Hàm nhập chuỗi: gets()
Lưu ý về gets()
gets()không an toàn, dễ bị tràn bộ nhớ- Nên dùng
fgets()hoặccin.getline()thay thế
Hàm xuất chuỗi: puts()
Sử dụng cin và cout:
char s[100];
// Nhập (chỉ nhận đến khoảng trắng đầu tiên)
cin >> s;
// Nhập cả dòng (bao gồm khoảng trắng)
cin.getline(s, 100);
// Xuất
cout << s;
8.5. Các Hàm Thư Viện <string.h>¤
| Hàm | Chức năng |
|---|---|
strlen(s) |
Tính độ dài chuỗi |
strcpy(dest, src) |
Sao chép chuỗi |
strdup(s) |
Tạo bản sao |
strlwr(s) |
Chuyển thành chữ thường |
strupr(s) |
Chuyển thành chữ hoa |
strrev(s) |
Đảo ngược chuỗi |
strcmp(s1, s2) |
So sánh (phân biệt hoa/thường) |
stricmp(s1, s2) |
So sánh (không phân biệt) |
strcat(dest, src) |
Nối 2 chuỗi |
strstr(s1, s2) |
Tìm chuỗi con |
Ví dụ:
#include <string.h>
char s1[] = "Hello";
char s2[20];
strlen(s1); // = 5
strcpy(s2, s1); // s2 = "Hello"
strcat(s2, " World"); // s2 = "Hello World"
strcmp(s1, "Hello"); // = 0 (bằng nhau)
strupr(s1); // s1 = "HELLO"
9. Các Thao Tác Trên Chuỗi Ký Tự¤
9.1. Đếm Ký Tự Khoảng Trắng¤
int DemKhoangTrang(char chuoi[100]) {
int dem = 0;
for (int i = 0; i < strlen(chuoi); i++) {
if (chuoi[i] == ' ')
dem++;
}
return dem;
}
9.2. Đếm Ký Tự Hoa/Thường¤
void DemKyTu(char chuoi[]) {
int dt = 0, dh = 0;
for (int i = 0; i < strlen(chuoi); i++) {
if (chuoi[i] >= 'a' && chuoi[i] <= 'z')
dt++;
else if (chuoi[i] >= 'A' && chuoi[i] <= 'Z')
dh++;
}
printf("So ky tu thuong: %d\n", dt);
printf("So ky tu hoa: %d\n", dh);
}
9.3. Viết Hoa Chữ Cái Đầu Câu¤
void DoiHoaThuong(char chuoi[100]) {
// Chữ đầu tiên viết hoa
chuoi[0] = toupper(chuoi[0]);
// Các chữ còn lại viết thường
for (int i = 1; i < strlen(chuoi); i++) {
chuoi[i] = tolower(chuoi[i]);
}
printf("Xuat chuoi: ");
puts(chuoi);
}
Input: hELLo WoRLD
Output: Hello world
9.4. Chuyển Hoa Sang Thường¤
void ChuyenHoaSangThuong(char chuoi[100]) {
char kq[100];
strcpy(kq, chuoi);
for (int i = 0; kq[i] != '\0'; i++) {
if (kq[i] >= 'A' && kq[i] <= 'Z')
kq[i] = tolower(kq[i]);
}
printf("Xuat chuoi: ");
puts(kq);
}
Cách đơn giản hơn:
9.5. Chuyển Thường Sang Hoa¤
void ChuyenThuongSangHoa(char chuoi[100]) {
char kq[100];
strcpy(kq, chuoi);
for (int i = 0; kq[i] != '\0'; i++) {
if (kq[i] >= 'a' && kq[i] <= 'z')
kq[i] = toupper(kq[i]);
}
printf("Xuat chuoi: ");
puts(kq);
}
Cách đơn giản hơn:
9.6. Liệt Kê Các Từ Trong Chuỗi¤
void LietKe(char chuoi[100]) {
int d = 0; // Vị trí bắt đầu từ
for (int i = 0; i < strlen(chuoi); i++) {
if (chuoi[i] == ' ') {
// In từ từ vị trí d đến i-1
for (int j = d; j < i; j++) {
printf("%c", chuoi[j]);
}
d = i + 1; // Vị trí bắt đầu từ mới
printf("\n");
}
}
// In từ cuối cùng
for (int j = d; j < strlen(chuoi); j++) {
printf("%c", chuoi[j]);
}
}
Input: "Hello World C++"
Output:
9.7. Xóa Khoảng Trắng Đầu/Cuối¤
Xóa đầu chuỗi:
void xoadau(char chuoi[100]) {
while (chuoi[0] == ' ') {
// Dịch tất cả ký tự sang trái
for (int i = 0; i < strlen(chuoi); i++) {
chuoi[i] = chuoi[i + 1];
}
}
}
Xóa cuối chuỗi:
void xoacuoi(char chuoi[100]) {
int len = strlen(chuoi);
while (chuoi[len - 1] == ' ') {
chuoi[len - 1] = '\0'; // Thay khoảng trắng bằng '\0'
len--;
}
}
Input: " Hello World "
Output sau xoadau: "Hello World "
Output sau xoacuoi: "Hello World"
10. Bài Tập - Chuỗi Ký Tự¤
Bài 1: Nhập/Xuất chuỗi
Viết hàm nhập và xuất chuỗi ký tự.
Bài 2: Xuất ký tự in hoa
Xuất các ký tự in hoa trong chuỗi.
Gợi ý: Kiểm tra c >= 'A' && c <= 'Z'
Bài 3: Đảo ngược chuỗi
Đảo ngược các ký tự trong chuỗi.
Gợi ý: Hoán vị s[i] và s[n-1-i]
Bài 4: Xen kẽ hoa thường
Đổi chữ xen kẽ: 1 chữ hoa, 1 chữ thường.
Input: "hello world"
Output: "HeLlO wOrLd"
Bài 5: Đếm ký tự xuất hiện
Đếm một ký tự xuất hiện bao nhiêu lần trong chuỗi.
Bài 6: Ký tự xuất hiện nhiều nhất
Tìm ký tự nào xuất hiện nhiều nhất trong chuỗi.
Gợi ý: Dùng mảng đếm tần suất 256 phần tử
Bài 7: Kiểm tra đối xứng
Kiểm tra xem chuỗi có đối xứng hay không?
Ví dụ: "abccba" → đối xứng
Bài 8: Xóa từ trong chuỗi
Nhập vào một từ và xóa từ đó trong chuỗi đã cho.
Gợi ý: Sử dụng strstr() để tìm, sau đó dịch chuỗi
Tổng kết Mảng 2 Chiều và Chuỗi
Mảng 2 chiều:
- Khai báo:
int a[m][n]; - Truy xuất:
a[i][j] - Truyền hàm: Phải giữ số cột
- Các khái niệm: đường chéo chính, phụ, nửa trên/dưới
Chuỗi ký tự:
- Là mảng ký tự kết thúc bằng
'\0' - Độ dài = kích thước - 1
- Nhập:
gets(),cin.getline() - Xuất:
puts(),cout - Thư viện
<string.h>: nhiều hàm hữu ích
Các thao tác cơ bản:
- Đếm, tìm kiếm, chuyển đổi
- Xóa, thêm, sửa
- So sánh, nối chuỗi
Chương tiếp theo: Đệ quy (Recursion)