Bài 9.2. Các kỹ thuật duyệt mảng 2 chiều

Chào mừng các bạn đã quay trở lại với series blog về Cấu trúc dữ liệu và Giải thuật của FullhouseDev! Sau khi tìm hiểu về mảng một chiều, chúng ta cùng nâng cấp lên một cấu trúc dữ liệu phổ biến và mạnh mẽ hơn: mảng 2 chiều, hay còn gọi là ma trận.

Mảng 2 chiều cho phép chúng ta lưu trữ dữ liệu dưới dạng bảng (table) hoặc lưới (grid), rất hữu ích trong việc biểu diễn các mối quan hệ không gian 2D như bản đồ, hình ảnh pixel, bảng tính, hoặc trạng thái của một số trò chơi (ví dụ: cờ vua, caro). Để làm việc với dữ liệu trong mảng 2 chiều, một thao tác cơ bản và quan trọng là duyệt (traversal) qua tất cả các phần tử của nó.

Tưởng chừng việc duyệt mảng 2 chiều chỉ đơn giản là dùng hai vòng lặp lồng nhau, nhưng thực tế có nhiều kỹ thuật duyệt khác nhau, mỗi kỹ thuật lại phù hợp với những mục đích và bài toán cụ thể. Việc nắm vững các kỹ thuật này sẽ giúp bạn xử lý dữ liệu 2D hiệu quả và linh hoạt hơn rất nhiều.

Trong bài viết này, chúng ta sẽ cùng nhau khám phá các kỹ thuật duyệt mảng 2 chiều từ cơ bản đến phức tạp, cùng với các ví dụ minh họa chi tiết bằng ngôn ngữ C++.

Hãy cùng bắt đầu hành trình khám phá thế giới 2D này nhé!

Duyệt Theo Hàng (Row-by-Row Traversal)

Đây là phương pháp duyệt mảng 2 chiều phổ biến nhấttự nhiên nhất. Nó mô phỏng cách chúng ta đọc một cuốn sách: đi hết hàng này rồi mới sang hàng kế tiếp.

  • Ý tưởng: Chúng ta sẽ sử dụng hai vòng lặp lồng nhau. Vòng lặp ngoài sẽ duyệt qua từng hàng của mảng (từ hàng 0 đến hàng cuối cùng), và vòng lặp trong sẽ duyệt qua từng cột trong hàng hiện tại (từ cột 0 đến cột cuối cùng).

Thứ tự duyệt sẽ là: (0,0), (0,1), ..., (0,cols-1), (1,0), (1,1), ..., (1,cols-1), ..., (rows-1, 0), ..., (rows-1, cols-1).

Hãy xem ví dụ C++ minh họa:

#include <iostream>
#include <vector>

int main() {
    // Khởi tạo mảng 2 chiều (ví dụ 3x3)
    std::vector<std::vector<int>> matrix = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    int rows = matrix.size(); // Số hàng
    if (rows == 0) { // Xử lý trường hợp mảng rỗng
        std::cout << "Mang rong!" << std::endl;
        return 0;
    }
    int cols = matrix[0].size(); // Số cột (giả sử tất cả các hàng đều có cùng số cột)

    std::cout << "--- Duyet theo hang (Row-by-Row Traversal) ---" << std::endl;
    // Vong lap ngoai duyet qua cac hang (chi so i)
    for (int i = 0; i < rows; ++i) {
        // Vong lap trong duyet qua cac cot trong hang hien tai (chi so j)
        for (int j = 0; j < cols; ++j) {
            std::cout << matrix[i][j] << " "; // Truy cap phan tu tai (i, j)
        }
        // Xuong dong sau khi ket thuc mot hang
        std::cout << std::endl;
    }
    std::cout << std::endl;

    return 0;
}

Giải thích code:

  • Chúng ta sử dụng std::vector<std::vector<int>> để tạo mảng 2 chiều động, có thể thay đổi kích thước. Đây là cách phổ biến trong C++ hiện đại.
  • rows = matrix.size(); lấy số lượng vector con bên trong, chính là số hàng.
  • cols = matrix[0].size(); lấy kích thước của vector con đầu tiên, chính là số cột. (Chúng ta cần đảm bảo mảng không rỗng trước khi truy cập matrix[0]).
  • Vòng lặp for (int i = 0; i < rows; ++i) điều khiển việc chuyển từ hàng này sang hàng khác. Biến i là chỉ số hàng hiện tại, chạy từ 0 đến rows - 1.
  • Vòng lặp for (int j = 0; j < cols; ++j) lồng bên trong điều khiển việc duyệt qua các cột của hàng i. Biến j là chỉ số cột hiện tại, chạy từ 0 đến cols - 1.
  • matrix[i][j] là cách truy cập phần tử tại hàng có chỉ số i và cột có chỉ số j.
  • Lệnh std::cout << std::endl; sau vòng lặp trong giúp in các phần tử của mỗi hàng trên một dòng riêng biệt, dễ hình dung hơn thứ tự duyệt.

