Mảng đa chiều
Bài trước: Mảng (Arrays) — Đã học về mảng 1 chiều và các thao tác cơ bản. Bài này sẽ học về mảng đa chiều để làm việc với dữ liệu dạng bảng và ma trận.
Sau bài này, bạn sẽ:
- Hiểu mảng 2 chiều là mảng của các mảng (rows × columns)
- Tạo và duyệt mảng 2 chiều với nested loops
- Sử dụng jagged arrays (mảng răng cưa) với rows có độ dài khác nhau
- Áp dụng mảng 2D cho bài toán thực tế: ma trận, game board, bảng điểm
- Làm việc với mảng 3 chiều cho dữ liệu không gian và RGB images
Mảng đa chiều (multidimensional arrays) cho phép lưu trữ dữ liệu dạng bảng, ma trận, hoặc các cấu trúc phức tạp hơn. Đây là công cụ quan trọng trong nhiều ứng dụng như game programming, xử lý hình ảnh, và scientific computing.
Mảng 2 chiều
Mảng 2 chiều là mảng của các mảng. Có thể hình dung như một bảng với rows (hàng) và columns (cột).
Khai báo và Khởi tạo
// Cách 1: Khai báo + Tạo với size
int[][] matrix = new int[3][4]; // 3 rows, 4 columns
// Cách 2: Khởi tạo trực tiếp với giá trị
int[][] numbers = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Cách 3: Khai báo rồi khởi tạo
String[][] names;
names = new String[2][3];
// Cách 4: Array initializer
int[][] data = new int[][]{{1, 2}, {3, 4}, {5, 6}};
Trực quan hóa mảng 2 chiều
int[][] matrix = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
/*
Trực quan:
Col 0 Col 1 Col 2 Col 3
Row 0 1 2 3 4
Row 1 5 6 7 8
Row 2 9 10 11 12
matrix[0][0] = 1
matrix[0][3] = 4
matrix[1][2] = 7
matrix[2][3] = 12
*/
Truy cập elements
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Truy cập element
System.out.println(matrix[0][0]); // 1 (row 0, col 0)
System.out.println(matrix[1][2]); // 6 (row 1, col 2)
System.out.println(matrix[2][1]); // 8 (row 2, col 1)
// Thay đổi element
matrix[1][1] = 50;
System.out.println(matrix[1][1]); // 50
// Lấy số rows
int rows = matrix.length; // 3
// Lấy số columns của row đầu tiên
int cols = matrix[0].length; // 3
// Lấy một row hoàn chỉnh
int[] firstRow = matrix[0]; // {1, 2, 3}
System.out.println(Arrays.toString(firstRow));
Duyệt mảng 2 chiều
1. Nested For Loop
int[][] matrix = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Duyệt toàn bộ matrix
for (int i = 0; i < matrix.length; i++) { // Duyệt rows
for (int j = 0; j < matrix[i].length; j++) { // Duyệt columns
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
/* Output:
1 2 3 4
5 6 7 8
9 10 11 12
*/
2. Enhanced For Loop (For-each)
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// For-each loop
for (int[] row : matrix) { // Mỗi row là 1 array
for (int value : row) { // Mỗi value trong row
System.out.print(value + " ");
}
System.out.println();
}
In mảng 2 chiều
import java.util.Arrays;
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Cách 1: Dùng loop
for (int[] row : matrix) {
System.out.println(Arrays.toString(row));
}
/* Output:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
*/
// Cách 2: Dùng Arrays.deepToString()
System.out.println(Arrays.deepToString(matrix));
// [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Jagged Arrays (Mảng răng cưa)
Jagged array là mảng 2 chiều mà các rows có thể có độ dài khác nhau.
Tạo Jagged Array
// Cách 1: Khởi tạo trực tiếp
int[][] jagged = {
{1, 2, 3, 4},
{5, 6},
{7, 8, 9}
};
// Cách 2: Tạo từng row riêng
int[][] jagged2 = new int[3][]; // 3 rows, columns chưa xác định
jagged2[0] = new int[4]; // Row 0: 4 columns
jagged2[1] = new int[2]; // Row 1: 2 columns
jagged2[2] = new int[3]; // Row 2: 3 columns
// Cách 3: Tạo và gán giá trị
int[][] jagged3 = new int[3][];
jagged3[0] = new int[]{1, 2, 3, 4};
jagged3[1] = new int[]{5, 6};
jagged3[2] = new int[]{7, 8, 9};
Trực quan hóa Jagged Array
int[][] jagged = {
{1, 2, 3, 4},
{5, 6},
{7, 8, 9}
};
/*
Trực quan:
Row 0: [1, 2, 3, 4]
Row 1: [5, 6]
Row 2: [7, 8, 9]
jagged.length = 3 (số rows)
jagged[0].length = 4
jagged[1].length = 2
jagged[2].length = 3
*/
Duyệt Jagged Array
int[][] jagged = {
{1, 2, 3, 4},
{5, 6},
{7, 8, 9}
};
// PHẢI dùng jagged[i].length vì mỗi row có độ dài khác nhau
for (int i = 0; i < jagged.length; i++) {
for (int j = 0; j < jagged[i].length; j++) { // Lưu ý: jagged[i].length
System.out.print(jagged[i][j] + " ");
}
System.out.println();
}
// For-each vẫn hoạt động tốt
for (int[] row : jagged) {
for (int value : row) {
System.out.print(value + " ");
}
System.out.println();
}
Ví dụ thực tế
1. Bảng điểm sinh viên
import java.util.Arrays;
public class GradeBook {
public static void main(String[] args) {
// Rows: sinh viên, Columns: môn học
// [Math, Physics, Chemistry]
int[][] grades = {
{85, 90, 78}, // Student 0
{92, 88, 95}, // Student 1
{76, 82, 88}, // Student 2
{95, 91, 89} // Student 3
};
String[] studentNames = {"Alice", "Bob", "Charlie", "David"};
String[] subjects = {"Math", "Physics", "Chemistry"};
// In bảng điểm
System.out.println("=== GRADE BOOK ===");
System.out.printf("%-10s", "Student");
for (String subject : subjects) {
System.out.printf("%10s", subject);
}
System.out.println();
System.out.println("-".repeat(40));
for (int i = 0; i < grades.length; i++) {
System.out.printf("%-10s", studentNames[i]);
for (int j = 0; j < grades[i].length; j++) {
System.out.printf("%10d", grades[i][j]);
}
System.out.println();
}
// Tính điểm trung bình của mỗi sinh viên
System.out.println("\n=== AVERAGES ===");
for (int i = 0; i < grades.length; i++) {
double avg = calculateAverage(grades[i]);
System.out.printf("%s: %.2f%n", studentNames[i], avg);
}
// Tính điểm trung bình của mỗi môn
System.out.println("\n=== SUBJECT AVERAGES ===");
for (int j = 0; j < subjects.length; j++) {
double avg = calculateSubjectAverage(grades, j);
System.out.printf("%s: %.2f%n", subjects[j], avg);
}
}
public static double calculateAverage(int[] scores) {
int sum = 0;
for (int score : scores) {
sum += score;
}
return (double) sum / scores.length;
}
public static double calculateSubjectAverage(int[][] grades, int subjectIndex) {
int sum = 0;
for (int i = 0; i < grades.length; i++) {
sum += grades[i][subjectIndex];
}
return (double) sum / grades.length;
}
}
2. Tic-Tac-Toe Board
public class TicTacToe {
private char[][] board;
private static final char EMPTY = '-';
private static final char PLAYER_X = 'X';
private static final char PLAYER_O = 'O';
public TicTacToe() {
board = new char[3][3];
initializeBoard();
}
public void initializeBoard() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
board[i][j] = EMPTY;
}
}
}
public void printBoard() {
System.out.println("\n 0 1 2");
for (int i = 0; i < 3; i++) {
System.out.print(i + " ");
for (int j = 0; j < 3; j++) {
System.out.print(board[i][j] + " ");
}
System.out.println();
}
}
public boolean makeMove(int row, int col, char player) {
if (row < 0 || row >= 3 || col < 0 || col >= 3) {
return false; // Out of bounds
}
if (board[row][col] != EMPTY) {
return false; // Cell already occupied
}
board[row][col] = player;
return true;
}
public boolean checkWinner(char player) {
// Check rows
for (int i = 0; i < 3; i++) {
if (board[i][0] == player && board[i][1] == player && board[i][2] == player) {
return true;
}
}
// Check columns
for (int j = 0; j < 3; j++) {
if (board[0][j] == player && board[1][j] == player && board[2][j] == player) {
return true;
}
}
// Check diagonals
if (board[0][0] == player && board[1][1] == player && board[2][2] == player) {
return true;
}
if (board[0][2] == player && board[1][1] == player && board[2][0] == player) {
return true;
}
return false;
}
public boolean isBoardFull() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j] == EMPTY) {
return false;
}
}
}
return true;
}
public static void main(String[] args) {
TicTacToe game = new TicTacToe();
// Simulate a game
game.makeMove(0, 0, PLAYER_X);
game.makeMove(0, 1, PLAYER_O);
game.makeMove(1, 1, PLAYER_X);
game.makeMove(0, 2, PLAYER_O);
game.makeMove(2, 2, PLAYER_X);
game.printBoard();
if (game.checkWinner(PLAYER_X)) {
System.out.println("Player X wins!");
} else if (game.checkWinner(PLAYER_O)) {
System.out.println("Player O wins!");
} else if (game.isBoardFull()) {
System.out.println("It's a draw!");
}
}
}
3. Ma trận (Matrix Operations)
public class MatrixOperations {
// Cộng 2 ma trận
public static int[][] addMatrices(int[][] a, int[][] b) {
if (a.length != b.length || a[0].length != b[0].length) {
throw new IllegalArgumentException("Matrices must have same dimensions");
}
int rows = a.length;
int cols = a[0].length;
int[][] result = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
result[i][j] = a[i][j] + b[i][j];
}
}
return result;
}
// Nhân 2 ma trận
public static int[][] multiplyMatrices(int[][] a, int[][] b) {
if (a[0].length != b.length) {
throw new IllegalArgumentException("Invalid matrix dimensions for multiplication");
}
int rows = a.length;
int cols = b[0].length;
int common = a[0].length;
int[][] result = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
for (int k = 0; k < common; k++) {
result[i][j] += a[i][k] * b[k][j];
}
}
}
return result;
}
// Transpose ma trận (chuyển vị)
public static int[][] transpose(int[][] matrix) {
int rows = matrix.length;
int cols = matrix[0].length;
int[][] result = new int[cols][rows];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
result[j][i] = matrix[i][j];
}
}
return result;
}
// In ma trận
public static void printMatrix(int[][] matrix) {
for (int[] row : matrix) {
for (int value : row) {
System.out.printf("%4d", value);
}
System.out.println();
}
}
public static void main(String[] args) {
int[][] a = {
{1, 2, 3},
{4, 5, 6}
};
int[][] b = {
{7, 8, 9},
{10, 11, 12}
};
// Cộng ma trận
System.out.println("A + B:");
int[][] sum = addMatrices(a, b);
printMatrix(sum);
// Transpose
System.out.println("\nTranspose of A:");
int[][] transposed = transpose(a);
printMatrix(transposed);
// Nhân ma trận
int[][] c = {
{1, 2},
{3, 4},
{5, 6}
};
System.out.println("\nA × C:");
int[][] product = multiplyMatrices(a, c);
printMatrix(product);
}
}
Mảng 3 chiều
Mảng 3 chiều có thể hình dung như một "cube" hay "book of pages".
// Khai báo mảng 3 chiều
int[][][] cube = new int[3][4][5]; // 3 "pages", mỗi page có 4 rows × 5 cols
// Khởi tạo với giá trị
int[][][] data = {
{
{1, 2},
{3, 4}
},
{
{5, 6},
{7, 8}
}
};
// Truy cập element
System.out.println(data[0][0][0]); // 1
System.out.println(data[1][1][1]); // 8
// Duyệt mảng 3 chiều
for (int i = 0; i < data.length; i++) { // Pages
System.out.println("Page " + i + ":");
for (int j = 0; j < data[i].length; j++) { // Rows
for (int k = 0; k < data[i][j].length; k++) { // Columns
System.out.print(data[i][j][k] + " ");
}
System.out.println();
}
System.out.println();
}
// In với Arrays.deepToString()
System.out.println(Arrays.deepToString(data));
Ví dụ thực tế: RGB Image (3D array)
public class ImageProcessor {
public static void main(String[] args) {
// Image: 2×2 pixels, mỗi pixel có 3 channels (R, G, B)
int[][][] image = {
{
{255, 0, 0}, // Pixel (0,0): Red
{0, 255, 0} // Pixel (0,1): Green
},
{
{0, 0, 255}, // Pixel (1,0): Blue
{255, 255, 0} // Pixel (1,1): Yellow
}
};
// Truy cập RGB values của pixel (0, 0)
int red = image[0][0][0];
int green = image[0][0][1];
int blue = image[0][0][2];
System.out.printf("Pixel (0,0) - R:%d, G:%d, B:%d%n", red, green, blue);
// Convert to grayscale (trung bình RGB)
for (int i = 0; i < image.length; i++) {
for (int j = 0; j < image[i].length; j++) {
int r = image[i][j][0];
int g = image[i][j][1];
int b = image[i][j][2];
int gray = (r + g + b) / 3;
System.out.printf("Pixel (%d,%d) - Grayscale: %d%n", i, j, gray);
}
}
}
}
Arrays.deepToString() và deepEquals()
Với mảng đa chiều, phải dùng deepToString() và deepEquals().
import java.util.Arrays;
int[][] matrix1 = {
{1, 2, 3},
{4, 5, 6}
};
int[][] matrix2 = {
{1, 2, 3},
{4, 5, 6}
};
// toString() vs deepToString()
System.out.println(Arrays.toString(matrix1)); // [[I@15db9742, [I@6d06d69c] - Không hữu ích
System.out.println(Arrays.deepToString(matrix1)); // [[1, 2, 3], [4, 5, 6]] - OK
// equals() vs deepEquals()
System.out.println(Arrays.equals(matrix1, matrix2)); // false - Chỉ so sánh references
System.out.println(Arrays.deepEquals(matrix1, matrix2)); // true - So sánh nội dung
Java 2D Array — Không liên tục trong Memory
Trong C/C++, mảng 2D được lưu liên tục (contiguous) trong memory. Trong Java, mảng 2D là array of references to arrays — mỗi row là một array riêng biệt ở vị trí khác nhau trong heap.
C/C++ 2D array (contiguous):
┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ ← Tất cả liền nhau
└───┴───┴───┴───┴───┴───┴───┴───┴───┘
Java 2D array (array of references):
matrix ──→ ┌────────┐
│ ref[0] │──→ [1, 2, 3] ← array riêng ở vị trí A trong heap
│ ref[1] │──→ [4, 5, 6] ← array riêng ở vị trí B trong heap
│ ref[2] │──→ [7, 8, 9] ← array riêng ở vị trí C trong heap
└────────┘
Hệ quả: Jagged arrays (mảng răng cưa) là tự nhiên trong Java vì mỗi row có thể trỏ đến array với length khác nhau.
Cache Locality — Duyệt theo hàng nhanh hơn theo cột
Vì mỗi row là một array liên tục trong memory, duyệt theo hàng (row-major) tận dụng CPU cache tốt hơn duyệt theo cột:
int[][] matrix = new int[1000][1000];
// ✅ NHANH: Duyệt theo hàng (row-major) — cache-friendly
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 1000; j++) {
matrix[i][j] = i + j; // Truy cập liên tiếp trong cùng 1 row array
}
}
// ❌ CHẬM: Duyệt theo cột (column-major) — cache-unfriendly
for (int j = 0; j < 1000; j++) {
for (int i = 0; i < 1000; i++) {
matrix[i][j] = i + j; // Nhảy giữa các row arrays khác nhau
}
}
Với ma trận lớn, duyệt theo hàng có thể nhanh hơn 2-10x so với duyệt theo cột do ít cache miss hơn.
Arrays.deepToString() vs Arrays.toString()
| Method | Dùng cho | Kết quả với 2D array |
|---|---|---|
Arrays.toString() | Mảng 1 chiều | [[I@15db9742, [I@6d06d69c] (chỉ in reference!) |
Arrays.deepToString() | Mảng nhiều chiều | [[1, 2, 3], [4, 5, 6]] (in nội dung) |
int[][] matrix = {{1, 2}, {3, 4}};
// ❌ toString chỉ in reference của inner arrays
System.out.println(Arrays.toString(matrix));
// [[I@15db9742, [I@6d06d69c]
// ✅ deepToString in nội dung đầy đủ
System.out.println(Arrays.deepToString(matrix));
// [[1, 2], [3, 4]]
// Tương tự cho so sánh:
int[][] a = {{1, 2}, {3, 4}};
int[][] b = {{1, 2}, {3, 4}};
System.out.println(Arrays.equals(a, b)); // false (so sánh references)
System.out.println(Arrays.deepEquals(a, b)); // true (so sánh nội dung)
Bài tập thực hành
Bài 1: Spiral Matrix
Tạo ma trận xoắn ốc.
public class SpiralMatrix {
public static int[][] generateSpiral(int n) {
// TODO: Tạo ma trận n×n với số từ 1 đến n² theo hình xoắn ốc
// Example n=3:
// 1 2 3
// 8 9 4
// 7 6 5
return new int[n][n];
}
public static void main(String[] args) {
int[][] spiral = generateSpiral(4);
for (int[] row : spiral) {
for (int val : row) {
System.out.printf("%3d", val);
}
System.out.println();
}
}
}
Bài 2: Rotate Matrix 90 Degrees
Xoay ma trận vuông 90 độ theo chiều kim đồng hồ.
public class MatrixRotation {
public static void rotate90Clockwise(int[][] matrix) {
// TODO: Rotate in-place
// [1 2 3] [7 4 1]
// [4 5 6] -> [8 5 2]
// [7 8 9] [9 6 3]
}
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
rotate90Clockwise(matrix);
// Print rotated matrix
}
}
Bài 3: Diagonal Sum
Tính tổng đường chéo chính và phụ của ma trận vuông.
public class DiagonalSum {
public static int sumDiagonals(int[][] matrix) {
// TODO: Tính tổng 2 đường chéo
// Lưu ý: Nếu n lẻ, phần tử giữa tính 1 lần
return 0;
}
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
System.out.println(sumDiagonals(matrix)); // 1+5+9+3+5+7 = 30 - 5 = 25
}
}
Bài 4: Search in 2D Matrix
Tìm kiếm trong ma trận đã sort (row và column đều tăng dần).
public class MatrixSearch {
public static boolean search(int[][] matrix, int target) {
// TODO: Implement efficient search
// Matrix:
// [1, 4, 7, 11]
// [2, 5, 8, 12]
// [3, 6, 9, 16]
// [10, 13, 14, 17]
return false;
}
public static void main(String[] args) {
int[][] matrix = {
{1, 4, 7, 11},
{2, 5, 8, 12},
{3, 6, 9, 16},
{10, 13, 14, 17}
};
System.out.println(search(matrix, 5)); // true
System.out.println(search(matrix, 20)); // false
}
}
Bài 5: Pascal's Triangle
Tạo tam giác Pascal với n hàng.
public class PascalTriangle {
public static int[][] generatePascal(int n) {
// TODO: Generate Pascal's Triangle
// n=5:
// 1
// 1 1
// 1 2 1
// 1 3 3 1
// 1 4 6 4 1
return new int[n][];
}
public static void main(String[] args) {
int[][] pascal = generatePascal(5);
for (int[] row : pascal) {
for (int val : row) {
System.out.print(val + " ");
}
System.out.println();
}
}
}
Bài 6: Sudoku Validator
Kiểm tra một bảng Sudoku 9×9 có hợp lệ không.
public class SudokuValidator {
public static boolean isValidSudoku(int[][] board) {
// TODO: Validate Sudoku board
// - Mỗi row chứa 1-9 không lặp
// - Mỗi column chứa 1-9 không lặp
// - Mỗi sub-box 3×3 chứa 1-9 không lặp
return false;
}
public static void main(String[] args) {
int[][] board = {
{5,3,0,0,7,0,0,0,0},
{6,0,0,1,9,5,0,0,0},
{0,9,8,0,0,0,0,6,0},
{8,0,0,0,6,0,0,0,3},
{4,0,0,8,0,3,0,0,1},
{7,0,0,0,2,0,0,0,6},
{0,6,0,0,0,0,2,8,0},
{0,0,0,4,1,9,0,0,5},
{0,0,0,0,8,0,0,7,9}
};
System.out.println(isValidSudoku(board));
}
}
Tổng kết
- Mảng 2 chiều là mảng của các mảng, có thể hình dung như bảng rows × columns
- Truy cập element:
matrix[row][column] matrix.length= số rows,matrix[0].length= số columns- Jagged arrays cho phép mỗi row có độ dài khác nhau
- Duyệt mảng 2 chiều cần nested loops
- Dùng
Arrays.deepToString()để in mảng đa chiều - Dùng
Arrays.deepEquals()để so sánh nội dung mảng đa chiều - Mảng 3 chiều thường dùng cho dữ liệu không gian hoặc RGB images
- Ứng dụng thực tế: ma trận, bảng điểm, game boards, xử lý ảnh