iT邦幫忙

2025 iThome 鐵人賽

DAY 3
0

我不知道大家看到這天會不會驚訝一下,不是應該接續 List 家族這是什麼? Matrix 就是 Array 只是我單獨抽出來說明,如果你沒有在學習或處理資料科學相關的,應該不會使用到 Matrix,但是我認為這是一個蠻常被忽略的部分,也就花一點篇幅來介紹它。

Matrix 是由數字、符號或表達式排列成的長方形陣列。從數學的角度來看,對於 m*n 矩陣的形式,可以描述一個電腦中 A(m,n) 二維陣列。

矩陣的表示方式

一個 $m$ 行 $n$ 列的矩陣通常寫作:

$$
A = \begin{pmatrix}
a_{11} & a_{12} & \cdots & a_{1n} \
a_{21} & a_{22} & \cdots & a_{2n} \
\vdots & \vdots & \ddots & \vdots \
a_{m1} & a_{m2} & \cdots & a_{mn}
\end{pmatrix}
$$

其中 $a_{ij}$ 表示第 $i$ 行第 $j$ 列的元素。

常見矩陣種類

名稱 定義說明
行矩陣 只有一行的矩陣 ($1 \times n$)
列矩陣 只有一列的矩陣 ($m \times 1$)
方陣 行數與列數相同的矩陣 ($n \times n$)
零矩陣 所有元素皆為 0 的矩陣
單位矩陣 對角線為 1,其餘為 0 的方陣

矩陣的基本運算

如果你使用資料科學常見的函式庫 (e.g. Numpy),對於 Matrix 的支援非常的強大,常見的基本運算可能調用一個屬性或方法就能直接處理完成,但是本系列是著重底層知識的理解,所以不會使用到這些高階 API,但是在實務上一定是直接使用這些高階 API。

Matrix 常見的操作沒有很多,接下來會逐一說明,期望各位讀者可以更好的理解,常見的操作如下:

  • 矩陣相加
  • 矩陣相乘
  • 轉置矩陣
  • 稀疏矩陣 (Sparse Matrix)

矩陣相加

矩陣的加法定義非常直觀: 兩個形狀相同 (同樣的行數與列數) 的矩陣,對應位置的元素相加,就得到一個新矩陣,這種操作在影像處理 (像素加權)、線性代數的向量空間運算中都很常見,代碼演示如下:

def add_matrices(a, b):
    rows = len(a)
    cols = len(a[0])
    
    c = []
    for y in range(rows):
        row = []
        for x in range(cols):
            row.append(0)  # 加入一個 0
        c.append(row)  # 把這列加進外層 list

    for i in range(rows):
        for j in range(cols):
            c[i][j] = a[i][j] + b[i][j]
    return c


A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]

print(add_matrices(A, B))

矩陣相乘

矩陣相乘則稍微複雜,A (m×n) 與 B (n×p) 可相乘,得到 C (m×p)。定義是:

$$
C[i][j] = \sum_{k=1}^{n} A[i][k] \times B[k][j]
$$

也就是「A 的第 i 行 × B 的第 j 列」。這種運算是線性代數的核心,在電腦科學、機器學習、圖像轉換、甚至圖神經網路中無處不在。雖然實務上一定用 Numpy、BLAS 等庫來計算,但理解這個三層迴圈的結構很重要,代碼演示如下:

def multiply_matrices(A, B):
    rows_A = len(A)
    cols_A = len(A[0])
    rows_B = len(B)
    cols_B = len(B[0])
    
    # 檢查是否可以相乘 (A 的欄數必須等於 B 的列數)
    if cols_A != rows_B:
        raise ValueError("A 的欄數必須等於 B 的列數")
    
    # 建立結果矩陣 (初始化為 0)
    C = [[0 for _ in range(cols_B)] for _ in range(rows_A)]
    
    # 三層迴圈:i=row_A, j=col_B, k=col_A/row_B
    for i in range(rows_A):
        for j in range(cols_B):
            for k in range(cols_A):
                C[i][j] += A[i][k] * B[k][j]
    
    return C

# 測試
A = [[1, 2, 3], [4, 5, 6]]
B = [[7, 8], [9, 10], [11, 12]]

C = multiply_matrices(A, B)
for row in C:
    print(row)

轉置矩陣

轉置 (Transpose) 是將矩陣沿著對角線翻轉,行變列、列變行:

$$
A^T[i][j] = A[j][i]
$$

轉置操作在數學中用途廣泛,例如:

  • 內積與正交向量的判斷
  • 線性代數中的對稱矩陣
  • 機器學習中的資料格式轉換 (樣本 × 特徵 ⇄ 特徵 × 樣本)