Kết quả của đoạn code trên sẽ in ra ma trận theo đúng cấu trúc ban đầu:

--- Duyet theo hang (Row-by-Row Traversal) ---
1 2 3
4 5 6
7 8 9

Kỹ thuật này đơn giản, hiệu quả, và thường là mặc định khi bạn cần xử lý từng phần tử theo thứ tự đọc thông thường.

Duyệt Theo Cột (Column-by-Column Traversal)

Đây là phương pháp đối lập với duyệt theo hàng. Thay vì đi hết hàng này sang hàng khác, chúng ta sẽ đi hết cột này sang cột khác.

  • Ý tưởng: Vẫn sử dụng hai vòng lặp lồng nhau, nhưng vai trò của chúng bị đảo ngược. Vòng lặp ngoài sẽ duyệt qua từng cột của mảng (từ cột 0 đến cột cuối cùng), và vòng lặp trong sẽ duyệt qua từng hàng trong cột hiện tại (từ hàng 0 đến hàng cuối cùng).

Thứ tự duyệt sẽ là: (0,0), (1,0), ..., (rows-1,0), (0,1), (1,1), ..., (rows-1,1), ..., (0, cols-1), ..., (rows-1, cols-1).

Ví dụ C++:

#include <iostream>
#include <vector>

int main() {
    // Khởi tạo mảng 2 chiều (ví dụ 3x3)
    std::vector<std::vector<int>> matrix = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    int rows = matrix.size();
    if (rows == 0) {
         std::cout << "Mang rong!" << std::endl;
         return 0;
    }
    int cols = matrix[0].size();

    std::cout << "--- Duyet theo cot (Column-by-Column Traversal) ---" << std::endl;
    // Vong lap ngoai duyet qua cac cot (chi so j)
    for (int j = 0; j < cols; ++j) {
        // Vong lap trong duyet qua cac hang trong cot hien tai (chi so i)
        for (int i = 0; i < rows; ++i) {
            std::cout << matrix[i][j] << " "; // Truy cap phan tu tai (i, j)
        }
        // Co the xuong dong sau moi cot neu can, hoac chi in tren 1 dong
        // std::cout << std::endl;
    }
    std::cout << std::endl << std::endl;

    return 0;
}

Giải thích code:

  • Cấu trúc code tương tự như duyệt theo hàng, nhưng thứ tự của vòng lặp ngoài và vòng lặp trong đã được đổi chỗ.
  • Vòng lặp ngoài for (int j = 0; j < cols; ++j) lặp qua các chỉ số cột j từ 0 đến cols - 1.
  • Vòng lặp trong for (int i = 0; i < rows; ++i) lặp qua các chỉ số hàng i từ 0 đến rows - 1 cho cột j hiện tại.
  • Việc truy cập vẫn là matrix[i][j], nhưng do thứ tự vòng lặp, chúng ta sẽ truy cập matrix[0][0], matrix[1][0], matrix[2][0] (cột 0), sau đó matrix[0][1], matrix[1][1], matrix[2][1] (cột 1), v.v.

Kết quả của đoạn code trên sẽ in ra các phần tử theo thứ tự cột (trên cùng một dòng trong ví dụ này):

--- Duyet theo cot (Column-by-Column Traversal) ---
1 4 7 2 5 8 3 6 9

Duyệt theo cột hữu ích khi bài toán yêu cầu xử lý dữ liệu theo từng cột, ví dụ như tính tổng các phần tử trên mỗi cột, hoặc khi dữ liệu được tổ chức theo cột trong bộ nhớ (ít phổ biến với mảng trong C++).

Duyệt Theo Đường Chéo (Diagonal Traversal)

Duyệt theo đường chéo phức tạp hơn một chút so với hai kỹ thuật trên, vì các phần tử trên cùng một đường chéo không liền kề nhau theo chỉ số hàng hoặc cột theo cách đơn giản. Có hai loại đường chéo chính mà chúng ta thường quan tâm: đường chéo chínhđường chéo phụ.

