【数据结构】从0开始数据结构学习-数组与广义表

本节为数组与广义表内容,回到总目录:点击此处

数组

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

稀疏矩阵

存储方式:三元组表表示法

下标行号列号元素值
11212
2139
331-3
43614
56416
#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中的存储位置(下标值)

img

img

其三元组的表现形式:
TermA
img

TermB
img

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;

img

·建立稀疏矩阵的十字链表

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

·广义表的头尾链表存储结构

img

这里用到了 union 共用体,因为同一时间此节点不是原子节点就是子表节点,当表示原子节点时,就使用 atom 变量;反之则使用 ptr 结构体。
例如,广义表 {a,{b,c,d}} 是由一个原子 a 和子表 {b,c,d} 构成,而子表 {b,c,d} 又是由原子 b、c 和 d 构成,用链表存储该广义表如图 2 所示:
img

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;

·广义表的同层结点链存储结构
在这个结构中,无论是原子结点还是表节点均由三个域组成
img

img

//同层结构链存储结构定义
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值