数据结构-数组和广义表(第六章)的整理笔记,若有错误,欢迎指正。
特殊矩阵的压缩存储
共用体类型
例: 假设有一个6×7阶稀疏矩阵A:
A 6 × 7 = [ 0 0 1 0 0 0 0 0 2 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 7 4 ] A_{6×7}=\begin{bmatrix} 0 & 0 & 1 & 0 & 0 & 0 & 0\\ 0 & 2 & 0 & 0 & 0 & 0 & 0\\ 3 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 5 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 6 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 7 & 4\\ \end{bmatrix} A6×7=⎣⎢⎢⎢⎢⎢⎢⎡003000020000100000000500000060000007000004⎦⎥⎥⎥⎥⎥⎥⎤
则稀疏矩阵A对应的三元组可表示为:
以行为主序: | i i i | j j j | a i , j a_{i,j} ai,j | 以列为主序: | i i i | j j j | a i , j a_{i,j} ai,j |
---|---|---|---|---|---|---|---|
(0,2,1) | 0 | 2 | 1 | (2,0,3) | 2 | 0 | 3 |
(1,1,2) | 1 | 1 | 2 | (1,1,2) | 1 | 1 | 2 |
(2,0,3) | 2 | 0 | 3 | (0,2,1) | 0 | 2 | 1 |
(3,3,5) | 3 | 3 | 5 | (3,3,5) | 3 | 3 | 5 |
(4,4,6) | 4 | 4 | 6 | (4,4,6) | 4 | 4 | 6 |
(5,5,7) | 5 | 5 | 7 | (5,5,7) | 5 | 5 | 7 |
(5,6,4) | 5 | 6 | 4 | (5,6,4) | 5 | 6 | 4 |
#include
#define M <稀疏矩阵行数>
#define N <稀疏矩阵列数>
#define MaxSize <稀疏矩阵中非零元素最多的个数>
typedef int ElemType;
typedef struct
{
int r; //行号
int c; //列号
ElemType d; //元素值
}TupNode; //三元组类型
typedef struct
{
int rows; //行号
int cols; //列号
int nums; //非零元素个数
TupNode data[MaxSize];
}TSMatrix; //三元组顺序表的类型
void CreateMat(TSMatrix& t, ElemType A[M][N])
{
t.rows = M;
t.cols = N;
t.nums = 0;
for (int i = 0; i < M; i++)
{
for (int j = 0; j < N; j++)
if (A[i][j] != 0) //只存储非零元素
{
t.data[t.nums].r = i;
t.data[t.nums].c = j;
t.data[t.nums].d = A[i][j];
t.nums++;
}
}
}
bool Value(TSMatrix& t, ElemType x, int i, int j)
{
int k, k1;
if (i >= t.rows || j >= t.cols) return false; //i、j参数超界,返回假
while (k < t.nums && t.data[k].r < i) k++; //查找第i行的第一个非0元素
while (k < t.nums && t.data[k].r == i && t.data[k].c < j) k++; //在第i行的非0元素中查找第j列
if (t.data[k].r == i && t.data[k].c == j) t.data[k].d = x; //若存在这样的非0元素,修改非0元素的值
else //若不存在这样的非0元素,若干元素均后移一个位置
{
for (k1 = t.nums - 1; k1 >= k; k1--)
{
t.data[k1 + 1].r = t.data[k1].r;
t.data[k1 + 1].c = t.data[k1].c;
t.data[k1 + 1].d= t.data[k1].d;
}
t.data[k].r = i; //插入非零元素x
t.data[k].c = j;
t.data[k].d = x;
t.nums++; //非0元素个数增1
}
return true; //操作成功后返回真
}
bool Assign(TSMatrix& t, ElemType& x, int i, int j)
{
int k = 0;
if (i >= t.cols && j >= t.rows) return false;
while (k < t.nums && t.data[k].r < i) k++;
while (k < t.nums && t.data[k].r == i && t.data[k].c < j) k++;
if (t.data[k].r == i && t.data[k].c == j) x = t.data[k].d;
else x=0;
return true;
}
void DispMat(TSMatrix t)
{
if (t.nums <= 0) return; //没有非零元素时直接返回
printf("\t%d\t%d\t%d\n", t.rows, t.cols, t.nums);
printf("\t-------------------------\n");
for (int k = 0; k < t.nums; k++) //输出所有非0元素
printf("\t%d\t%d\t%d\n", t.data[k].r, t.data[k].c, t.data[k].d);
}
void TranTup(TSMatrix& t, TSMatrix& tb)
{
int k1 = 0; //k1记录tb中的元素个数
tb.rows = t.rows;
tb.cols = t.cols;
tb.nums = t.nums;
if (t.nums <= 0) return; //没有非零元素时直接返回
for (int v = 0; v < t.cols; v++) //当存在非零元素时执行转置,按v=0、1、...、t.cols循环
{
for (int k = 0; k < t.nums; k++) //k用于扫描t.data的所有元素
if (t.data[k].c == v) //找到一个列号为v的元素
{
tb.data[k1].r = t.data[k].c; //将行、列交换后添加到tb中
tb.data[k1].c = t.data[k].r;
tb.data[k1].d = t.data[k].d;
k1++; //tb中元素的个数增1
}
}
}
- 该算法中含有两重for循环,其时间复杂度为O(t.cols×t.nums)。最坏的情况是当稀疏矩阵中的非零元素个数t.nums和m×n同数量级时,时间复杂度为 O ( m × n 2 ) O(m×n^2) O(m×n2),所以这不是一种高效的算法。
#include
#define M 6
#define N 7
#define MaxSize 42
int A[M][N] = { {0,0,1,0,0,0,0},{0,2,0,0,0,0,0},{3,0,0,0,0,0,0,},
{0,0,0,5,0,0,0},{0,0,0,0,6,0,0},{0,0,0,0,0,7,4} };
typedef int ElemType;
typedef struct
{
int r; //行号
int c; //列号
ElemType d; //元素值
}TupNode; //三元组类型
typedef struct
{
int rows; //行号
int cols; //列号
int nums; //非零元素个数
TupNode data[MaxSize];
}TSMatrix; //三元组顺序表的类型
//--------三元组的操作---------
void CreateMat(TSMatrix& t, ElemType A[M][N]) //从一个二维稀疏矩阵创建其三元组表示
{
t.rows = M;
t.cols = N;
t.nums = 0;
for (int i = 0; i < M; i++)
{
for (int j = 0; j < N; j++)
if (A[i][j] != 0) //只存储非零元素
{
t.data[t.nums].r = i;
t.data[t.nums].c = j;
t.data[t.nums].d = A[i][j];
t.nums++;
}
}
}
bool Value(TSMatrix& t, ElemType x, int i, int j) //三元组元素的赋值
{
int k=0, k1;
if (i >= t.rows || j >= t.cols) return false; //i、j参数超界,返回假
while (k < t.nums && t.data[k].r < i) k++; //查找第i行的第一个非0元素
while (k < t.nums && t.data[k].r == i && t.data[k].c < j) k++; //在第i行的非0元素中查找第j列
if (t.data[k].r == i && t.data[k].c == j) t.data[k].d = x; //若存在这样的非0元素,修改非0元素的值
else //若不存在这样的非0元素,若干元素均后移一个位置
{
for (k1 = t.nums - 1; k1 >= k; k1--)
{
t.data[k1 + 1].r = t.data[k1].r;
t.data[k1 + 1].c = t.data[k1].c;
t.data[k1 + 1].d= t.data[k1].d;
}
t.data[k].r = i; //插入非零元素x
t.data[k].c = j;
t.data[k].d = x;
t.nums++; //非0元素个数增1
}
return true; //操作成功后返回真
}
bool Assign(TSMatrix& t, ElemType& x, int i, int j) //将指定位置的元素值赋给变量
{
int k = 0;
if (i >= t.cols && j >= t.rows) return false;
while (k < t.nums && t.data[k].r < i) k++;
while (k < t.nums && t.data[k].r == i && t.data[k].c < j) k++;
if (t.data[k].r == i && t.data[k].c == j) x = t.data[k].d;
else x=0;
return true;
}
void DispTup(TSMatrix t) //输出三元组
{
if (t.nums <= 0) return; //没有非零元素时直接返回
printf("\t%d\t%d\t%d\n", t.rows, t.cols, t.nums);
printf("\t------------------\n");
for (int k = 0; k < t.nums; k++) //输出所有非0元素
printf("\t%d\t%d\t%d\n", t.data[k].r, t.data[k].c, t.data[k].d);
}
void TranTup(TSMatrix& t, TSMatrix& tb) //稀疏矩阵转置后的三元组
{
int k1 = 0; //k1记录tb中的元素个数
tb.rows = t.rows;
tb.cols = t.cols;
tb.nums = t.nums;
if (t.nums <= 0) return; //没有非零元素时直接返回
for (int v = 0; v < t.cols; v++) //当存在非零元素时执行转置,按v=0、1、...、t.cols循环
{
for (int k = 0; k < t.nums; k++) //k用于扫描t.data的所有元素
if (t.data[k].c == v) //找到一个列号为v的元素
{
tb.data[k1].r = t.data[k].c; //将行、列交换后添加到tb中
tb.data[k1].c = t.data[k].r;
tb.data[k1].d = t.data[k].d;
k1++; //tb中元素的个数增1
}
}
}
//-----------矩阵的操作---------
void DispMat(ElemType A[M][N]) //输出矩阵
{
for (int i = 0; i < M; i++)
{
for (int j = 0; j < N; j++)
printf("\t%d ", A[i][j]);
printf("\n");
}
}
void DispTranMat(ElemType B[N][M]) //输出矩阵的转置
{
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
printf("\t%d ", B[i][j]);
printf("\n");
}
}
void TranMat(ElemType B[N][M]) //矩阵转置
{
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
B[i][j] = A[j][i];
}
void ValueMat(ElemType A[M][N], ElemType x, int i, int j) //修改矩阵对应位置的值
{
for (int m = 0; m < N; m++)
for (int n = 0; n < M; n++)
if (m == i && n == j) A[m][n] = x;
}
int main()
{
TSMatrix t, tb;
ElemType B[N][M], x;
int i, j;
CreateMat(t,A);
printf("输出稀疏矩阵A:\n");
DispMat(A);
printf("\n输出稀疏矩阵A的三元组:\n");
DispTup(t);
TranMat(B);
printf("\n输出稀疏矩阵A的转置矩阵B:\n");
DispTranMat(B);
TranTup(t, tb);
printf("\n输出稀疏矩阵A的转置矩阵B的三元组:\n");
DispTup(tb);
x = 8, i = 3, j = 2;
printf("\n三元组元素的赋值(A[%d][%d])=%d:\n", i, j, x);
Value(t, x, i, j);
DispTup(t);
i = 4, j = 4;
Assign(t, x, i, j);
printf("\n矩阵中A[%d][%d]的值为:%d", i, j, x);
return 0;
}
- 从以上可以看出,稀疏矩阵采用三元组顺序表存储后,当非零元素个数较少时会在一定程度上节省存储空间。如果用一个二维数组直接存储稀疏矩阵,此时具有随机存取特性,但采用三元组顺序表存储后会丧失随机存取特性。
例: 假设有一个3×4阶稀疏矩阵B:
B 3 × 4 = [ 1 0 0 2 0 0 3 0 0 0 0 4 ] B_{3×4}=\begin{bmatrix} 1 & 0 & 0 & 2\\ 0 & 0 & 3 & 0\\ 0 & 0 & 0 & 4\\ \end{bmatrix} B3×4=⎣⎡100000030204⎦⎤
#include
#define M<稀疏矩阵行数>
#define N<稀疏矩阵列数>
#define Max (M>N?M:N) //三目运算符,取矩阵行列的较大者
typedef int ElemType;
typedef struct MatNode
{
int row; //行号或者行数
int col; //列号或者列数
struct MatNode* right, * down; //行、列指针
union //共用体
{
ElemType value; //非零元素值
struct MatNode* link; //指向下一个头结点
}tag;
};
例: A = ( ( ( a , b , ( ) , c ) , d ) , e , ( ( f ) , g ) ) A=(((a,b,(),c),d),e,((f),g)) A=(((a,b,(),c),d),e,((f),g))
- 表头是: ( ( a , b , ( ) , c ) , d ) ((a,b,(),c),d) ((a,b,(),c),d),表尾是 ( ( f ) , g ) ((f),g) ((f),g)
- 长度(红框的部分)是:3,深度(括弧的重数,蓝色点的地方)是:4
以上广义表可以看成 A = ( B , C , D ) A=(B,C,D) A=(B,C,D),A中含有3个元素:B、C、D;- 其中,B又是一个子表,可以看成 B = ( E , F ) B=(E,F) B=(E,F),E又是一个子表,有三个原子(a,b,c)以及一个空表();F只含有单个原子d
- C是含有单个原子e
- D又是一个子表,可以看成 D = ( G , H ) D=(G,H) D=(G,H),G是一个只有单个原子f的表,H只含单个原子g。
广义表是一种递归的数据结构,因此很难为每个广义表分配固定大小的存储空间,所以其存储结构只好采用链式存储结构。
从上图可以看到,广义表有两类结点,一类为圆圈结点,在这里对应子表;另一类为方形结点,在这里对应原子。
为了使子表和原子两类结点既能在形式上保持一致,又能进行区别,可采用以下结构形式:
其中,tag域为标志字段,用于区分两类结点,即由tag决定是使用结点的sublist还是data域:
广义表的结点类型GLNode声明如下:
typedef struct lnode
{
int tag; //结点类型标识
union
{
ElemType data; //存放原子值
struct lnode* sublist; //指向子表的指针
}val;
struct lnode* link; //指向下一个元素
}GLNode; //广义表的结点类型