Đường Chéo Chính (Main Diagonal)

Đường chéo chính bao gồm các phần tử mà chỉ số hàng và chỉ số cột bằng nhau. Ví dụ: matrix[0][0], matrix[1][1], matrix[2][2], v.v. Kỹ thuật này áp dụng đơn giản nhất cho ma trận vuông (số hàng bằng số cột).

  • Ý tưởng: Chỉ cần một vòng lặp duy nhất từ 0 đến kích thước (số hàng hoặc số cột) - 1, và truy cập phần tử matrix[i][i].

Ví dụ C++ (cho ma trận vuông):

#include <iostream>
#include <vector>
#include <algorithm> // For std::min (optional, but good practice for non-square)

int main() {
    // Khởi tạo ma trận vuông (ví dụ 3x3)
    std::vector<std::vector<int>> matrix = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    int rows = matrix.size();
    if (rows == 0) {
        std::cout << "Mang rong!" << std::endl;
        return 0;
    }
    int cols = matrix[0].size();

    std::cout << "--- Duyet duong cheo chinh (Main Diagonal) ---" << std::endl;

    // Duyet tren duong cheo chinh. Chi ap dung don gian cho ma tran vuong.
    // Neu ma tran khong vuong, chi co the duyet phan chung nho nhat.
    int size = std::min(rows, cols);
    for (int i = 0; i < size; ++i) {
        std::cout << matrix[i][i] << " "; // Truy cap phan tu tai (i, i)
    }
    std::cout << std::endl << std::endl;

    return 0;
}

Giải thích code:

  • Chúng ta sử dụng std::min(rows, cols) để lấy kích thước nhỏ nhất giữa số hàng và số cột. Điều này đảm bảo vòng lặp không đi ra ngoài giới hạn của mảng, ngay cả khi nó không vuông (chỉ duyệt được phần đường chéo chính nằm gọn trong cả hai chiều).
  • Vòng lặp for (int i = 0; i < size; ++i) lặp qua chỉ số i.
  • matrix[i][i] truy cập phần tử tại vị trí (i, i), tức là các phần tử trên đường chéo chính.

Kết quả (với ma trận 3x3):

--- Duyet duong cheo chinh (Main Diagonal) ---
1 5 9
Duyệt Các Đường Chéo Phụ (Anti-Diagonals)

Các đường chéo phụ chạy theo hướng ngược lại, từ góc trên bên phải xuống góc dưới bên trái. Các phần tử trên cùng một đường chéo phụ có tổng chỉ số hàng và chỉ số cột là một hằng số. Ví dụ, trong ma trận 3x3, các đường chéo phụ có tổng chỉ số là:

  • Tổng 0: (0,0)
  • Tổng 1: (0,1), (1,0)
  • Tổng 2: (0,2), (1,1), (2,0)
  • Tổng 3: (1,2), (2,1)
  • Tổng 4: (2,2)

Với ma trận kích thước M x N, tổng chỉ số i + j sẽ chạy từ 0 (cho matrix[0][0]) đến (M-1) + (N-1) = M + N - 2 (cho matrix[M-1][N-1]).

  • Ý tưởng: Vòng lặp ngoài duyệt qua tất cả các giá trị tổng chỉ số có thể có (k từ 0 đến rows + cols - 2). Vòng lặp trong duyệt qua các chỉ số hàng i (hoặc cột j) sao cho i + j = k. Nếu ta chọn duyệt theo hàng i, thì chỉ số cột tương ứng là j = k - i. Chúng ta cần đảm bảo cả ij đều nằm trong phạm vi hợp lệ của mảng.

Ví dụ C++ (cho ma trận bất kỳ):

#include <iostream>
#include <vector>
#include <algorithm> // For std::max and std::min

