一、矩阵的表示与压缩
在数学中多维矩阵通常用数组来表示,这里我们只学习二维矩阵的相关知识,多维矩阵与二维矩阵本质上没有区别,只是在逻辑上比二维矩阵复杂。
我们所说的二维矩阵(以下简称矩阵)通常是使用二维数组来表示,例如一个5×6的矩阵可以这样表示:
对于矩阵中0值元素的数量很多时,可以使用3元组矩阵表示法。即:用(行号,列号,值)这3个数字为1组来表示一个元素的位置。那么上面的矩阵就可以表示为:
(0, 1, 3), (0, 4, 12), (2, 0, -7), (3, 2, 8), (3, 5, 13), (4, 0, 11), (4, 1, -5), (4, 4, 9)
这样就起到了矩阵压缩的作用。
我们首先定义一下3元组矩阵的数据结构:
//三元组
typedef struct
{
//行号
int i;
//列号
int j;
//值
int val;
} m_data;
//二维矩阵
typedef struct
{
//总行数
int m;
//总列数
int n;
//非0元素个数
int k;
//线性三元组
m_data *data;
} matrix;
对矩阵使用3元组压缩算法如下:
//原始矩阵
int original_matrix[5][6] =
{
{ 0, 3, 0, 0, 12, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ -7, 0, 0, 0, 0, 0 },
{ 0, 0, 8, 0, 0, 13 },
{ 11, -5, 0, 0, 9, 0 } };
//压缩矩阵
matrix mat;
mat.m = 5;
mat.n = 6;
mat.k = 0;
//8个非空元素
mat.data = (m_data *) malloc(sizeof(m_data) * 8);
//将原始矩阵压缩
for (int i = 0; i < mat.m; ++i)
{
for (int j = 0; j < mat.n; ++j)
{
if (original_matrix[i][j] != 0)
{
/*
* 注意 mat.data + k的真正含义:
* 在指针data值(地址)上每加1,表示对这个地址加上一个单位的m_data(因为此指针是指向m_data类型)大小
* 因为 sizeof(m_data) == 12 == 0xc
* 假设: mat.data == 0x90cb008
* mat.data + 1 == 0x90cb014
* mat.data + 2 == 0x90cb020
* mat.data + 3 == 0x90cb02c
* mat.data + 4 == 0x90cb038
*/
set_data(mat.data + mat.k, i, j, original_matrix[i][j]);
mat.k++;
}
}
}
//显示矩阵内容
//(0, 1, 3), (0, 4, 12), (2, 0, -7), (3, 2, 8), (3, 5, 13), (4, 0, 11), (4, 1, -5), (4, 4, 9)
for (int p = 0; p < mat.k; ++p)
{
printf("(%d, %d, %d), ", mat.data[p].i, mat.data[p].j, mat.data[p].val);
}
printf("\n");
运行结果:
(0, 1, 3), (0, 4, 12), (2, 0, -7), (3, 2, 8), (3, 5, 13), (4, 0, 11), (4, 1, -5), (4, 4, 9)
二、矩阵转置
矩阵转置就是将矩阵元素的行号与列号互换:
转置后矩阵如下:
我们首先实现矩阵的直接转置算法:
//转置矩阵
matrix r_mat;
r_mat.data = (m_data *) malloc(sizeof(m_data) * 8);
r_mat.m = mat.n;
r_mat.n = mat.m;
r_mat.k = mat.k;
int q = 0;
//从原始矩阵的每一列开始遍历,也就是转置矩阵中的行号
for (int col = 0; col < mat.n; ++col)
{
//在mat中的所有3元组中查找
for (int p = 0; p < mat.k; ++p)
{
//原矩阵中“列号”等于转置矩阵中行号
if (mat.data[p].j == col)
{
//行列转置
r_mat.data[q].i = mat.data[p].j;
r_mat.data[q].j = mat.data[p].i;
r_mat.data[q].val = mat.data[p].val;
q++;
}
}
}
printf("\n");
for (int p = 0; p < r_mat.k; ++p)
{
printf("(%d, %d, %d), ", r_mat.data[p].i, r_mat.data[p].j, r_mat.data[p].val);
}
这种转置算法很好理解,就是在原矩阵的列中找到转置矩阵中的行。运行结果:
(0, 2, -7), (0, 4, 11), (1, 0, 3), (1, 4, -5), (2, 3, 8), (4, 0, 12), (4, 4, 9), (5, 3, 13)
下面我们再来实现一种快速转置的算法:
//迅速转置矩阵
matrix e_mat;
e_mat.data = (m_data *) malloc(sizeof(m_data) * 8);
e_mat.m = mat.n;
e_mat.n = mat.m;
e_mat.k = mat.k;
//用于记录矩阵每一列中有多少个非0元素
int *num = (int *) malloc(sizeof(int) * mat.n);
//用于记录矩阵每一列第一个非0元素的开始位置(行号)
int *cpot = (int *) malloc(sizeof(int) * mat.n);
//初始化num,全部设为0
for (int j = 0; j < mat.n; ++j)
{
num[j] = 0;
}
//计算矩阵每一列中有多少个非0元素
for (int q = 0; q < mat.k; ++q)
{
++num[mat.data[q].j];
}
//初始号为0
cpot[0] = 0;
//计算每一列第一个非0元素的开始位置
for (int col = 1; col < mat.n; ++col)
{
cpot[col] = cpot[col - 1] + num[col - 1];
}
//转置矩阵
int col;
for (int p = 0; p < mat.k; ++p)
{
//取得列号
col = mat.data[p].j;
//从cpot中得到开始的位置
q = cpot[col];
//转置矩阵元素
e_mat.data[q].i = mat.data[p].j;
e_mat.data[q].j = mat.data[p].i;
e_mat.data[q].val = mat.data[p].val;
//此列的开始位置加一(转置矩阵的行号)
++cpot[col];
}
printf("\n\n");
for (int p = 0; p < r_mat.k; ++p)
{
printf("(%d, %d, %d), ", e_mat.data[p].i, e_mat.data[p].j, e_mat.data[p].val);
}
可以看到这种快速转置算法是4个顺序执行的for循环语句,但在非0元素个数与m×n个数在同一个数量级时,快速转置算法会比原转置算法快的多。
运行结果:
(0, 2, -7), (0, 4, 11), (1, 0, 3), (1, 4, -5), (2, 3, 8), (4, 0, 12), (4, 4, 9), (5, 3, 13)
本例代码:
code path chapter.04/e.g.4.2/
https https://github.com/magicworldos/datastructure.git
git git@github.com:magicworldos/datastructure.git
subverion https://github.com/magicworldos/datastructure
Copyright © 2015-2023 问渠网 辽ICP备15013245号