相信学习过线性代数或者高等代数的你,可以很轻松地用多维数组来构建一个矩阵。
#include <stdio.h>
int main() {
// 声明并初始化3x3矩阵
int matrix[3][3] = {
{1, 2, 3},
{2, 5, 6},
{3, 6, 9}
};
// 遍历并打印矩阵的每个元素
for (int i = 0; i < 3; i++) { // 对于每一行
for (int j = 0; j < 3; j++) { // 对于每一列
printf("%d ", matrix[i][j]);
}
printf("\n"); // 每打印完一行后换行
}
return 0;
}
你可以翻来覆去地储存和打印这些多维数组,但是你有没有为计算机想过,或许它们记忆这些矩阵很累,并且怪费时间空间的呢?
也许我们可以通过一些特殊手段,为计算机减轻一些负担。比如你在线性代数课上学的对称矩阵、三角矩阵等等呢?
对称矩阵
我们解决压缩矩阵问题,关键的两个字是降维。
{1, 2, 3}
{2, 5, 6}
{3, 6, 9}
我们看开头那个你轻而易举就搞出的那个矩阵,我们只需要记录主对角线上的元素及其上方的元素,那我们现在只需要搞一个一维向量{1, 2, 3, 5, 6, 9}就能解决问题了。调取元素时,我们只要把两个数组元素映射起来就好了。
这就是矩阵元素和一维数组小标的对应关系公式。我们以上边 i大于等于j 的情况为例进行一个说明,它其实可以理解成下三角区和主对角线元素合并的情况,i代表行累积,j代表列个数。大家可以理解一下,我们接下来就会提供给大家对称矩阵压缩存储的Python和C代码。
这个C程序与Python程序大致相同,但需要注意的是,在C中我们需要手动管理动态分配的内存。在程序的末尾,我们使用free
来释放之前分配的内存,以避免内存泄漏。这一点我们在先前的介绍中也是提到多次。
def get_index(i, j):
if i >= j:
return j * (j - 1) // 2 + i - 1
else:
return i * (i - 1) // 2 + j - 1
def compress_symmetric_matrix(matrix, n):
size = n * (n + 1) // 2
compressed = [0] * size
for i in range(n):
for j in range(n):
index = get_index(i, j)
compressed[index] = matrix[i][j]
return compressed
def main():
n = int(input("Enter the size of the matrix (n): "))
matrix = []
print("Enter the elements of the symmetric matrix row by row:")
for _ in range(n):
row = list(map(int, input().split()))
matrix.append(row)
compressed_matrix = compress_symmetric_matrix(matrix, n)
while True:
i, j = map(int, input("Enter the row and column to retrieve the element (enter -1 -1 to exit): ").split())
if i == -1 and j == -1:
break
if i < 0 or j < 0 or i >= n or j >= n:
print("Invalid row or column. Please try again.")
else:
index = get_index(i, j)
print(f"The element at position ({i}, {j}) is: {compressed_matrix[index]}")
if __name__ == "__main__":
main()
#include <stdio.h>
#include <stdlib.h>
int get_index(int i, int j, int n) {
if (i >= j) {
return j * (j - 1) / 2 + i - 1;
} else {
return i * (i - 1) / 2 + j - 1;
}
}
int* compress_symmetric_matrix(int n) {
int size = n * (n + 1) / 2;
int* compressed = (int*) malloc(sizeof(int) * size);
int i, j, input;
printf("Enter the elements of the symmetric matrix row by row:\n");
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
scanf("%d", &input);
int index = get_index(i, j, n);
compressed[index] = input;
}
}
return compressed;
}
int main() {
int n, i, j;
printf("Enter the size of the matrix (n): ");
scanf("%d", &n);
int* compressed_matrix = compress_symmetric_matrix(n);
while (1) {
printf("Enter the row and column to retrieve the element (enter -1 -1 to exit): ");
scanf("%d %d", &i, &j);
if (i == -1 && j == -1) {
break;
}
if (i < 0 || j < 0 || i >= n || j >= n) {
printf("Invalid row or column. Please try again.\n");
} else {
int index = get_index(i, j, n);
printf("The element at position (%d, %d) is: %d\n", i, j, compressed_matrix[index]);
}
}
free(compressed_matrix);
return 0;
}
三角矩阵
搞明白了对称矩阵的存储,我们接下来介绍的上下三角矩阵对于我们来说就是砍瓜切菜了。
三角矩阵和对称矩阵唯一的主要区别可能就是我们需要多存储一个0或者一个常量。
我们先以上三角矩阵为例,
类似对称矩阵地,我们利用求和公式,可以得到映射的一维数组。这个过程涉及到计算一维数组中每个元素的索引,这个索引取决于矩阵元素的行(i)和列(j)。
下面的代码注释很详细,我们可以参考一下。
def compress_upper_triangular_matrix(n, matrix):
"""将n×n的上三角矩阵压缩存储到一维数组中。"""
compressed = []
for i in range(n):
for j in range(n):
if i <= j:
# 应用给定公式进行压缩存储
k = (i * (2*n - i + 1)) // 2 + j - i
compressed.append(matrix[i][j])
return compressed
def get_element_from_compressed(compressed, n, i, j):
"""从压缩存储的一维数组中检索元素。"""
if i > j:
return "Error: Accessing lower triangular matrix element"
k = (i * (2*n - i + 1)) // 2 + j - i
return compressed[k]
# 输入矩阵的大小和矩阵元素
n = int(input("Enter the size of the matrix (n): "))
matrix = []
print("Enter the elements of the upper triangular matrix row-wise:")
for i in range(n):
row = list(map(int, input().split()))
matrix.append(row)
# 压缩存储矩阵
compressed_matrix = compress_upper_triangular_matrix(n, matrix)
print("Compressed Storage:", compressed_matrix)
# 输入行号和列号,检索元素
i, j = map(int, input("Enter the row and column numbers to retrieve an element: ").split())
element = get_element_from_compressed(compressed_matrix, n, i, j)
print(f"Element at position ({i}, {j}):", element)
#include <stdio.h>
#include <stdlib.h>
// 函数原型声明
int getIndex(int i, int j, int n);
void printMatrix(int* compressedMatrix, int size);
int main() {
int n, i, j;
// 输入矩阵的大小
printf("Enter the size of the matrix (n): ");
scanf("%d", &n);
// 根据矩阵大小,分配存储空间
int *matrix = (int*)malloc(n * n * sizeof(int));
int size = n * (n + 1) / 2; // 上三角矩阵的元素个数
int *compressedMatrix = (int*)malloc(size * sizeof(int));
// 输入矩阵元素
printf("Enter the elements of the matrix row by row:\n");
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
scanf("%d", &matrix[i * n + j]);
// 如果是上三角矩阵的元素,则存储到压缩矩阵中
if (i <= j) {
int index = getIndex(i, j, n);
compressedMatrix[index] = matrix[i * n + j];
}
}
}
// 输入要检索的行号和列号
printf("Enter the row and column numbers to retrieve an element: ");
scanf("%d %d", &i, &j);
// 检索并输出元素
if (i <= j) {
int index = getIndex(i - 1, j - 1, n); // 调整为从0开始的索引
printf("Element at position (%d, %d): %d\n", i, j, compressedMatrix[index]);
} else {
printf("Invalid input. Element (%d, %d) is not in the upper triangular matrix.\n", i, j);
}
// 释放分配的内存
free(matrix);
free(compressedMatrix);
return 0;
}
// 根据给定的行号和列号以及矩阵的大小,计算在压缩矩阵中的索引
int getIndex(int i, int j, int n) {
return (i * (2 * n - i + 1)) / 2 + j - i;
}
// 打印压缩后的矩阵,用于调试
void printMatrix(int* compressedMatrix, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", compressedMatrix[i]);
}
printf("\n");
}
稀疏矩阵
稀疏矩阵是一个包含大量零值元素的矩阵。
在科学计算和工程应用中,经常会遇到稀疏矩阵,高手科学家们开发了多种稀疏矩阵的压缩存储技术,以减少内存的使用,提高计算效率。数组存储法和十字链表法是两种常见的稀疏矩阵压缩存储方法。
数组存储法
数组存储法(也称为三元组表表示法或COO格式)是一种将稀疏矩阵中的非零元素存储到数组中的方法。在这种方法中,通常使用三个数组来存储稀疏矩阵中的非零元素及其对应的行索引和列索引。
- data数组(v):存储稀疏矩阵中所有非零元素的值。
- rowIndex数组(i):存储每个非零元素对应的行号。
- colIndex数组(j):存储每个非零元素对应的列号。
十字链表法
对于十字链表法,大家了解一下即可。十字链表法(Cross Linked List)适合于矩阵的动态修改。在十字链表中,矩阵的每个非零元素由一个节点表示,每个节点都包含元素值及其行号和列号。此外,节点还包含指向同一行下一个非零元素和同一列下一个非零元素的指针。
为了快速访问任何行或列的第一个非零元素,通常还会维护两个头节点数组:
- 行头节点数组:每个元素指向该行的第一个非零元素节点。
- 列头节点数组:每个元素指向该列的第一个非零元素节点。
十字链表法的优点是可以高效地执行添加、删除和修改非零元素的操作,因为这些操作主要涉及指针的调整,而不需要移动大量元素。同时,它也支持快速地按行或列遍历非零元素。不过,这种方法的实现比数组存储法要复杂,且需要更多的内存来存储节点和指针。