int main() {
    // Khởi tạo mảng 2 chiều (ví dụ 4x3)
    std::vector<std::vector<int>> matrix = {
        { 1,  2,  3},
        { 4,  5,  6},
        { 7,  8,  9},
        {10, 11, 12}
    };

    int rows = matrix.size();
    if (rows == 0) {
        std::cout << "Mang rong!" << std::endl;
        return 0;
    }
    int cols = matrix[0].size();

    std::cout << "--- Duyet theo duong cheo phu (Anti-Diagonals) ---" << std::endl;

    // Tong chi so i + j = k. k chay tu 0 den rows + cols - 2
    for (int k = 0; k <= rows + cols - 2; ++k) {
        // Duyet qua cac hang i. Chi so cot j = k - i.
        // i phai tu 0 den rows - 1  => i >= 0 && i < rows
        // j = k - i phai tu 0 den cols - 1 => k - i >= 0 && k - i < cols
        // => i <= k && i > k - cols
        // Ket hop cac dieu kien: i phai nam trong khoang [max(0, k - cols + 1), min(rows - 1, k)]

        int start_i = std::max(0, k - cols + 1);
        int end_i = std::min(rows - 1, k);

        for (int i = start_i; i <= end_i; ++i) {
            int j = k - i;
            std::cout << matrix[i][j] << " ";
        }
        // Co the xuong dong sau moi duong cheo phu neu can
        // std::cout << std::endl;
    }
    std::cout << std::endl << std::endl;

    return 0;
}

Giải thích code:

  • Vòng lặp ngoài for (int k = 0; k <= rows + cols - 2; ++k) duyệt qua tất cả các giá trị tổng ki + j có thể nhận.
  • Đối với mỗi giá trị k, vòng lặp trong duyệt qua các giá trị i (chỉ số hàng). Chỉ số cột tương ứng j được tính là k - i.
  • Điều mấu chốt là xác định phạm vi duyệt của i. i phải đủ lớn để j = k - i không âm (k - i >= 0 => i <= k), và i phải đủ nhỏ để j = k - i nhỏ hơn cols (k - i < cols => i > k - cols). Kết hợp với giới hạn tự nhiên của i (từ 0 đến rows - 1), phạm vi hợp lệ của i là từ max(0, k - cols + 1) đến min(rows - 1, k).
  • Các hàm std::maxstd::min giúp tính toán chính xác các giới hạn start_iend_i.
  • matrix[i][j] truy cập phần tử tại vị trí hợp lệ trên đường chéo phụ có tổng chỉ số k.

Kết quả (với ma trận 4x3):

--- Duyet theo duong cheo phu (Anti-Diagonals) ---
1 2 4 3 5 7 6 8 10 9 11 12

Thứ tự duyệt này đi qua từng đường chéo phụ một.

Duyệt Xoắn Ốc (Spiral Traversal)

Duyệt xoắn ốc là một kỹ thuật thú vị và thử thách hơn, nơi chúng ta đi qua các phần tử theo hình xoắn ốc, thường bắt đầu từ góc trên bên trái và di chuyển vào trong cho đến khi duyệt hết mảng.

  • Ý tưởng: Duy trì bốn biến để đánh dấu các biên của vùng mảng chưa được duyệt: top (hàng trên cùng), bottom (hàng dưới cùng), left (cột trái cùng), right (cột phải cùng). Ban đầu, chúng là các chỉ số của các biên ngoài cùng của mảng. Chúng ta lặp đi lặp lại bốn bước:

    1. Duyệt từ trái sang phải dọc theo hàng top. Sau khi duyệt xong, tăng top lên 1.
    2. Duyệt từ trên xuống dưới dọc theo cột right. Sau khi duyệt xong, giảm right xuống 1.
    3. Duyệt từ phải sang trái dọc theo hàng bottom. Sau khi duyệt xong, giảm bottom xuống 1.
    4. Duyệt từ dưới lên trên dọc theo cột left. Sau khi duyệt xong, tăng left lên 1.

    Sau mỗi bước, chúng ta cần kiểm tra xem vùng chưa duyệt có còn hợp lệ không (tức là top <= bottomleft <= right). Nếu không, dừng lại.

Ví dụ C++:

#include <iostream>
#include <vector>

