三元组顺序表和广义表
1 稀疏矩阵:
假设在m*n的矩阵中,有t个元素不为0.令q=t/(m*n),称q为矩阵的稀疏因子。通常认为q<=0.05的时候就认为稀疏矩阵。
2 三元组顺序表
如果对每一个元素都分配存储空间的话,矩阵含有大量的0则会造成资源浪费。所以一般我们采用压缩存储的方式,除了存储非0元素的值外,还要存储相应的行和列。因此,稀疏矩阵可以表示成为非0元的三元组及行列数唯一确定 。
相关定义如下:
typedef int Status #define MAX_ROW 100 #define MAX_SIZE 101 typedef int elemtype; typedef struct { int row; //设定行索引值 int col; //设定列索引值 elemtype value; //设定元素值 }Triple; typedef struct { int rn; //设定行数 int cn; //设定列数 int tn; Triple data[MAX_SIZE]; // 设定 数组 }TMatrix;
转置的操作(按照一定顺序进行的转置):
这里我们注意,存储方式是按行存储的。
时间复杂度为rn*tn. 当tn远远小于rn*cn的时候使用,否则时间复杂度相比传统方法大的多。(牺牲时间来优化存储)
//转置操作 void TransposeSMatrix(TMatrix M, TMatrix T) { T.rn = M.cn; T.cn = M.rn; T.tn = M.tn; if (T.tn) { q = 1; for (col = 1; col < M.rn; ++col) for (p = 1; p < M.tn; ++p) if (M.data[p].cn == col) { T.data[q].rn = M.data[p].cn; T.data[q].cn = M.data[p].rn; T.data[q].value = M.data[p].value; } } }
快速转置:
//快速转置 void FastTranspodeSMtrix(TMatrix M, TMatrix T) { T.rn = M.cn; T.cn = M.rn; T.tn = M.tn; if (T.tn) { for (col = 1; col < M.rn; ++col) num[col] = 0; for (t = 1; t <= M.tnl++t) ++num[M.data[t].cn]; cpot[1] = 1; //求第col列中的第一个非0元素在M.data中的序号 for (col = 2; col <= M.tn; ++p) cpot[col] = cpot[col - 1] + num[col - 1]; for (p=1; p <= M.tn; ++p) { col = M.data[p].cn; q = cpot[col]; T.data[q].rn = M.data[p].cn; T.data[q].cn = M.data[p].rn; T.data[q].value = M.data[p].value; ++cpot[col]; } } }
这个算法相比前一个多了两个辅助向量。有两个单次循环,所以时间复杂度为rn+cn.在M的非0元素个数和rn*cn相同的数量级的时候,时间复杂度与经典算法相同。
3 行逻辑连接的顺序表:
我们把指示“行”信息的辅佐数组cpot固定在稀疏矩阵的存储结构当中。
下述为两个矩阵相乘的算法:
typedef struct { Triple data[MAX_SIZE]; int rpos[MAX_ROW]; int rn, cn, tn; }RLSMatrix; //三元组定义 默认行从1开始 void MultsMatrix(PLSMatrix a, RLSMatrix b, RLSMatrix c) { elemtype ctemp[MAX_SIZE]; int p, q, arow, ccol, brow, t; if (a.cn != b.rn) { cout << "Error" << endl; } else { c.rn = a.rn; c.cn = b.cn; c.tn = 0; if (a.tn * b.tn != 0) { for (arow = 1; arow <= a.rn; ++arow) //处理a的每一行 { ctemp[arow] = 0; //当前行各元素累加器清0 c.rpos[arow] = c.tn + 1; p = a.rpos[arow]; for (; p < a.rpos[arrow + 1]; ++p) //对当前行中的每一个非0元 { brow = a.data[p].col; //找到对应在b中行号 if (brow < b.rn) t = b.rpos[brow + 1]; else t = b.tn + 1; for (q = b.rpos[brow]; q < t; ++q) { ccol = b.data[q].col; ctemp[ccol] += a.data[p].value * b.data[q].value; } } for(ccol=1;ccol<=c.cn;++ccol) //压缩存储在行的非0元 if (ctemp[ccol] != 0) { if (++c.tn > MAX_SIZE) { cout << "Error" << endl; exit(0); } else c.data[c.tn] = (arow, ccol, ctemp[ccol]); } } } } }
4 十字链表
当矩阵的非0元个数和位置在操作过程中变化较大时,就不宜采用顺序结构来表示三元组的线性表。例如,将矩阵B加到矩阵A,由于非0元的操作会引起数组中的变化。所以,采用链性表更加方便。
除了行,列和值外,还有两个指针,用以标识同行中的下一个非0元和同列中的下一个非0元。
typedef struct Clnode { int i, j; elemtype e; struct Clnode* down, * right; }OLNode; //下面是整个稀疏矩阵的定义 typedef struct { int mu; int nu; int tu; OLNode* rhead; OLNode* chead; }CrossList; void CreateSMatrix_OL(CressList& M) { int m, n, t; //创建稀疏矩阵。采用十字链表表示 if (M) free(M); cout << '请输入矩阵的M的行数,列数 和非0元素个数' << endl; cin >> m >> n >> t; M.mu = m; M.nu = n; M.tu = t; //数额uM的行数,列数和非0元素个数 if (!(M.rhead = (OLNode*)malloc((m + 1) * sizeof(OLNode)))) cout << "输入数据出现错误" << endl; if (!(M.chead = (OLNode*)malloc((n + 1) * sizeof(OLNode)))) cout << "输入数据出现错误" << endl; M.rhead[] = M.chead[] = NULL; for (cin >> i >> j >> e; i != 0; cin >> i >> j >> e;) { if (!(p = (OLNode*)malloc(sizeof(OLNode))))cout << "数据分配出现错误" << endl; p->i = i; p->j = j; p->e = e; //生成节点 if (M.rhead[i] == NULL || M.rhead[i]->j > j) { p->right = M.rhead[i]; M.rhead[i] = p; } else { //寻找在行表中插入的位置 for (q = M.rhead[i]; (q->right) && q->right->j < j; q = q->right); p->right = q->right; q->right = p; } if (M.chead[j] == NULL || M.chead[j]->i > i) { p->down = M.chead[j]; M.chead[j] = p; } else { //寻找在列表中插入的位置 for (q = M.chead[i]; (q->down) && q->down->j < j; q = q->down); p->down = q->down; q->down = p; //完成列的插入 } } }
5 广义表
广义表是线性表的推广。
广义表一般记作:
LS = (a1, a2, a3,..., an)
每个元素可以是单个元素,也可以是广义表,分别称为广义表的原子和子表。
当广义表非空的时候,我们习惯称第一个元素为表头,称其余的元素为表尾(a2, a3, a4,..., an)。
值得提醒的是列表()和(())不同。前者为空表,长度为0;后者长度为1,可分解得到表头,表尾均为空表()。
这种灵活性就需要用到链式存储结构。需要两种结构的节点,一种是原子的节点,用以表示原子,由标志域,值域组成;另一种是列表,由标值域,标识表头的指针域和值域组成。
1 求广义表的深度(递归):
广义表的深度就是 各个子表深度的最大值加一,注意区分长度;
我们用ATOM(Tag)为0表示原子,反之为子表。
typedef struct GLNode { ElemTag tag; //公共部分,用于区分原子节点和表节点 int atom; GLNode* hp; GLNode* tp; }*GList; //求解表深度 int GListDepth(GList L) { int dep, max; GList pp; //采用头尾链表存储结构,求广义表L的深度 if (!L) return 1; //空表深度为一 if (L->tag == ATOM) retun 0; //原子深度为0 for (max = 0, pp = L; pp; pp = pp->tp) { dep = GListDepth(pp->hp); //求以pp->hp为头指针的子表深度 if (dep > max)max = dep; } return max + 1; }
2 递归复制广义表
分别递归的复制表头和表尾:
//复制广义表 void CopyGlist(GList& T, GList L) { if (!L)T = NULL; //复制空表 else { if(!(T=(GList)malloc(sizeof(GLNode)))cout<<"数据分配出现错误"<<endl; //建表节点 T->tag=L->tag; if(L->tag==0)T->atom=L->atom; //复制单原子 else { CopyGlist(T->hp,L->hp); CopyGlist(T->tp,L->tp); } } }