本节为数组与广义表内容,回到总目录:点击此处
数组
N维数组是数据元素为N-1维数组的线性表。
ADT Array
{
数据对象:
结构关系:
基本操作:
①InitArray 维数n和各维的长度合法,则构造相应的数组A,并返回True
②DestroyArray(A):销毁数组A
③GetValue :若下标合法,则用e返回其值
③Setvalue: 若下标合法,将指定的元素值置为e
}
注意这里的数组下标从1开始
·数组的顺序存储结构有两种:
1、按行序存储 2、按列序存储
二维数组 A m n A_{mn} Amn 首元素a11 Loc[1,1]
Loc[i,j] = Loc[1,1] + n x (i -1) + (j-1)如果每个元素占size个存储单元 则 Loc[i,j] = Loc[1,1] + (n X (i-1) + (j -1)) x size
三维数组 Loc[i,j,k] = Loc[1,1,1] + (i - 1)xmxn + (j-1) xn +(k -1)
特殊矩阵的压缩存储
原则:对于有规律的元素和值相同的元素只分配一个存储单元,对于零元素则不分配空间。
三角矩阵
矩阵空间压缩—>> Loc[i,j] = Loc[1,1] + (i x (i - 1)/2 + j - 1);
↑将空间压缩成
n
(
n
+
1
)
/
2
n(n+1)/2
n(n+1)/2
带状矩阵
1、确定一维向量空间的大小:所需的一维向量空间大小:2+2+3x(n-2) = 3n-2;
2、确定非零元素的地址 Loc(A[i] [j]) = Loc(A[1] [1]) + (前i - 1行非零元素个数 +第i行中aij前非零元素个数)
由此可得 Loc[i,j] = Loc[1,1] + 3x(i-1) -1 +j -i +1 ----> Loc[1,1] +2(i-1)+j-1
稀疏矩阵
存储方式:三元组表表示法
下标 行号 列号 元素值 1 1 2 12 2 1 3 9 3 3 1 -3 4 3 6 14 5 6 4 16
#define MAXSIZE 1000
typedef struct
{
int row,col; //非零元素的行下标和列下标
ElementType e; //非零元素的值
}Triple;
typedef struct
{
Triple data[Maxsize + 1]; //非零元素的三元组表,data[0]未使用
int m,n,len; //矩阵的行数、列数、和非零元素的个数
}TSMatrix;
· 用三元组实现稀释矩阵的转置运算
·常规方法:
void TransMatrix(ElementType source[m][n], ElementType dest[n][m]) { //source转置前的 、 dest转置后 int i ,j; for(i = 0 ; i < m; i++) { for (j = 0;j < n; j ++) { dest[j][i] = source[i][j]; } } }
·使用三元组,如果仅仅交换行列值,则转置后的序列不是按行序为主存储的,为保证顺序,需要重新进行排序,这样会消耗大量的时间,可以采取以下两种处理方法
方法一 :列序递增转置法
每次扫描先把原矩阵中的列标按顺序进行存储,然后再进行转置处理
void TransposeTSMatrix (TSMatrix A, TSMatrix *B) { int i,j,k; B -> m = A.n; B -> n =A.m;B -> len = A.len; if(B -> len > 0) { j = 1; //j为辅助计数器,记录转置后的三元组在三元组表B的下标值 for(k = 1; k <= A.n; k ++) //扫描三元组A共A.n次,每次寻找列值为k的三元组进行转置 { for(i = 1; i <= A.n; k++) { if(A.data[i].col == k) //寻找col为k的三元组进行转置 { B -> data[j].row = A.data[i].col; B -> data[j].col = A.data[i].row; B -> data[j].e = A.data[i].e; j ++; } } } } }
方法二:一次定位快速转置法
num[col] 用来存放三元组表A第col列中非零元素总个数
postion[col] 用来存放转置前三元组表A中第col列中第一个非零元素在三元组表B中的存储位置(下标值)
其三元组的表现形式:
TermA
TermB
FastTransposeMatrix(TSMatrix A, TSMatrix *B) { int col,t,p,q; int num[Maxsize],position[Maxsize]; B -> len = A.len; B -> n = A.m; B -> m = A.n; if (B -> len) { for(col = 1; col <= A; col ++) //置为0 { num[col] = 0; } for(t = 1; t <= A.len; t ++) { num[A.data[t].col]++; //数组下标计数法计算每一列的非零元素的个数 } position[1]=1; for(col = 2;col <= A.n; col ++) //求col列中第一个非零元素在B.data[]中的正确位置 { position[col] = position[col - 1] + num[col - 1]; //分析一下i = 2时,position[2]为第2列第一个非零 元应在的索引位置,它显然等于 第一列第一个非零元所应在的索引位置 + 第一列的非零元个数, //此列第一个非零元所应在的索引位置 = 上一列第一个非零元所应在的索引位置 + 上一列的非零元素个数。 //num数组只是为了求position数组而服务的 } for(p = 1; p <= A.len; p ++) { col = A.data[p].col; q = position[col]; //第col列第一个元素的在B中的索引位置 B -> data[q].row = A.data[p].col; B -> data[q].col = A.data[p].row; B -> data[q].e = A.data[p].e; position[col]++; //指向下一个列标为col的非零元素在三元组B中的存放位置 //此处自增1的含义:之后,把index自增1。这点非常重要!很多人不理解这句话是什么意思,这里解释一下。 //在矩阵A中,可以看到第一列有两个元素,分别在第一行和第六行。在position[1]中只指出了第一行元素应该对应的索引 值,却没有指出第六行元素应该对应的索引值。因此,在进行第一个元素的映射后,此元素就没有用了,该列后面的第一个元素 就可以顺理成章地成为“该列的第一个元素” 。并且这个元素所对应的termsB索引值是紧挨着上一元素的, position[terms[i].col]++就这这个道理。 } } }
1、初始化num 2、计算num 3、初始化position(在B数组中的正确位置)position指的是索引位置
参考资料:https://blog.csdn.net/Elford/article/details/109178804
稀疏矩阵的链式存储结构:十字链表
进行矩阵加法、减法时,有时非零元素发生很大变化,如果仍用三元组的表示方法,则可能会出现需要移动大量元素的情况,故为了避免这一情况,可以使用稀疏矩阵的链式存储法——十字链表
//十字链表的定义方法:
typedef struct OLNode
{
int row,col;
ElementType value;
struct OLNode *right, * down; //两个链域 right指向同一行中的下一个非零元素,down指向同一列中下一个非零元素。
}OLNode,*OLink;
typedef struct
{
OLink * row_head,*col_head; //附设一个存放所有行链表的头指针的一维数组,和一个存放所有列链表的头指针的一维数组
int m,n,len;
}CrossList;
·建立稀疏矩阵的十字链表
CreateCrossList(CrossList *M)
{
scanf(&m,&n,&t); //M行,n列以及非零元素个数
M -> m = m;
M -> n = n;
M -> len = t;
if(!(M -> row_head = (OLink *)malloc((m+1)sizeof(OLink)))) exit(OVERFLOW);
if(!(M -> col_head = (OLink *)malloc((n+1)sizeof(OLink)))) exit(OVERFLOW);
M -> row_head[] = M -> col_head[] = NULL;
for(scanf(&i,&j,&e); i != 0; scanf(&i,&j,&e))
{
if(!(p = (OLNode *)malloc(sizeof(OLNode)))) exit(OVERFLOW);
p -> row = i;
p -> col = j;
p -> value = e;
if(M -> row_head[i] == NULL) M -> row_head[i] = p;
else
{
//寻找行表中的插入位置
q = M -> row_head[i];
while(q -> right != NULL && q -> right -> col <j)
q = q ->right;
p -> right = q ->right;
q -> right = p;//完成插入
}
if(M -> col_head[j] == NULL) M -> col_head[i] = p;
else
{
//寻找列表中的插入位置
q = M -> col_head[j];
while (q -> down != NULL && q -> down -> col <i)
q = q -> down;
p -> down = q -> down;
q -> down = p;
}
}
}
参考资料:(矩阵的十字链表)https://blog.csdn.net/xiangxizhishi/article/details/79119532
广义表
广义表也是一种线性表,只不过在广义表内,其中n个元素中,元素即可以是一个单元素,也可以是一个广义表,广义表通常用GL表示。
GL={d1,d2,d3…dn}
·d1 —— 是广义表表头
·(d2,d3…dn) —— 是广义表表尾
·表头可以是原子或表,但表尾一定是表
··广义表()与(())表示的含义不同,前者表示广义表为空表,无法进行求表头表尾运算,而后者表示广义表长度为1,其中有唯一元素为空表
一些例子
1.$A = () $ 表A是一个长度为0的空表
2.B=(e) 表B只有一个原子e,其长度为1
3.C=(a,(b,c,d)) 表C的长度为2,一个元素为原子a,另一个为子表(b,c,d)
4.$ D = (A, B, C)$ 表D的长度为3,三个元素都是子表。
5.$ E = (a, E)$ 表E是一个递归的表,它的长度为2,但它可以
无限的展开,因此它是无限的列表。
6.F=(()) 表F是一个长度为1的表,它的元素是一个空表。另外,根据前述定义,广义表分为表头和表尾,而表尾一定是列表。值得注意的是,只有单个元素的表的表尾是空表。例如表B的表头为e,表尾为空表。
上面说到的深度在这里就可以理解为展开后括号的层数。
广义表的两种结构参考资料:http://data.biancheng.net/view/190.html
·广义表的头尾链表存储结构
这里用到了 union 共用体,因为同一时间此节点不是原子节点就是子表节点,当表示原子节点时,就使用 atom 变量;反之则使用 ptr 结构体。
例如,广义表 {a,{b,c,d}} 是由一个原子 a 和子表 {b,c,d} 构成,而子表 {b,c,d} 又是由原子 b、c 和 d 构成,用链表存储该广义表如图 2 所示:
typedef enum {ATOM,LIST} ElemTag; //ATOM=0表示原子,LIST=1表示子表
typedef struct GLNode
{
ElemTag tag;
union
{
AtomType atom;
struct {struct GLNode *hp,*tp;} htp;
}atom_htp;
}GLNode, *GList;
·广义表的同层结点链存储结构
在这个结构中,无论是原子结点还是表节点均由三个域组成
//同层结构链存储结构定义
typedef enum {ATOM, LIST } ElemTag;
typedef struct GLNode
{
ElemTag tag;
union
{
AtomType atom;
struct GLNode *hp; //表头指针域
}atom_htp; //这是原子结点的值域atom和表结点的表头指针域hp的联合体域
struct GLNode *tp; //同层下一结点的指针域
}GLNode, *GList;
广义表的操作实现
·求广义表L的表头
GList Head(GList L)
{//求广义表表头并返回表头指针
if(L == NULL) return NULL; //空表无表头
if(L -> tag == ATOM) exit(0); //原子不是表
else return(L -> atom_htp.htp.hp);
}
·求广义表L的表尾
GList Tail(GList L)
{
if(L == NULL) return NULL //空表无表尾
if (L -> tag == ATOM) exit(0); //原子不是表
else return(L -> atom_htp.htp.tp);
}
·求广义表的长度
int Length(GList L)
{
int k = 0;
GLNode *s;
if(L == NULL) return 0;
if(L -> tag == ATOM) exit(0);
s = L;
while(s != NULL)
{
k ++;
s = s -> atom_htp.htp.tp;
}
return k;
}
·求广义表的深度
int Depth (GList L)
{
int d,max;
GLNode *s;
if(L == NULL) return 1; //空表深度为1
if(L -> tag == ATOM) return 0;
s = L;
while(s != NULL)
{
d = Depth(s -> atom_htp.htp.hp);
if (d > max) max = d;
s = s -> atom_htp.htp.tp;
}
return max+1;//表的深度等于最深子表的深度+1
}
·统计广义表中原子数目
int CountAtom(GList L)
{
int n1,n2;
if (L == NULL) return 0;
if (L -> tag == ATOM) return 1;
n1 = CountAtom (L -> atom_htp.htp.hp);
n2 = CountAtom (L -> atom_htp.htp.tp);
return n1+n2;
}
·复制广义表
int CopyGList(GList S, GList *T)
{
if(S == NULL) {*T = NULL; return;}
*T = (GLNode *)malloc(sizeof(GLNode));
if(*T == NULL) return error;
(*T) -> tag = S -> tag;
if (S -> tag == ATOM) (*T) -> atom = S -> atom;
else
{
CopyGList(S -> atom_htp.htp.hp,&((*T) -> atom_htp.htp.hp)); //复制表头
CopyGList(S -> atom_htp.htp.tp,&((*T) -> atom_htp.htp.tp)); //复制表尾
}
return OK;
}