int main() {
    // Khởi tạo mảng 2 chiều (ví dụ 4x4)
    std::vector<std::vector<int>> matrix = {
        { 1,  2,  3,  4},
        { 5,  6,  7,  8},
        { 9, 10, 11, 12},
        {13, 14, 15, 16}
    };

    int rows = matrix.size();
     if (rows == 0) {
        std::cout << "Mang rong!" << std::endl;
        return 0;
    }
    int cols = matrix[0].size();

    std::cout << "--- Duyet xoan oc (Spiral Traversal) ---" << std::endl;

    int top = 0, bottom = rows - 1;
    int left = 0, right = cols - 1;

    while (top <= bottom && left <= right) {
        // 1. Duyet tu trai sang phai (tren hang top)
        for (int j = left; j <= right; ++j) {
            std::cout << matrix[top][j] << " ";
        }
        top++; // Da duyet xong hang top, tang gioi han top len 1

        // Kiem tra dieu kien dung sau moi buoc de xu ly truong hop mang chu nhat hoac le
        if (top > bottom || left > right) break;

        // 2. Duyet tu tren xuong duoi (tren cot right)
        for (int i = top; i <= bottom; ++i) {
            std::cout << matrix[i][right] << " ";
        }
        right--; // Da duyet xong cot right, giam gioi han right xuong 1

        // Kiem tra dieu kien dung
        if (top > bottom || left > right) break;

        // 3. Duyet tu phai sang trai (tren hang bottom)
        for (int j = right; j >= left; --j) {
            std::cout << matrix[bottom][j] << " ";
        }
        bottom--; // Da duyet xong hang bottom, giam gioi han bottom xuong 1

        // Kiem tra dieu kien dung
        if (top > bottom || left > right) break;

        // 4. Duyet tu duoi len tren (tren cot left)
        for (int i = bottom; i >= top; --i) {
            std::cout << matrix[i][left] << " ";
        }
        left++; // Da duyet xong cot left, tang gioi han left len 1
    }
    std::cout << std::endl << std::endl;

    return 0;
}

Giải thích code:

  • Chúng ta khởi tạo 4 biến top, bottom, left, right lần lượt bằng các chỉ số của hàng/cột biên ngoài cùng.
  • Vòng lặp while (top <= bottom && left <= right) tiếp tục miễn là còn một vùng (dù chỉ 1x1) chưa được duyệt.
  • Bên trong vòng while là 4 vòng lặp for, mỗi vòng lặp thực hiện duyệt theo một hướng và sau đó điều chỉnh biến biên tương ứng để thu hẹp vùng chưa duyệt:
    • Vòng for đầu tiên duyệt hàng top từ left đến right, sau đó top được tăng.
    • Vòng for thứ hai duyệt cột right từ hàng mới top đến bottom, sau đó right được giảm.
    • Vòng for thứ ba duyệt hàng bottom từ cột mới right về left, sau đó bottom được giảm.
    • Vòng for cuối cùng duyệt cột left từ hàng mới bottom về top, sau đó left được tăng.
  • Điều kiện if (top > bottom || left > right) break; sau mỗi vòng lặp for rất quan trọng. Nó xử lý các trường hợp khi số hàng hoặc số cột là lẻ, hoặc khi mảng có dạng chữ nhật (không vuông), đảm bảo chúng ta không cố gắng duyệt vào vùng đã bị thu hẹp và thoát khỏi vòng lặp while đúng lúc.
  • Quá trình lặp lại, vùng chưa duyệt sẽ co lại dần cho đến khi top > bottom hoặc left > right, lúc đó toàn bộ mảng đã được duyệt.

Kết quả (với ma trận 4x4):

--- Duyet xoan oc (Spiral Traversal) ---
1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10

Kỹ thuật duyệt xoắn ốc thường xuất hiện trong các bài toán phỏng vấn hoặc các tác vụ xử lý hình ảnh đặc thù.

Duyệt Ziczac (Zig-zag Traversal)

Duyệt Ziczac, đôi khi còn gọi là Snake Traversal (duyệt kiểu rắn bò), là một biến thể của duyệt theo hàng nhưng có thêm một "twist" (sự thay đổi hướng).

  • Ý tưởng: Duyệt qua từng hàng. Đối với các hàng có chỉ số chẵn (0, 2, 4, ...), duyệt các cột theo thứ tự tăng dần (từ trái sang phải). Đối với các hàng có chỉ số lẻ (1, 3, 5, ...), duyệt các cột theo thứ tự giảm dần (từ phải sang trái).

Thứ tự duyệt sẽ là: (0,0), (0,1), ..., (0,cols-1), (1,cols-1), (1,cols-2), ..., (1,0), (2,0), (2,1), ..., (2,cols-1), ....

Ví dụ C++:

#include <iostream>
#include <vector>

