【数据结构与算法】数组和广义表

广义表的定义及概念

广义表是线性表的推广,是递归定义的、多层次线性结构

广义表的长度定义为最外层包含元素的个数,及最外层逗号数 + 1

深度定义为所含括弧的重数。(原子的深度为0,空表的深度为1)

任何一个非空广义表都可以分为表头和表尾两部分。

对表头表尾的判断例题如下:

广义表的表头和表尾

广义表的存储结构

表头表尾分析法

通常采用头、尾指针的链表结构。

结构体定义如下:

1
2
3
4
5
6
7
8
9
10
typedef enum {ATOM, LIST} ElemTag;//ATOM==0:原子,LIST==1:子表                          
typedef struct GLNode{
ElemTag tag; //用于区分原子结点和表结点
union{ //原子结点和表结点的联合
AtomType atom; //atom是原子结点的值域, AtomType由用户定义
struct {
struct GLNode * hp, *tp;
}ptr;//ptr是表结点的指针域,ptr.hp和ptr.tp分别指向表头和表尾
};
}*GList;//广义表类型

示例如图:

B =(e)

C =(a,(b,c,d))

D = ((),B,C)

E = (a,E)

表头表尾分析法

子表分析法

是一种扩展线性链表存储的方式。

结构体定义如下:

1
2
3
4
5
6
7
8
9
10
typedef enum{ATOM, LIST} ElemTag;//ATOM==0:原子,LIST==1:子表

typedef struct GLNode{
ElemTag tag; //公共部分,用于区分原子结点和表结点
union{ //原子结点和表结点的联合部分
AtomType atom; //是原子结点的值域
struct GLNode * hp;//表结点的表头指针
};
struct GLNode * tp; //相当于线性链表的next,指向下一个元素结点
}*GList;//广义表类型GList是一种扩展的线性链表

示例如图:

子表分析法

稀疏矩阵的几种表示方法

三元组顺序表

结构体定义如下:

1
2
3
4
5
6
7
8
9
10
#define MAXSIZE 12500
typedef struct{
int i, j;//该非零元的行下标和列下标
ElemType e;
}Triple;//三元组类型

typedef union{
Triple data[MAXSIZE + 1];
int mu, nu, tu;//分别为行数、列数、非零元个数
}TSMatrix;//稀疏矩阵类型

如何求转置矩阵:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void TransposeSMatrix1(TSMatrix M, TSMatrix &T) {
T.mu = M.nu; T.nu = M.mu; T.tu = M.tu;
if(T.tu){
q = 1;
for(col = 1; col <= M.nu; ++ col)
for(p = 1; p <= M.tu; p ++) //找每个元素
if(M.data[p].j == col){ //这里比较耗时
T.data[q].i = M.data[p].j;
T.data[q].j = M.data[p].i;
T.data[q].e = M.data[p].e;
++q;
}
}//endif
return OK;
} //DONE

先互换行数和列数,以 q 作为新矩阵元素的下标,遍历全部找每一列的元素,这样的做法时间复杂度为O(mu * tu) 。

可以通过确定每一行的第一个非零元在三元组中的位置来更快的计算。

首先用一个数组 num[col] 来记录第 col 列有多少非零元,然后用数组 cpot[col] 来记录在转置后的新矩阵中,非零元的位置。两个数组的计算方法如下:

1
2
3
cpot[1] = 1;
for(col = 2; col <= M.nu; ++ col)
cpot[col] = cpot[col - 1] + num[col - 1];

示例如下:

示例

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Status FastTransposeSMatrix(TSMatrix M, TSMatrix &T){
T.mu = M.nu;
T.nu = M.mu;
T.tu = M.tu;
if (T.tu) {
for (col = 1; col <= M.nu; ++ col)
num[col] = 0;
for (t = 1; t <= M.tu; ++ t)
++num[M.data[t].j];//计算每一列有多少个非零元
cpot[1] = 1;
for (col = 2; col <= M.nu; ++ col)
cpot[col] = cpot[col-1] + num[col-1];//计算新矩阵中非零元位置
for (p = 1; p <= M.tu; ++ p) {
col = M.data[p].j;
T.data[cpot[col]].i = M.data[p].j;
T.data[cpot[col]].j = M.data[p].i;
T.data[cpot[col]].e = M.data[p].e;
++ cpot[col];//重点!!!!!!!!!!!!!!!!!!!!!
}
} // if
return OK;
} // FastTransposeSMatrix

此算法的时间复杂度为O(M.nu + M.tu) 。

行逻辑连接的顺序表

又称为有序的双下标法,其特点是,非零元在表中按行序有序存储,便于进行依行顺序处理的矩阵运算

结构体定义如下:

1
2
3
4
5
6
#define MAXMN 500
typedef struct{
Triple data[MAXSIZE + 1];//非零元三元组表
int rpos[MAXRC + 1];//各行第一个非零元的位置表
int mu, nu, tu;//矩阵的行数、列数和非零元个数
}RLSMatrix;//行逻辑链接顺序表类型

例如:给定一组下标,求矩阵的元素值。(注意要防止跨行

两个矩阵相乘:

十字链表

对稀疏矩阵的每个非零元,建立一个结点,

结构体定义如下:

向右域right 指向同一行中下一个非零元,向下域down 指向同一列中下一个非零元。

1
2
3
4
5
6
7
8
9
10
11
typedef struct OLNode{
int i, j; //该非零元的行和列下标
ElemType e;
struct OLNode *right, *down;
//该非零元所在行表和列表的后继链域
}OLNode,*OLink;

typedef struct {
OLink *rhead, *chead; //行和列链表头指针向量基址由CreateSMatrix分配
int mu, nu, tu; //稀疏矩阵的行数、列数和非零元个数
} CrossList;

创建稀疏矩阵,采用十字链表存储表示:

Author: iwannaeat
Link: https://iwannaeat.github.io/2020/08/20/【数据结构与算法】数组和广义表/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.