Bài 6.5: Bài tập thực hành vòng lặp lồng nhau trong C++

Bài 6.5: Bài tập thực hành vòng lặp lồng nhau trong C++
Chào mừng các bạn quay trở lại với chuỗi bài viết về C++ của FullhouseDev!
Trong các bài trước, chúng ta đã làm quen với các loại vòng lặp cơ bản như for
, while
, do-while
. Chúng là những công cụ cực kỳ mạnh mẽ giúp chúng ta tự động hóa các công việc lặp đi lặp lại. Hôm nay, chúng ta sẽ nâng cấp sức mạnh đó lên một tầm cao mới bằng cách tìm hiểu và thực hành về vòng lặp lồng nhau (nested loops).
Vòng lặp lồng nhau đơn giản là việc đặt một vòng lặp bên trong một vòng lặp khác. Điều này cho phép chúng ta giải quyết các vấn đề có tính chất lặp đi lặp lại trên nhiều cấp độ, chẳng hạn như xử lý dữ liệu dạng lưới (ma trận), vẽ các hình mẫu phức tạp, hoặc thực hiện các thao tác cần duyệt qua tất cả các cặp phần tử trong một tập hợp.
Hãy cùng đi sâu vào lý thuyết và thực hành qua các ví dụ cụ thể nhé!
Vòng lặp lồng nhau hoạt động như thế nào?
Khi bạn có một vòng lặp bên ngoài và một vòng lặp bên trong:
- Vòng lặp bên ngoài bắt đầu chạy.
- Với mỗi một lần lặp của vòng lặp bên ngoài, vòng lặp bên trong sẽ chạy toàn bộ các lần lặp của nó từ đầu đến cuối.
- Sau khi vòng lặp bên trong hoàn thành, vòng lặp bên ngoài mới tiếp tục lần lặp kế tiếp (nếu có).
- Quá trình này lặp lại cho đến khi vòng lặp bên ngoài kết thúc.
Hãy hình dung như kim đồng hồ: Kim giờ (vòng lặp ngoài) nhích đi một nấc, thì kim phút (vòng lặp trong) phải quay hết một vòng (60 nấc). Kim giờ nhích nấc tiếp theo, kim phút lại quay hết một vòng nữa, cứ thế cho đến khi kim giờ hoàn thành vòng quay của nó.
Cú pháp chung có thể trông như thế này:
// Vòng lặp bên ngoài
for (khoi_tao_ngoai; dieu_kien_ngoai; cap_nhat_ngoai) {
// Các lệnh xử lý cho vòng lặp bên ngoài (nếu có)
// Vòng lặp bên trong
for (khoi_tao_trong; dieu_kien_trong; cap_nhat_trong) {
// Các lệnh xử lý cho vòng lặp bên trong
// Các lệnh này sẽ chạy mỗi khi vòng lặp bên trong thực hiện 1 lần
// và sẽ chạy TOÀN BỘ số lần lặp của vòng trong
// cho MỖI lần lặp của vòng ngoài.
}
// Các lệnh xử lý khác sau khi vòng lặp bên trong kết thúc cho lần lặp hiện tại của vòng ngoài
}
Bạn có thể lồng ghép bất kỳ loại vòng lặp nào (for
, while
, do-while
) vào với nhau. Tuy nhiên, phổ biến nhất cho các bài toán có số lần lặp xác định trước (như in hình, duyệt mảng 2D) là sử dụng for
lồng for
.
Tổng số lần thực hiện các lệnh bên trong vòng lặp bên trong sẽ bằng số lần lặp của vòng ngoài nhân với số lần lặp của vòng trong. Ví dụ, nếu vòng ngoài chạy 5 lần và vòng trong chạy 10 lần (cho mỗi lần của vòng ngoài), thì tổng cộng vòng trong sẽ chạy 5 * 10 = 50 lần.
Bài tập thực hành 1: In hình chữ nhật cơ bản
Bài tập kinh điển nhất để bắt đầu với vòng lặp lồng nhau là in ra một hình chữ nhật sử dụng một ký tự bất kỳ. Chúng ta cần chỉ định số hàng và số cột.
- Vòng lặp ngoài sẽ điều khiển số hàng.
- Vòng lặp trong sẽ điều khiển số ký tự (cột) trên mỗi hàng.
Sau khi vòng lặp trong kết thúc (hoàn thành in một hàng), chúng ta cần xuống dòng để bắt đầu hàng tiếp theo.
Hãy xem code C++:
#include <iostream> // Thư viện nhập xuất cơ bản
int main() {
int rows = 5; // Số hàng của hình chữ nhật
int cols = 8; // Số cột của hình chữ nhật
char symbol = '*'; // Ký tự sẽ dùng để vẽ
// Vòng lặp NGOÀI: Điều khiển số hàng
// Biến 'i' sẽ chạy từ 0 đến rows-1
for (int i = 0; i < rows; ++i) {
// Vòng lặp TRONG: Điều khiển số ký tự trên MỖI hàng
// Biến 'j' sẽ chạy từ 0 đến cols-1 cho MỖI giá trị của 'i'
for (int j = 0; j < cols; ++j) {
cout << symbol; // In ký tự symbol
}
// Sau khi vòng lặp trong (in xong 1 hàng) kết thúc, xuống dòng
cout << endl;
}
return 0; // Kết thúc chương trình thành công
}
Giải thích:
- Vòng lặp
for (int i = 0; i < rows; ++i)
chạyrows
lần. Mỗi lần lặp này tương ứng với việc xử lý một hàng. - Bên trong vòng lặp ngoài, vòng lặp
for (int j = 0; j < cols; ++j)
chạycols
lần. Vòng lặp này chạy hoàn toàn từj=0
đếnj=cols-1
cho mỗi giá trị củai
. Nó có nhiệm vụ in racols
ký tự trên cùng một dòng. cout << symbol;
nằm trong vòng lặp trong, nên nó sẽ được thực thirows * cols
lần, in ra tổng số ký tự bằng số hàng nhân số cột.cout << endl;
nằm sau vòng lặp trong nhưng trong vòng lặp ngoài. Điều này đảm bảo rằng sau khi in đủcols
ký tự cho một hàng, chương trình sẽ xuống dòng trước khi bắt đầu in hàng tiếp theo.
Kết quả chạy chương trình với rows = 5
và cols = 8
sẽ như sau:
********
********
********
********
********
Tuyệt vời! Bạn đã in được hình chữ nhật đầu tiên bằng vòng lặp lồng nhau.
Bài tập thực hành 2: In hình tam giác vuông
Bây giờ, hãy thử một hình phức tạp hơn một chút: hình tam giác vuông. Với hình tam giác, số ký tự trên mỗi hàng sẽ thay đổi. Ví dụ, hàng đầu tiên có 1 ký tự, hàng thứ hai có 2, v.v... cho đến hàng cuối cùng có số ký tự bằng chiều cao của tam giác.
Điều này có nghĩa là số lần lặp của vòng lặp bên trong sẽ phụ thuộc vào biến đếm của vòng lặp bên ngoài.
Hãy xem code:
#include <iostream>
int main() {
int height = 6; // Chiều cao của tam giác
char symbol = '#'; // Ký tự sẽ dùng để vẽ
// Vòng lặp NGOÀI: Điều khiển số hàng (từ 0 đến height-1)
for (int i = 0; i < height; ++i) {
// Vòng lặp TRONG: Điều khiển số ký tự trên hàng HIỆN TẠI (i)
// Số ký tự trên hàng i (bắt đầu từ i=0) là i+1
// Do đó, biến 'j' sẽ chạy từ 0 đến i
for (int j = 0; j <= i; ++j) {
cout << symbol; // In ký tự symbol
}
// Sau khi in xong 1 hàng, xuống dòng
cout << endl;
}
return 0;
}
Giải thích:
- Vòng lặp ngoài
for (int i = 0; i < height; ++i)
vẫn điều khiển số hàng, chạyheight
lần. Biếni
sẽ lần lượt là 0, 1, 2, ...,height-1
. - Điểm mấu chốt là vòng lặp trong:
for (int j = 0; j <= i; ++j)
. Điều kiện lặpj <= i
cho thấy rằng số lần lặp của vòng trong phụ thuộc vào giá trị hiện tại củai
.- Khi
i = 0
(hàng đầu tiên), vòng trong chạy khij <= 0
, tức là chỉ 1 lần (j=0
). In ra 1 ký tự. - Khi
i = 1
(hàng thứ hai), vòng trong chạy khij <= 1
, tức là 2 lần (j=0
,j=1
). In ra 2 ký tự. - Khi
i = 2
(hàng thứ ba), vòng trong chạy khij <= 2
, tức là 3 lần (j=0
,j=1
,j=2
). In ra 3 ký tự. - ... và cứ thế cho đến khi
i = height - 1
, vòng trong chạyheight
lần.
- Khi
cout << endl;
vẫn đảm bảo xuống dòng sau mỗi hàng.
Kết quả chạy chương trình với height = 6
sẽ là:
#
##
###
####
#####
######
Bằng cách thay đổi điều kiện hoặc phạm vi của vòng lặp trong dựa trên biến của vòng lặp ngoài, bạn có thể tạo ra vô số các hình mẫu khác nhau.
Bài tập thực hành 3: In bảng cửu chương
Vòng lặp lồng nhau không chỉ dùng để in hình. Một ứng dụng cổ điển khác là in ra bảng cửu chương. Để in bảng cửu chương từ 1 đến 10, chúng ta cần một vòng lặp ngoài cho số nhân (từ 1 đến 10) và một vòng lặp trong cho số bị nhân (cũng từ 1 đến 10). Kết quả của phép nhân sẽ được in ra bên trong vòng lặp trong.
Để bảng cửu chương được căn chỉnh đẹp mắt, chúng ta có thể sử dụng setw()
từ thư viện <iomanip>
.
#include <iostream>
#include <iomanip> // Cần cho setw()
int main() {
int max_multiplier = 10; // Bảng cửu chương đến số nào
// Tùy chọn: In tiêu đề cột để dễ nhìn
cout << " |"; // Khoảng trống cho cột đầu tiên
for (int i = 1; i <= max_multiplier; ++i) {
cout << setw(4) << i; // In số cột với độ rộng 4 ký tự
}
cout << endl;
cout << "---+"; // Đường phân cách
for (int i = 0; i < max_multiplier; ++i) {
cout << "----";
}
cout << endl;
// Vòng lặp NGOÀI: Số nhân (từ 1 đến max_multiplier)
for (int i = 1; i <= max_multiplier; ++i) {
// In số nhân ở cột đầu tiên, căn chỉnh
cout << setw(3) << i << "|";
// Vòng lặp TRONG: Số bị nhân (từ 1 đến max_multiplier)
// Chạy cho MỖI số nhân i
for (int j = 1; j <= max_multiplier; ++j) {
// In kết quả i * j, căn chỉnh
cout << setw(4) << (i * j);
}
// Xuống dòng sau khi hoàn thành 1 hàng (bảng cửu chương của số i)
cout << endl;
}
return 0;
}
Giải thích:
- Vòng lặp ngoài
for (int i = 1; i <= max_multiplier; ++i)
chạy từ 1 đến 10 (nếumax_multiplier
là 10). Biếni
đóng vai trò là số nhân. - Trước khi vào vòng lặp trong,
cout << setw(3) << i << "|";
in ra số nhân hiện tại (i
) ở đầu mỗi hàng, được căn chỉnh đẹp mắt. - Vòng lặp trong
for (int j = 1; j <= max_multiplier; ++j)
chạy từ 1 đến 10 cho mỗi giá trị củai
. Biếnj
đóng vai trò là số bị nhân. cout << setw(4) << (i * j);
tính kết quả củai * j
và in ra, cũng được căn chỉnh. Lệnh này nằm trong vòng lặp trong, nên nó sẽ được thực thi 10 * 10 = 100 lần (chomax_multiplier = 10
).cout << endl;
xuống dòng sau khi in xong tất cả các kết quả nhân với sối
hiện tại.- Phần code ở đầu trước vòng lặp ngoài là để in ra tiêu đề cột và đường phân cách, giúp bảng cửu chương dễ đọc hơn.
setw(n)
thiết lập độ rộng tối thiểu cho phần tử tiếp theo được in ra làn
ký tự.
Kết quả sẽ là một bảng cửu chương được căn chỉnh:
| 1 2 3 4 5 6 7 8 9 10
---+----------------------------------------
1| 1 2 3 4 5 6 7 8 9 10
2| 2 4 6 8 10 12 14 16 18 20
3| 3 6 9 12 15 18 21 24 27 30
4| 4 8 12 16 20 24 28 32 36 40
5| 5 10 15 20 25 30 35 40 45 50
6| 6 12 18 24 30 36 42 48 54 60
7| 7 14 21 28 35 42 49 56 63 70
8| 8 16 24 32 40 48 56 64 72 80
9| 9 18 27 36 45 54 63 72 81 90
10| 10 20 30 40 50 60 70 80 90 100
Bài tập này cho thấy vòng lặp lồng nhau rất hữu ích khi bạn cần kết hợp các giá trị từ hai tập hợp khác nhau (số nhân và số bị nhân trong trường hợp này).
Bài tập thực hành 4: Duyệt mảng 2 chiều (Ma trận)
Một trong những ứng dụng quan trọng nhất của vòng lặp lồng nhau là làm việc với các cấu trúc dữ liệu nhiều chiều, điển hình là mảng 2 chiều (hay ma trận). Mảng 2 chiều có hàng và cột, rất giống với cấu trúc của vòng lặp lồng nhau.
Để duyệt qua tất cả các phần tử trong một mảng 2 chiều, chúng ta sẽ sử dụng vòng lặp ngoài để duyệt qua các hàng và vòng lặp trong để duyệt qua các cột trong mỗi hàng đó.
#include <iostream>
int main() {
// Khởi tạo một mảng 2 chiều (ma trận) 3x4
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int rows = 3; // Số hàng
int cols = 4; // Số cột
cout << "Duyet va in cac phan tu cua ma tran:\n";
// Vòng lặp NGOÀI: Duyệt qua các hàng
for (int i = 0; i < rows; ++i) {
// Vòng lặp TRONG: Duyệt qua các cột trong hàng HIỆN TẠI (i)
for (int j = 0; j < cols; ++j) {
// Truy cập và in phần tử tại hàng 'i', cột 'j'
cout << matrix[i][j] << " ";
}
// Xuống dòng sau khi in xong 1 hàng
cout << endl;
}
// Ví dụ khác: Tính tổng các phần tử trong ma trận
int sum = 0;
cout << "\nTinh tong cac phan tu cua ma tran:\n";
for (int i = 0; i < rows; ++i) { // Duyệt qua hàng
for (int j = 0; j < cols; ++j) { // Duyệt qua cột
sum += matrix[i][j]; // Cộng giá trị của phần tử hiện tại vào tổng
}
}
cout << "Tong cac phan tu: " << sum << endl;
return 0;
}
Giải thích:
- Chúng ta khai báo một mảng 2 chiều
matrix
có 3 hàng và 4 cột. - Vòng lặp ngoài
for (int i = 0; i < rows; ++i)
chạy từi = 0
đếnrows - 1
. Mỗi giá trị củai
tương ứng với chỉ số của một hàng. - Vòng lặp trong
for (int j = 0; j < cols; ++j)
chạy từj = 0
đếncols - 1
cho mỗi giá trị củai
. Mỗi giá trị củaj
tương ứng với chỉ số của một cột trong hàng hiện tại. matrix[i][j]
truy cập đến phần tử tại hàng có chỉ sối
và cột có chỉ sốj
. Lệnh này nằm trong vòng lặp trong, nên nó sẽ được thực thi cho mọi cặp(i, j)
, tức là duyệt qua tất cả các phần tử của ma trận.- Ví dụ tính tổng minh họa cách bạn có thể xử lý từng phần tử khi duyệt qua mảng 2 chiều bằng vòng lặp lồng nhau.
Kết quả chương trình:
Duyet va in cac phan tu cua ma tran:
1 2 3 4
5 6 7 8
9 10 11 12
Tinh tong cac phan tu cua ma tran:
Tong cac phan tu: 78
Mảng 2 chiều là một cấu trúc dữ liệu cực kỳ phổ biến, đặc biệt trong các bài toán liên quan đến bảng biểu, hình ảnh, hoặc các trò chơi như cờ vua, caro. Vòng lặp lồng nhau là công cụ không thể thiếu để làm việc với chúng.
Những lưu ý khi làm việc với vòng lặp lồng nhau
- Hiểu luồng thực thi: Luôn nhớ rằng vòng lặp bên trong chạy hoàn toàn cho mỗi lần lặp của vòng lặp bên ngoài. Điều này giúp bạn dự đoán được tổng số thao tác và kết quả.
- Biến đếm: Các biến đếm của vòng lặp (ví dụ:
i
,j
) thường là độc lập với nhau. Biếnj
trong vòng trong được khởi tạo lại từ đầu cho mỗi lần lặp mới của vòng ngoài. break
vàcontinue
: Bạn có thể sử dụngbreak
hoặccontinue
bên trong vòng lặp lồng nhau.break
sẽ thoát khỏi vòng lặp gần nhất chứa nó (thường là vòng lặp trong).continue
sẽ bỏ qua các lệnh còn lại trong lần lặp hiện tại của vòng lặp gần nhất và chuyển sang lần lặp tiếp theo của nó.- Hiệu suất: Vòng lặp lồng nhau làm tăng đáng kể số lượng thao tác. Nếu bạn có hai vòng lặp lồng nhau chạy
N
lần vàM
lần, tổng số thao tác là khoảngN * M
. Với ba vòng lặp lồng nhau, sẽ làN * M * K
, v.v... Hãy cẩn trọng với việc lồng quá nhiều cấp độ hoặc chạy trên tập dữ liệu rất lớn, vì nó có thể ảnh hưởng đến hiệu suất chương trình.
Tự thực hành thêm
Để thành thạo vòng lặp lồng nhau, không gì tốt hơn là tự tay viết code và thử nghiệm. Hãy thử sức với những bài tập sau:
- In hình tam giác vuông ngược (số ký tự giảm dần theo từng hàng).
- In hình tam giác cân rỗng hoặc đặc.
- In hình thoi.
- Trên một mảng 2 chiều, hãy tìm giá trị lớn nhất, nhỏ nhất.
- Trên một mảng 2 chiều, hãy tính tổng các phần tử trên đường chéo chính.
- (Nâng cao) In hình bàn cờ vua (xen kẽ 2 ký tự khác nhau).
Hãy cố gắng tự giải các bài tập này trước khi tìm kiếm đáp án. Việc "vật lộn" với code chính là cách tốt nhất để hiểu sâu vấn đề.
Chúc các bạn thực hành hiệu quả và làm chủ được kỹ thuật vòng lặp lồng nhau này!
Comments