1. Mảng răng cưa khác mảng hai chiều
Chúng ta đã đến chủ đề mà nhiều người gọi là “mảng răng cưa” — tiếng Anh là jagged arrays. Khác với mảng hai chiều, mảng răng cưa cho phép lưu các cột có độ dài khác nhau. Tưởng tượng như một khu nhà, mỗi tòa có số căn hộ riêng — ở một tòa có 5 căn, tòa khác có 20 căn, còn tòa thứ ba chỉ có một căn.
Mảng răng cưa là mảng mà mỗi phần tử của nó là một mảng, và các mảng lồng nhau (còn gọi là “mảng con”) có thể có độ dài khác nhau.
Khác biệt chính:
- Trong mảng hai chiều, mỗi “hàng” (và mỗi “cột”) có cùng số phần tử. Ví dụ: int[][] grid = new int[3][5]; — luôn có 3 hàng, mỗi hàng 5 phần tử.
- Trong mảng răng cưa, mỗi hàng có thể có độ dài khác nhau! Ví dụ: int[][] jagged = new int[3][]; — và chỉ sau đó ta khởi tạo từng hàng (mảng con) theo cách riêng.
Minh họa trực quan:
Mảng hai chiều (3x3):
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
├───┼───┼───┤
│ 4 │ 5 │ 6 │
├───┼───┼───┤
│ 7 │ 8 │ 9 │
└───┴───┴───┘
Mảng răng cưa (độ dài khác nhau):
┌───┬───┐
│ 1 │ 2 │
├───┼───┼───┬───┐
│ 3 │ 4 │ 5 │ 6 │
├───┼───┴───┴───┘
│ 7 │
└───┘
2. Cú pháp khai báo và khởi tạo mảng răng cưa
Khai báo mảng răng cưa không hề khó hơn các kiểu trước! Đừng sợ dấu ngoặc vuông kép:
int[][] jaggedArray = new int[3][];
Điều đó có nghĩa là ta có một mảng gồm 3 phần tử, và mỗi phần tử cũng là một mảng int. Nhưng các mảng bên trong vẫn chưa được tạo! Để dễ hiểu hơn, hãy đi sâu hơn một chút.
Khởi tạo mảng răng cưa theo từng bước
Bước 1 — tạo mảng chính (bên ngoài):
int[][] jaggedArray = new int[3][];
Giờ ta có 3 “hàng”, nhưng tất cả hiện đều bằng null.
Bước 2 — tạo và cấp phát các mảng bên trong (mảng con):
Ví dụ, cho hàng thứ nhất dài 2, hàng thứ hai — 4, hàng thứ ba — 3:
jaggedArray[0] = new int[2]; // 2 phần tử ở hàng đầu tiên
jaggedArray[1] = new int[4]; // 4 phần tử ở hàng thứ hai
jaggedArray[2] = new int[3]; // 3 phần tử ở hàng thứ ba
Bước 3 — gán giá trị:
Các mảng bên trong chính là mảng bình thường! Ví dụ:
jaggedArray[0][0] = 1;
jaggedArray[0][1] = 2;
jaggedArray[1][0] = 3;
jaggedArray[1][1] = 4;
jaggedArray[1][2] = 5;
jaggedArray[1][3] = 6;
jaggedArray[2][0] = 7;
jaggedArray[2][1] = 8;
jaggedArray[2][2] = 9;
Khởi tạo ngắn gọn mảng răng cưa
Có thể tạo và điền giá trị ngay nếu bạn biết trước các giá trị:
int[][] jaggedArray = new int[][]
{
new int[] { 1, 2 },
new int[] { 3, 4, 5, 6 },
new int[] { 7, 8, 9 }
};
Hoặc ngắn hơn một chút, bỏ qua kiểu của các mảng bên trong:
int[][] jaggedArray =
{
{ 1, 2 },
{ 3, 4, 5, 6 },
{ 7, 8, 9 }
};
3. Duyệt và làm việc với mảng răng cưa
Duyệt mảng răng cưa không khó hơn mảng hai chiều, nhưng lúc này vòng ngoài đi qua các hàng, còn vòng trong đi qua các phần tử của hàng (có thể có độ dài khác nhau):
for (int i = 0; i < jaggedArray.length; i++)
{
System.out.println("Dòng " + i + ":");
for (int j = 0; j < jaggedArray[i].length; j++)
{
System.out.print(jaggedArray[i][j] + " ");
}
System.out.println();
}
Kết quả trên màn hình:
Dòng 0:
1 2
Dòng 1:
3 4 5 6
Dòng 2:
7 8 9
Có thể dùng for-each để khỏi phải nghĩ về chỉ số:
for (int[] row : jaggedArray)
{
for (int value : row)
{
System.out.print(value + " ");
}
System.out.println();
}
4. Tình huống sử dụng điển hình cho mảng răng cưa
Khi nào mảng răng cưa hữu ích hơn mảng hai chiều?
- Khi bạn lưu cho mỗi người dùng số lượng dữ liệu khác nhau: điểm theo môn, mua sắm, bình luận, v.v.
- Khi dữ liệu có cấu trúc tam giác hoặc bậc thang (ví dụ, để in hình kim tự tháp, tam giác Pascal, v.v.).
- Khi muốn tiết kiệm bộ nhớ: trong mảng hai chiều, các hàng là cố định, còn với mảng răng cưa — chỉ cấp đúng số phần tử cần thiết.
Ví dụ thực tế: quản lý điểm của sinh viên
Giả sử có ba sinh viên, và đây là điểm của họ cho các bài tập khác nhau trong môn toán:
| Sinh viên | Điểm |
|---|---|
| 0 | 5, 4 |
| 1 | 3, 4, 4 |
| 2 | 5 |
Khai báo mảng như sau:
int[][] studentMarks = new int[3][];
studentMarks[0] = new int[] { 5, 4 }; // Sinh viên thứ nhất — 2 điểm
studentMarks[1] = new int[] { 3, 4, 4 }; // Sinh viên thứ hai — 3 điểm
studentMarks[2] = new int[] { 5 }; // Sinh viên thứ ba — 1 điểm
In ra điểm của từng sinh viên:
for (int i = 0; i < studentMarks.length; i++)
{
System.out.print("Sinh viên " + i + ": ");
for (int j = 0; j < studentMarks[i].length; j++)
{
System.out.print(studentMarks[i][j] + " ");
}
System.out.println();
}
Sử dụng mảng răng cưa với các kiểu khác
Mảng răng cưa có thể là mảng của bất cứ thứ gì: chuỗi, mảng các mảng khác (sâu hơn!), thậm chí các đối tượng do bạn tự định nghĩa.
Ví dụ: mảng chuỗi
String[][] groups = {
{ "Ivan", "Pyotr" },
{ "Mariya", "Aleksey", "Sergey" },
{ "Vasilisa" }
};
5. Mảng ba chiều và mảng đa chiều
Thêm một điều thú vị về mảng mà có lẽ bạn đã đoán. Nếu đã có thể tạo mảng hai chiều, vậy có thể tạo mảng ba chiều không?
Có, bạn có thể tạo mảng với bất kỳ số chiều nào. Những mảng như vậy được gọi là mảng đa chiều.
Cách khai báo mảng đa chiều
Chỉ cần liệt kê số kích thước cần thiết trong các cặp ngoặc:
int[][][] cube = new int[2][3][4]; // 2 “lớp”, 3 hàng, 4 cột
cube[0][1][2] = 99;
Ở đây ta có mảng ba chiều:
- 2 phần tử theo tọa độ thứ nhất,
- 3 — theo tọa độ thứ hai,
- 4 — theo tọa độ thứ ba.
Mảng như vậy là một “khối lập phương” dữ liệu lớn được sắp xếp liên tiếp.
Duyệt mảng ba chiều
Truy cập phần tử cần đủ mọi chỉ số ngay lập tức:
for (int i = 0; i < cube.length; i++)
{
for (int j = 0; j < cube[i].length; j++)
{
for (int k = 0; k < cube[i][j].length; k++)
{
System.out.print(cube[i][j][k] + " ");
}
System.out.println();
}
System.out.println("---");
}
- Chỉ số bắt đầu từ 0, như thường lệ trong Java.
- Tổng số phần tử trong mảng này là 2 × 3 × 4 = 24.
Ví dụ thực tế của mảng đa chiều
- 2D — bảng, bàn cờ, hình ảnh.
- 3D — “khối” trong đồ họa máy tính, dữ liệu cho tính toán khoa học (ví dụ, nhiệt độ ở các điểm khác nhau theo không gian và thời gian).
- 4D trở lên — hiếm dùng, nhưng xuất hiện trong toán học nâng cao, mô phỏng, học máy, v.v.
6. Lỗi thường gặp khi làm việc với mảng đa chiều
Lỗi số 1: Vượt ra ngoài phạm vi mảng
Lỗi phổ biến nhất — cố truy cập vào phần tử không tồn tại, ví dụ:
int[][] arr = new int[2][3];
arr[2][0] = 5; // Lỗi! Không có hàng có chỉ số 2 (chỉ có 0 và 1)
arr[0][3] = 7; // Lỗi! Không có cột có chỉ số 3 (chỉ có 0, 1, 2)
Khi truy cập như vậy, chương trình sẽ ném ArrayIndexOutOfBoundsException. Luôn kiểm tra rằng chỉ số trong phạm vi hợp lệ: từ 0 đến length - 1.
Lỗi số 2: Hàng chưa được khởi tạo trong mảng răng cưa
Nếu tạo mảng răng cưa mà quên khởi tạo các mảng bên trong, khi truy cập sẽ gặp NullPointerException:
int[][] jagged = new int[3][];
jagged[0][0] = 5; // Lỗi! jagged[0] == null
Trước hết cần tạo mảng bên trong: jagged[0] = new int[2];
Lỗi số 3: Sử dụng sai độ dài của mảng
Dễ nhầm giữa matrix.length (số hàng) và matrix[0].length (số cột). Đặc biệt thường gặp khi sao chép, duyệt, hoặc cộng theo cột.
Lỗi số 4: Cho rằng mọi hàng đều cùng độ dài
Trong mảng răng cưa, các hàng có thể có độ dài khác nhau! Nếu bạn viết matrix[i][j], hãy đảm bảo rằng j < matrix[i].length.
Lỗi số 5: Lẫn lộn thứ tự chỉ số
Đôi khi nhầm lẫn rằng chỉ số thứ nhất là hàng rồi mới đến cột: matrix[row][column]. Không phải ngược lại!
GO TO FULL VERSION