文章目录
- 数组和广义表可以看成是线性表在下述含义上的扩展:表中的数据元素本身也是一个数据结构
1.矩阵的压缩存储
- 假若值相同的元素或者零元素在矩阵中的分布有一定规律,则我们称此类矩阵为特殊矩阵;反之,称为稀疏矩阵。
(1)特殊矩阵的压缩存储
- 若n节矩阵A中的元满足下述性质:
aij=aji 1<=i,i<=n
则称为n阶对称矩阵 - 对于对称矩阵,我们可以只存储下三角,将n2个元压缩存储到n(n+1)/2个元的空间中。
- 这种压缩存储的方法同样也适用于三角矩阵。所谓下(上)三角矩阵是指矩阵的上(下)三角(不包括对角线)中的元均为常数c或零的n阶矩阵。则除了和对称矩阵一样, 只存储其下(上)三角中的元之外,再加一个存储常数c的存储空间即可
(2)稀疏矩阵的压缩存储
- 假设在m×n的矩阵中,有t个元素不为零。令δ = t/(m×n),通常认为δ≤0.05时称为稀疏矩阵
① 三元组顺序表压缩
- 存储表示
#define MAXSIZE 12500 //假设非零元的最大值
typedef struct
{
int i,j; //该非零元的行下标和列下标
ElemType data;
}Triple;
typedef struct
{
Triple data[MAXSIZE+1];
int mu,nu,tu; //矩阵的行数、列数和非零元个数
}TSMatrix;
- 压缩的稀疏矩阵对转置矩阵的运算是由很大优势的,只需要进行下面几部:
- 将矩阵的行列值相互交换
- 将每个三元组的i和j相互调换
- 重排三元组之间的次序便可实现矩阵的转置。
- 主要难点在于第三步,下面给出两种解法
- 解法1:按照矩阵M的列序来进行转置。为了找到M的每一列中所有的非零元素,需要对其三元组表a.data从第一行起整个扫描一遍,由于a.data是以M的行序为主序来存放每个非零元,由此得到的恰是b.data应有的顺序
bool TransposeSMatrix(TSMatrix M, TSMatrix& T)
{
T.mu = M.nu; T.nu = M.mu; T.tu = M.tu;
int num = 0;
if (!T.tu) return false;
for (int col = 0; col < M.nu; ++col)
{
for (int k = 0; k < M.tu; ++k)
{
if (M.data[k].j == col)
{
T.data[num].i = M.data[k].j;
T.data[num].j = M.data[k].i;
T.data[num].data = M.data[k].data;
++num;
}
}
}
return true;
}
- 一般的矩阵转置算法为O(mu×nu),而上述算法如果tu的数量级和mu×nu相同时,时间复杂度为O(mu×nu2)虽然节省了存储空间,但时间复杂度提高了,因此算法仅适于tu<<mu×nu的情况
- 解法2:按照a.data中三元组的次序进行转置其,并将转置后的三元组置入b中恰当的位置。如果能预先确定矩阵M中每一列(即T中每一行)的第一个非零元在b.data中应有的位置,那么在对a.data中的三元组依次做转置时,便可直接放到b.data中恰当的位置上去。为了确定这些位置,在转置前 ,应先求得M的每一列中非零元的个数,进而求得每一列的第一个非零元在b.data中应有的位置。
在此,需要附设num和cpot两个向量。num[col]表示矩阵M中第col列中非零元的个数,cpot[col]指示M中第col列中的第一个非零元在b.data中的恰当位置
bool FastTransposeSMatrix(TSMatrix M, TSMatrix& T)
{
int num[MAXSIZE+1];
int cpot[MAXSIZE + 1];
memset(num, 0, M.tu);
memset(cpot, 0, M.tu);
//得到M中每列元素的个数
for (int k = 0; k < M.tu; ++k)
++num[M.data[k].j];
//得到每一列第一个元素放在T中的位置
for (int k = 1; k < M.nu; ++k)
cpot[k] = cpot[k- 1] + num[k - 1];
for (int k = 0; k < M.nu; ++k)
{
T.data[cpot[M.data[k].j]].i = M.data[k].j;
T.data[cpot[M.data[k].j]].j = M.data[k].i;
T.data[cpot[M.data[k].j]].data = M.data[k].data;
++cpot[M.data[k].j];
}
return true;
}
- 上述算法时间复杂度仅为O(tu+nu)
② 行逻辑链接的顺序表
- 为了便于随机存储任意一行的非零元,则需知道每一行的第一个非零元在三元组表中的位置。谓词,可将指示“行”信息的辅助数组固定在稀疏矩阵的存储结构中如下:
#define MAXSIZE 12500 //假设非零元的最大值
typedef struct
{
int i,j; //该非零元的行下标和列下标
ElemType data;
}Triple;
typedef struct
{
Triple data[MAXSIZE+1];
int rpos[MAXRC+1];
int mu,nu,tu; //矩阵的行数、列数和非零元个数
}TSMatrix;
- 普通矩阵乘法算法:
时间复杂度为O(m1×n1×n2)
for(int i =1;i<m1:++i){
for(int j=1;j<=n2;++j){
Q[i][j] = 0;
for(k=1;k<=n1;++k)
Q[i][j]+=M[i][k]*N[k][j];
}
}
- 利用压缩后的矩阵计算乘法:
//矩阵相乘
bool MultSMatrix(TSMatrix M, TSMatrix N, TSMatrix& Q)
{
//不符合格式直接退出
if (M.nu != N.mu) return false;
//对Q初始化
Q.mu = M.mu;
Q.nu = N.nu;
Q.tu = 0;
int num = 0;
memset(Q.rpos, 0, Q.mu * sizeof(ElemType));
//计算
for (int row = 0; row < M.mu; ++row)
{
int tmp[MAXRC + 1] = { 0 };
for (int k = M.rpos[row]; k < M.rpos[row + 1]; ++k)
{
int col = M.data[k].j;
for (int q = N.rpos[col]; q < N.rpos[col + 1]; ++q)
{
tmp[N.data[q].j] += M.data[k].data * N.data[q].data;
}
}
for (int k = 0; k < N.nu; ++k)
{
if (tmp[k] != 0)
{
Q.data[num].i = row;
Q.data[num].j = k;
Q.data[num].data = tmp[k];
num++;
}
}
Q.rpos[row+1] = num;
}
}
③ 十字链表
- 当矩阵的非零元个数和位置在操作过程中变化较大时,就不宜采用顺序存储结构来表示三元组的线性表。例如,在作“将矩阵B加到矩阵A上”的操作时,由于非零元的插入或删除将会引起A.data中元素的移动。为此,对这种类型的矩阵,采用链式存储结构表示三元组的线性更为恰当
- 在链表中,每个非零元可用一个含5个域的结点表示,其中i,j和e这3个域分别表示该非零元所在的行、列和非零元的值,向右域right用以链接同一行中下一个非零元,向下down用以链接同一列中的下一个非零元
- 稀疏矩阵的十字链表存储表示
typedef struct OLNode
{
int i,j;
ElemType e;
struct QLNode *right,*down;
}OLNode,*QLink;
typedef struct
{
OLink *rhead,*chread;
int mu,nu,tu;
}CrossList;
- 十字链表的初始化
bool CreateSMatrix_OL(CrossList& M)
{
//输入M的行数、列数和非零元个数
cin >> M.mu >> M.nu >> M.tu;
M.rhead = (OLink*)malloc((M.mu + 1) * sizeof(OLink));
M.chead = (OLink*)malloc((M.nu + 1) * sizeof(OLink));
if (M.rhead || M.rhead) return false;
//初始化行列头指针向量
for (int i = 1; i < M.mu + 1; ++i)
M.rhead[i] = nullptr;
for (int i = 1; i < M.nu + 1; ++i)
M.chead[i] = nullptr;
//插入数据
for (int k = 0 ;k < M.tu; ++k)
{
int i, j;
ElemType e;
cin >> i >> j >> e;
//生成结点
ONode* newNode = (ONode*)malloc(sizeof(ONode));
newNode->i = i; newNode->j = j; newNode->e = e;
//行插入
if (M.rhead[i] == NULL || M.rhead[i]->j > j)
{
newNode->right = M.rhead[i];
M.rhead[i] = newNode;
}
else
{
for (ONode* q = M.rhead[i]; (q->right) && (q->right->j < j); q = q->right)
{
//插到q的后面
newNode->right = q->right;
q->right = newNode;
}
}
//列插入
if (M.chead[j] == NULL || M.chead[j]->i > i)
{
newNode->down = M.chead[j];
M.chead[j] = newNode;
}
else
{
for (ONode* q = M.chead[j]; (q->down) && (q->down->i < i); q = q->down)
{
//插到q的后面
newNode->down = q->down;
q->down = newNode;
}
}
}
return true;
}
2.广义表
(1)广义表的定义
-
广义表是线性表的推广,也有人称其为列表。在线性表的定义中,ai可以是单个元素,也可以是广义表,分别称为广义表LS的原子和子表。
-
习惯上,用大写字母表示广义表的名称,用小写字母表示原子。
-
当广义表LS非空时,称第一个元素为LS的表头,称其余元素组成的表是LS的表尾,如果说表中只有一个元素,则表尾为空表
-
列表的三个重要结论
- 列表的元素可以是字表,而子表的元素还可以是子表
- 列表可为其他列表所共享
- 列表可以是一个递归的表,即列表也可以是其本身的一个子表
(2)广义表的存储结构
- 由于广义表中的数据元素可以具有不同的结构,因此难以用顺序存储结构表示,通常采用链式存储结构。
- 广义表的头尾链表存储表示
typedef enum { ATOM, LIST }ElemTag;
typedef int AtomType;
typedef struct GLNode{
ElemTag tag; //公共部分标志位
union
{
AtomType atom;
struct {
struct GLNode* hp, * tp;
}ptr;
};
}GLNode,*GList;
- 示例:
(3)广义表的应用——m元多项式
- 如果单纯用普通链表表示m元多项式,每个结点需要分配m+1个空间存储1个系数和m个值数,然而不是每个项都有m个值数,会造成存储空间的浪费。通过下面例子我们看出一个m元多项式可以改写成广义表的形式:
- 任何一个m元多项式都可以这么做:先分解出一个主变元,随后再分解出第二个变元等等。
- 可以用如下结构存储:
typedef struct MPNode
{
ElemTag tag;
int exp;
union
{
float coef;
struct MPNode *hp;
};
struct MPNode *tp;
}*MPList;
(4)广义表的递归算法
① 求广义表的深度
- 广义表的深度定义为广义表中括弧的重数,是广义表的一种量度
- 广义表深度的递归定义为:
基本项:
DEPTH(LS)= 1 当LS为空表
DEPTH(LS)= 0 当LS为原子
归纳项:
DEPTH(LS)= 1 + MAX{DEPTH(ai)} n>=1
int GListDepth(GList L)
{
if(!L) return 1;
if(L->tag == ATOM) return 0;
for(max = 0;pp=L;pp;pp=pp->ptr.tp)
{
dep = GListDepth(pp->ptr).hp);
if(dep>max) max = dep;
}
return max+1;
}