Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 10.615 phút đọc

Đường chéo và tam giác

Sau bài này bạn sẽ làm được

  • Viết điều kiện chỉ số cho hai đường chéo
  • Phân biệt ba vùng tam giác trên, dưới và đường chéo
  • Tính vết ma trận và kiểm tra ma trận đơn vị
  • Nhận ra ma trận đối xứng bằng một vòng lặp nửa

Bài cuối của Chương 10 gom lại những mẫu điều kiện chỉ số hay dùng nhất với ma trận vuông. Chúng đơn giản nhưng xuất hiện khắp nơi, và biết viết chúng nhanh giúp bạn tập trung vào phần lô gic thật của bài toán.

#Hai đường chéo

i == j           /* đường chéo chính, từ góc trên trái xuống góc dưới phải */
i + j == n - 1   /* đường chéo phụ, từ góc trên phải xuống góc dưới trái */
Với n = 4:

đường chéo chính        đường chéo phụ
| X  .  .  . |          | .  .  .  X |
| .  X  .  . |          | .  .  X  . |
| .  .  X  . |          | .  X  .  . |
| .  .  .  X |          | X  .  .  . |
duong-cheo.c
/* Đường chéo chính: chỉ cần MỘT vòng lặp */
long long tong_cheo_chinh(const int *A, size_t n)
{
    long long s = 0;

    for (size_t i = 0; i < n; ++i)
        s += A[i * n + i];

    return s;
}

/* Đường chéo phụ: cũng chỉ cần một vòng lặp */
long long tong_cheo_phu(const int *A, size_t n)
{
    long long s = 0;

    for (size_t i = 0; i < n; ++i)
        s += A[i * n + (n - 1 - i)];

    return s;
}

#Ba vùng tam giác

VùngĐiều kiệnSố ô khi ma trận n nhân n
Tam giác trên, không kể đường chéoj > in(n-1)/2
Tam giác trên, kể cả đường chéoj >= in(n+1)/2
Đường chéo chínhj == in
Tam giác dưới, kể cả đường chéoj <= in(n+1)/2
Tam giác dưới, không kể đường chéoj < in(n-1)/2
Với n = 4, ký hiệu T là trên, C là chéo, D là dưới:

| C  T  T  T |
| D  C  T  T |
| D  D  C  T |
| D  D  D  C |
tam-giac.c
/* Duyệt tam giác trên bằng vòng lặp trực tiếp, không dùng if */
for (size_t i = 0; i < n; ++i)
    for (size_t j = i + 1; j < n; ++j)
        xu_ly(A[i * n + j]);

/* Tam giác dưới */
for (size_t i = 1; i < n; ++i)
    for (size_t j = 0; j < i; ++j)
        xu_ly(A[i * n + j]);

/* Tam giác trên kèm đường chéo */
for (size_t i = 0; i < n; ++i)
    for (size_t j = i; j < n; ++j)
        xu_ly(A[i * n + j]);

Kiểm tra ma trận tam giác

/* Ma trận tam giác trên: mọi ô ở tam giác dưới đều bằng 0 */
int la_tam_giac_tren(const int *A, size_t n)
{
    for (size_t i = 1; i < n; ++i)
        for (size_t j = 0; j < i; ++j)
            if (A[i * n + j] != 0)
                return 0;

    return 1;
}

Ma trận tam giác quan trọng trong đại số tuyến tính vì hệ phương trình với ma trận tam giác giải được ngay bằng phép thế ngược, không cần khử Gauss. Định thức của ma trận tam giác cũng chỉ là tích các phần tử trên đường chéo.

#Vết ma trận

Vết
Tổng các phần tử trên đường chéo chính. Ký hiệu toán học là tr(A).
long long vet(const int *A, size_t n)
{
    long long s = 0;

    for (size_t i = 0; i < n; ++i)
        s += A[i * n + i];

    return s;
}
Tính chấtCông thức
Tuyến tínhtr(A + B) = tr(A) + tr(B)
Với hệ sốtr(cA) = c * tr(A)
Bất biến qua chuyển vịtr(At) = tr(A)
Hoán vị vòngtr(A * B) = tr(B * A)
Bằng tổng các trị riêngtr(A) = tổng các trị riêng

#Nhận dạng các loại ma trận

nhan-dang.c
/* Ma trận đơn vị: đường chéo bằng 1, mọi ô khác bằng 0 */
int la_don_vi(const int *A, size_t n)
{
    for (size_t i = 0; i < n; ++i)
        for (size_t j = 0; j < n; ++j) {
            int mong_doi = (i == j) ? 1 : 0;

            if (A[i * n + j] != mong_doi)
                return 0;
        }

    return 1;
}

/* Ma trận đối xứng: A[i][j] == A[j][i] */
int la_doi_xung(const int *A, size_t n)
{
    for (size_t i = 0; i < n; ++i)
        for (size_t j = i + 1; j < n; ++j)
            if (A[i * n + j] != A[j * n + i])
                return 0;

    return 1;
}