程式實作上,就是把 $A[i][j]$ 賦值給 $T[j][i]$,代碼演示如下:

def transpose_matrix(A):
    rows = len(A)
    cols = len(A[0])

    # 建立轉置後的矩陣 (cols × rows)
    T = [[0 for _ in range(rows)] for _ in range(cols)]

    for i in range(rows):
        for j in range(cols):
            T[j][i] = A[i][j]

    return T

# 測試
A = [
    [1, 2, 3],
    [4, 5, 6]
]
T = transpose_matrix(A)
for row in T:
    print(row)

稀疏矩陣 (Sparse Matrix)

稀疏矩陣是指大部分元素為 0,只少部分有非零值的矩陣。如果直接用二維陣列存放,會浪費大量空間,所以常用「壓縮存儲」方式。

一種常見的表示法是三元組表示法 (COO, Coordinate List):

  • Header: 記錄矩陣大小與非零元素數量 (rows, cols, nnz)
  • Triplet: 記錄每個非零元素的 (row, col, val)

代碼演示如下:

from typing import List, Tuple

Triplet = List[Tuple[int, int, float]]  # [(row, col, val), ...]
Header = Tuple[int, int, int]           # (rows, cols, nnz)


def dense_to_triplet(A: List[List[float]], index_base: int = 1) -> Tuple[Header, Triplet]:
    """
    將稀疏矩陣的 dense 形式 (list of lists) 壓縮成三元組 (COO)。
    index_base: 1 → 輸出 1-based 座標;0 → 輸出 0-based 座標。
    """
    if not A or not A[0]:
        raise ValueError("矩陣不可為空")
    m, n = len(A), len(A[0])
    for r in A:
        if len(r) != n:
            raise ValueError("各列長度需一致 (矩形矩陣)")

    triplet: Triplet = []
    for i in range(m):
        for j in range(n):
            v = A[i][j]
            if v != 0:
                triplet.append((i + index_base, j + index_base, v))

    header: Header = (m, n, len(triplet))
    return header, triplet

def triplet_to_dense(header: Header, triplet: Triplet, index_base: int = 1) -> List[List[float]]:
    """
    將三元組 (COO) 還原為 dense 矩陣。
    index_base 要與壓縮時一致。
    """
    m, n, nnz = header
    if nnz != len(triplet):
        raise ValueError("header 的 nnz 與 triplet 長度不一致")

    A = [[0 for _ in range(n)] for _ in range(m)]
    for r, c, v in triplet:
        i = r - index_base
        j = c - index_base
        if not (0 <= i < m and 0 <= j < n):
            raise IndexError(f"索引超界: ({r}, {c}) for shape=({m},{n}) with index_base={index_base}")
        A[i][j] = v
    return A

def print_dense(A: List[List[float]]) -> None:
    for row in A:
        print(" ".join(str(x) for x in row))

def print_triplet(header: Header, triplet: Triplet) -> None:
    print(header[0], header[1], header[2])
    for r, c, v in triplet:
        print(r, c, v)

# ----------------------- 範例 -----------------------
if __name__ == "__main__":
    # 8x9 的稀疏矩陣(自行示範,可換成你的資料)
    A = [
        [0,0,0,0,0,0,0,0,0],
        [0,0,8,1,0,0,0,0,0],
        [0,0,0,0,0,7,0,0,0],
        [0,0,0,0,0,0,0,0,0],
        [0,9,0,0,0,0,0,0,0],
        [0,0,0,0,3,0,0,0,4],
        [2,0,5,0,0,0,0,3,0],
        [0,0,0,0,0,0,0,0,0],
    ]

    print("【原矩陣】")
    print_dense(A)

    header, trip = dense_to_triplet(A, index_base=1)  # 1-based 座標 (與很多教科書顯示一致)
    print("\n【三元組壓縮】(rows cols nnz / row col val)")
    print_triplet(header, trip)

    A_back = triplet_to_dense(header, trip, index_base=1)
    print("\n【還原矩陣】")
    print_dense(A_back)

結語

矩陣是現代數學與科學不可或缺的工具,掌握其基本概念與運算,將為後續學習打下堅實基礎。矩陣雖然看似只是二維陣列的延伸,但它的運算承載了線性代數的核心思想。

理解矩陣的加法、乘法、轉置與稀疏表示,不僅能幫助你在程式設計上打好基礎,更是日後進入機器學習、圖形處理、科學運算等領域的必備知識。


上一篇
(Day 2) 陣列 (Array)
下一篇
(Day 4) 鏈表 (Linked List)
系列文
快速掌握資料結構與演算法6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言