int main() {
    // Khởi tạo mảng 2 chiều (ví dụ 3x4)
    std::vector<std::vector<int>> matrix = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    int rows = matrix.size();
     if (rows == 0) {
        std::cout << "Mang rong!" << std::endl;
        return 0;
    }
    int cols = matrix[0].size();

    std::cout << "--- Duyet ziczac (Zig-zag Traversal) ---" << std::endl;

    for (int i = 0; i < rows; ++i) { // Duyet qua cac hang
        if (i % 2 == 0) {
            // Hang co chi so chan (0, 2, 4...): duyet tu trai sang phai
            for (int j = 0; j < cols; ++j) {
                std::cout << matrix[i][j] << " ";
            }
        } else {
            // Hang co chi so le (1, 3, 5...): duyet tu phai sang trai
            for (int j = cols - 1; j >= 0; --j) {
                 std::cout << matrix[i][j] << " ";
            }
        }
    }
    std::cout << std::endl << std::endl;

    return 0;
}

Giải thích code:

  • Vòng lặp ngoài for (int i = 0; i < rows; ++i) duyệt qua các chỉ số hàng.
  • Bên trong vòng lặp hàng, chúng ta sử dụng điều kiện if (i % 2 == 0) để kiểm tra xem chỉ số hàng i là chẵn hay lẻ.
  • Nếu i là chẵn, chúng ta dùng vòng lặp for (int j = 0; j < cols; ++j) để duyệt các cột từ 0 đến cols - 1.
  • Nếu i là lẻ, chúng ta dùng vòng lặp for (int j = cols - 1; j >= 0; --j) để duyệt các cột từ cols - 1 về 0.
  • Trong cả hai trường hợp, chúng ta truy cập phần tử bằng matrix[i][j], với j thay đổi theo hướng duyệt của hàng hiện tại.

Kết quả (với ma trận 3x4):

--- Duyet ziczac (Zig-zag Traversal) ---
1 2 3 4 8 7 6 5 9 10 11 12

Bài tập ví dụ:

Ma trận xoáy ốc

Xây dựng ma trận xoáy ốc cấp n.

Input Format

Số nguyên dương n là cấp của ma trận xoáy ốc cần xây dựng.(2≤N≤100)

Constraints

.

Output Format

In ra ma trận xoáy ốc.

Ví dụ:

Dữ liệu vào
4
Dữ liệu ra
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7

Hướng dẫn giải bài Ma trận xoáy ốc bằng C++ (ngắn gọn):

  1. Khởi tạo:

    • Tạo một ma trận vuông kích thước n x n (ví dụ: dùng std::vector<std::vector<int>>).
    • Khởi tạo các biến biên giới: row_start = 0, row_end = n-1, col_start = 0, col_end = n-1.
    • Khởi tạo biến đếm current_number = 1.
  2. Vòng lặp điền số:

    • Sử dụng vòng lặp while (row_start <= row_end && col_start <= col_end). Vòng lặp này tiếp tục khi vùng làm việc còn hợp lệ.
    • Trong mỗi lần lặp, thực hiện 4 bước di chuyển theo xoáy ốc:
      • Điền từ trái sang phải: Điền các số từ current_number vào hàng row_start, từ cột col_start đến col_end. Tăng current_number sau mỗi lần điền. Sau khi xong, tăng row_start lên 1 (vì hàng trên cùng đã được điền).
      • Điền từ trên xuống dưới: Điền các số vào cột col_end, từ hàng row_start đến row_end. Tăng current_number. Sau khi xong, giảm col_end đi 1 (vì cột ngoài cùng bên phải đã được điền).
      • Điền từ phải sang trái: Kiểm tra điều kiện if (row_start <= row_end) trước khi thực hiện. Nếu điều kiện đúng, điền các số vào hàng row_end, từ cột col_end về col_start. Tăng current_number. Sau khi xong, giảm row_end đi 1 (vì hàng dưới cùng đã được điền).
      • Điền từ dưới lên trên: Kiểm tra điều kiện if (col_start <= col_end) trước khi thực hiện. Nếu điều kiện đúng, điền các số vào cột col_start, từ hàng row_end về row_start. Tăng current_number. Sau khi xong, tăng col_start lên 1 (vì cột ngoài cùng bên trái đã được điền).
  3. In kết quả:

    • Sau khi vòng lặp kết thúc (khi current_number đã điền hết hoặc các biên giới đã giao nhau/vượt qua nhau), in ma trận đã điền theo định dạng yêu cầu (số cách nhau bằng dấu cách, mỗi hàng trên một dòng mới).

Lưu ý: Hai bước kiểm tra điều kiện trước khi điền từ phải sang trái và từ dưới lên trên là quan trọng để xử lý đúng các trường hợp ma trận có kích thước lẻ ở lớp trong cùng.

Làm thêm nhiều bài tập miễn phí tại đây

Comments

There are no comments at the moment.