/* Ma trận đường chéo: mọi ô ngoài đường chéo bằng 0 */
int la_duong_cheo(const int *A, size_t n)
{
    for (size_t i = 0; i < n; ++i)
        for (size_t j = 0; j < n; ++j)
            if (i != j && A[i * n + j] != 0)
                return 0;

    return 1;
}

Ma phương

ma-phuong.c
/* Ma phương: mọi hàng, mọi cột và hai đường chéo có tổng bằng nhau. */
int la_ma_phuong(const int *A, size_t n)
{
    if (n == 0) return 0;

    /* Lấy tổng hàng đầu làm chuẩn */
    long long chuan = 0;

    for (size_t j = 0; j < n; ++j)
        chuan += A[j];

    /* Kiểm tra từng hàng */
    for (size_t i = 1; i < n; ++i) {
        long long s = 0;

        for (size_t j = 0; j < n; ++j)
            s += A[i * n + j];

        if (s != chuan) return 0;
    }

    /* Kiểm tra từng cột */
    for (size_t j = 0; j < n; ++j) {
        long long s = 0;

        for (size_t i = 0; i < n; ++i)
            s += A[i * n + j];

        if (s != chuan) return 0;
    }

    /* Hai đường chéo */
    long long c1 = 0, c2 = 0;

    for (size_t i = 0; i < n; ++i) {
        c1 += A[i * n + i];
        c2 += A[i * n + (n - 1 - i)];
    }

    return c1 == chuan && c2 == chuan;
}
terminal
./ma-phuong
     2     7     6
     9     5     1
     4     3     8

La ma phuong, tong moi duong = 15

#Duyệt xoắn ốc

Bài toán cuối cùng của chương, và là bài khó nhất về mặt điều khiển chỉ số. In các phần tử theo đường xoắn từ ngoài vào trong.

| 1  2  3  4 |
| 5  6  7  8 |     ->  1 2 3 4 8 12 11 10 9 5 6 7
| 9 10 11 12 |
xoan-oc.c
void duyet_xoan_oc(const int *A, size_t hang, size_t cot)
{
    if (hang == 0 || cot == 0) return;

    size_t tren = 0, duoi = hang - 1;
    size_t trai = 0, phai = cot - 1;

    while (1) {
        /* Đi sang phải trên hàng trên cùng */
        for (size_t j = trai; j <= phai; ++j)
            printf("%d ", A[tren * cot + j]);

        if (tren == duoi) break;
        ++tren;

        /* Đi xuống dưới ở cột phải cùng */
        for (size_t i = tren; i <= duoi; ++i)
            printf("%d ", A[i * cot + phai]);

        if (trai == phai) break;
        --phai;

        /* Đi sang trái trên hàng dưới cùng */
        for (size_t j = phai + 1; j-- > trai; )
            printf("%d ", A[duoi * cot + j]);

        if (tren == duoi) break;
        --duoi;

        /* Đi lên trên ở cột trái cùng */
        for (size_t i = duoi + 1; i-- > tren; )
            printf("%d ", A[i * cot + trai]);

        if (trai == phai) break;
        ++trai;
    }
}
terminal
./xoan-oc
3x4: 1 2 3 4 8 12 11 10 9 5 6 7
1x4: 1 2 3 4
4x1: 1 2 3 4
1x1: 1

Tự làm thử

  1. Viết chương trình tính tổng hai đường chéo của ma trận vuông, xử lý đúng trường hợp n lẻ.
  2. Đếm số ô của từng vùng tam giác với n bằng 5 và đối chiếu với công thức trong bảng.
  3. Viết hàm kiểm tra ma trận tam giác trên, tam giác dưới và ma trận đường chéo.
  4. Cài vet_tich tính vết của tích mà không tính tích, so sánh thời gian với cách tính tích trước rồi lấy vết, với n bằng 1000.
  5. Viết chương trình kiểm tra ma phương, thử với ma phương ba nhân ba kinh điển và một ma trận gần đúng chỉ sai một ô.
  6. Cài duyệt xoắn ốc, thử đủ các trường hợp một hàng, một cột, một ô, và ma trận vuông lẻ.
  7. Viết hàm sinh ma trận xoắn ốc: điền các số từ 1 tới n nhân n theo đường xoắn.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Đường chéo chính là i == j, đường chéo phụ là i + j == n - 1. Cả hai chỉ cần một vòng lặp.
  • Khi n lẻ, hai đường chéo cắt nhau ở ô tâm, phải trừ đi khi cộng tổng hai đường.
  • Đặt điều kiện vùng tam giác vào biên của vòng lặp thay vì vào câu lệnh if, để không duyệt thừa một nửa số ô.
  • Vết của tích tính được trong phép nhân mà không cần tính cả tích.
  • Duyệt xoắn ốc cần bốn chỗ kiểm tra dừng, và phải thử với ma trận một hàng, một cột và một ô.