【数据结构】CH6 数组和广义表
目录
前言
一、数组
1.数组的基本概念
(1)定义
(2)性质
(3)d维数组的抽象数据类型
(4)【例6.1】
2.数组的存储结构
(1)一维数组的存储结构
(2)二维数组的存储结构
(3)三维数组的存储结构
3.特殊矩阵的压缩存储
(1)前言
(2)对称矩阵的压缩存储
(3)下三角矩阵的压缩存储
(4)上三角矩阵的压缩存储
(5)三对角矩阵的压缩存储
二、稀疏矩阵
1.定义
2.稀疏矩阵的三元组表(list of 3-tuples)
(1)含义
(2)三元组顺序表的数据类型声明
(3)从一个二维稀疏矩阵创建其三元组表示
(4)三元组元素的赋值
(5)将指定位置的元素值赋给变量
(6)输出三元组
(7)稀疏矩阵转置
3.稀疏矩阵的十字链表表示
三、广义表
1.广义表的定义
2.广义表的存储结构
3.广义表的运算
(1)广义表的算法设计方法
(2)求广义表的长度
(3)求广义表的深度
(4)输出广义表
(5)建立广义表的链式存储结构
(6)销毁广义表
前言
数组(array)是具有相同类型的数据元素的有限序列,可以将它看作是线性表的推广,稀疏矩阵是一种特殊的二维数组。广义表也可以看成是线性表的推广,它是采用递归方法定义的。
本文介绍数组、稀疏矩阵和广义表的存储结构及相关算法设计。
一、数组
1.数组的基本概念
(1)定义
数组是n(n>1)个相同数据类型数据元素构成的有限序列。
【注】
- 从逻辑结构上看,一维数组A是n(n>1)个相同类型数据元素构成的有限序列,其逻辑表示如下:,其中,表示数组A的第i个元素
- 一个二维数组可以看作是每个数据元素都是相同类型的一维数组的一维数组。依此类推,任何多维数组都可以看作一个线性表,这时线性表中的每个数据元素也是一个线性表
- 推广到d()维数组,不妨把它看作一个由d-1为数组作为数据元素的线性表;或者可以这样理解,它是一种较复杂的线性结构,由简单的数据结构(即 线性表)辗转合成而得。所以说数组是线性表的推广。在d维数组中,每个元素的位置由d个整数的d维下标来标识
(2)性质
- 数组中的数据元素数目固定(定长)
- 数组中的数据元素具有相同的数据类型
- 数组中的每个数据元素都和一组唯一的下标值对应
- 数组是一种随机存储结构,可以随机存取数组中的任意数据元素
因此,用户可以在C/C++程序中直接使用数组来存放数据,并使用数组的运算符来完成相应的功能。
(3)d维数组的抽象数据类型
从上可以看出,数组除了初始化和销毁以外,在数组中通常只有下面两种操作:
- 读操作:给定一组下标,读取相应的数组元素
- 写操作:给定一组下标,存储或者修改相应的数组元素
(4)【例6.1】
利用数组求解约瑟夫问题:设有n个人站成一圈,其编号为1~n。从编号为1的人开始按顺时针方向“1,2,3,4……”循环报数,数到m的人出列,然后从出列者的下一个人重新开始报数,数到m的人又出列,如此重复进行,直到n个人都出列为止,要求输出这n个人的出列顺序。
【举例】
例如,有8个人的初始序列为:1,2,3,4,5,6,7,8
当m=4时,出列顺序为:4,8,5,2,1,3,7,6
【分析】采用一维数组p[]存放人的编号,先将n个人的编号存入到p[0]~p[n-1]中。从编号为1的人(下标t=0)开始循环报数,数到m的人p[t](下标为t=(t+m-1)%i),i表示当前未出列的人数)输出并将其从数组中删除(即将后面的元素前移一个位置),因此每次报数的起始位置就是上次报数的出列位置。反复执行直到出列n个人为止。算法如下:
void josephus(int n,int m)
{int p[MaxSize];int i,j,t;for(i=0;i<n;i++) //构建初始序列(1,2,……,n)p[i]=i+1;t=0; //首次报数的起始位置printf("出列顺序:");for(i=n;i>=1;i--) //i为数组p中当前的人数,出列一次,人数减一{t=(t+m-1)%i; //t为数列者的编号printf("%d",p[t]); //编号为t的元素出列for(j=t+1;j<=i-1;j++) //后面的元素前移一个位置p[j-1]=p[j];}printf("\n");
}
【注】本文的数组是作为一种数据结构讨论的,而C/C++中的数组是一种数据类型,前者可以借助后者来存储,像线性表的顺序存储结构(即顺序表)就是借助一维数组这种数据类型来存储的,二者不能混淆。
2.数组的存储结构
在设计数组的存储结构时,通常将数组的所有元素存储到存储器的一块地址连续的内存单元中,即数组特别适合采用顺序存储结构来存储(数组主要随机访问,没有插入和删除,所以采用顺序方式存储)。
(1)一维数组的存储结构
- 按次序依次存储在一组连续的存储空间中
- 多维数组的存储:计算机内存是一维的,如何存储多维数组?将多维数组中每个元素按某种次序排列成一维结构。其中,以行为主序顺序和以列为主序顺序
(2)二维数组的存储结构
- 行优先顺序存储结构
若第一个元素为,则:
- 列有优先顺序存储结构
若第一个元素为,则
(3)三维数组的存储结构
3.特殊矩阵的压缩存储
(1)前言
问题:
- 矩阵中有大量相同的非零元素(多个相同的非零元素只分配一个存储空间)
- 矩阵中有大量的零元素(零元素不分配存储空间)
- 通常可以采用二维数组存储矩阵的空间复杂度:
- 特殊矩阵是指非零元素或零元素的分布有一定规律的矩阵,如:对称矩阵、三角矩阵、对角矩阵等
(2)对称矩阵的压缩存储
- 对称矩阵的特点:
- 只存储上三角或者下三角中的元素
- 矩阵中n*n个元素只需要个存储单元
(3)下三角矩阵的压缩存储
- 矩阵中n*n个元素只需要个存储单元
(4)上三角矩阵的压缩存储
- 矩阵中n*n个元素只需要个存储单元
(5)三对角矩阵的压缩存储
二、稀疏矩阵
1.定义
当一个阶数较大的矩阵中非零元素个数s相对于矩阵元素的总数t非常小时,即s<<t时,称该矩阵为稀疏矩阵( )。例如一个100*100的矩阵,若只有100个非零元素,就可称其为稀疏矩阵。
稀疏矩阵和上面介绍的特殊矩阵相比有一个明显的差异:特殊矩阵中特殊元素的分布具有某种规律性,而稀疏矩阵中特殊元素(非零元素)的分布没有规律,即具有随机性。
稀疏矩阵抽象数据类型与d(d=2)维数组抽象数据类型的描述相似,这里不再介绍。
2.稀疏矩阵的三元组表(list of 3-tuples)
(1)含义
由于非零元素在矩阵中的位置没有规律,所以不但要存储非零元素的值,而且还要存储其在矩阵中的位置。若把稀疏矩阵的三元组线性表按顺序结构存储,则称为稀疏矩阵的三元组顺序表,简称为三元组表(list of 3-tuples)。
(2)三元组顺序表的数据类型声明
#define M<稀疏矩阵行数>
#define N<稀疏矩阵列数>
#define MaxSize<稀疏矩阵中非零元素最多的个数>
typedef struct
{int r; //行号int c; //列号ElemType d; //元素值
}TupNode; //三元组类型
typedef struct
{int rows; //行数int cols; //列数int nums; //非零元素个数TupNode data[MaxSize];
}TSMatrix; //三元组顺序表的类型
- 【注】data域中表示的非零元素通常以行序为主序排列
(3)从一个二维稀疏矩阵创建其三元组表示
稀疏矩阵的运算包括矩阵转置、矩阵加、矩阵减和矩阵乘等,这里仅讨论一些基本运算算法。
采用以行为主序的方式扫描二维稀疏矩阵A,将其中非零的元素依次插入到三元组顺序表t中。
void CreatMat(TSMatrix &t,ElemType A[M][N])
{int i,j;t.rows=M;t.cols=N;t.nums=0;for(i=0;i<M;i++){for(j=0;j<N;j++)if(A[i][j]!=0){t.data[t.nums].r=i;t.data[t.nums].c=j;t.data[t.nums].d=A[i][j];t.nums++;}}
}
(4)三元组元素的赋值
- 该运算是对稀疏矩阵A执行(x通常是一个非零值)。先在三元组顺序表t中找到适当的位置k,如果该位置对应一个非零元素,将其d数据域修改为x;否则,需要插入一个非零元素,将的元素均后移一个位置,再将非零元素x插入到处
bool Value(TSMtrix &t,ElemType x,int i,int j)
{int k=0,k1;if(i>=t.rows||j>=t.cols) //i,j超界return false;while(k<t.nums&&i>t.data[k].r) k++; //查找到第i行的第一个非零元素while(k<t.nums&&i==t.data[k].r&&j>t.data[k].c) k++; //在第i行的非零元素中查找第j列if(t.data[k].r==i&&t.data[k].c==j) //若存在这样的非零元素t.data[k].d=x; //修改非零元素的值else //若不存在这样的非零元素{for(k1=t.nums-1;k1>=k;k1--) //若干元素均后移一个位置{t.data[k1+1].r=t.data[k1].r;t.data[k1+1].c=t.data[k1].c;t.data[k1+1].d=t.data[k1].d;}t.data[k].r=i; //插入非零元素xt.data[k].c=j;t.data[k].d=x;t.nums++; //非零元素个数增1}return true; //成功操作后返回真
}
(5)将指定位置的元素值赋给变量
- 该运算就是对于稀疏矩阵A执行,即提取A中指定下标的元素值。先在三元组顺序表t中查找指定的位置,若找到了,说明是一个非零元素,将其值赋给x;否则说明是零元素,置x=0
bool Assign(TSMatrix t,ElemType &x,int i,int j)
{int k=0;if(i>=t.rows||j>=t.cols) //参数超界return false;while(k<t.nums&&i>t.data[k].r) //查找第i行k++;while(k<t.nums&&i==t.data[k].r&&j>t.data[k].c) //在第i行的非零元素中查找第j列k++;if(t.data[k].r==i&&t.data[k].c==j) //若存在这样的非零元素x=t.data[k].d; //提取元素值else //若不存在这样的非零元素x=0; //置x为0return true; //成功操作后返回真
}
(6)输出三元组
- 该运算从头到尾扫描三元组顺序表t,依次输出元素值。
void DispMat(TSMtrix t)
{int k;if(t.nums<=0)return;printf("\t%d\t%d\t%d\n",t.rows,t.cols,t.nums);printf("\t------------------------\n");for(k=0;k<t.nums;k++)printf("\t%d\t%d\t%d\n",t.data[k].r,t.data[k].c,t.data[k].d);
}
(7)稀疏矩阵转置
- 该运算对于一个m*n的稀疏矩阵,求其转置矩阵,即,,。采用的算法思路是A对应的三元组顺序表为t,其转置矩阵B对应的三元组顺序表为tb。按在中找列号为的元素,没找到一个这样的元素,将行、列交换后添加到tb中
void TranTat(TSMatrix t,TSMatrix &tb)
{int k,k1=0,v; //k1记录tb中的元素个数tb.rows=t.cols;tb.cols=t.rows;tb.nums=t.nums;if(t.nums!=0) //当存在非零元素时执行转置{for(v=0;v<t.cols;v++) //按v=0,1,2,……,t.cols循环{for(k=0;k<t.nums;k++) //k用于扫描t.data的所有元素{if(t.data[k].c==v) //找到一个列号为v的元素{tb.data[k1].r=t.data[k].c; //将行列交换和添加到tb中tb.data[k1].c=t.data[k].r;tb.data[k1].d=t.data[k].d;k1++; //tb的元素个数增1}}}}
}
- 稀疏矩阵采用三元组顺序表存储后,当非零元素个数较少时会在一定程度上节省存储空间;如果用一个二维数组直接存储稀疏矩阵,此时具有随机存取特性,但采用三元组顺序表存储后会丧失随机存储特性
3.稀疏矩阵的十字链表表示
十字链表(orthogonal list)是稀疏矩阵的一种链式存储结构(相应的,前面的三元顺序表是稀疏矩阵的一种顺序存储结构)。有如下3*4的稀疏矩阵:
创建稀疏矩阵B的十字链表的步骤如下:
(1)对于稀疏矩阵中的每个非零元素创建一个结点存放它,包含元素的行号、列号和元素值。这里有四个非零元素,创建四个数据结点
(2)将同一行的所有结点构成一个带头结点的循环单链表,行号为i的单链表的头结点为hr[i]。这里有三行,对应有三个循环单链表,头结点分别为hr[0]~hr[2]。头结点的行指针指向行号为i的单链表的首结点
(3)将同一列的所有结点构成一个带头结点的循环单链表,列号为j的单链表的头结点为hd[j]。这里有4列,对应有4个循环单链表,头结点分别为hd[0]~hd[3]。头结点的列指针指向列号为j的单链表的首结点
由此创建了3+4=7个循环单链表,头结点的个数也为7个。实际上,可以将hr[i]和hd[j]合起来变为h[i],即h[i]同时包含有行指针和列指针。头结点的行指针指向行号为i的单链表的首结点,头结点的列指针指向列号为i的单链表的首结点,这样的头结点的个数为
(4)再将所有头结点连起来构成一个带头结点的循环单链表,这样需要增加一个总头结点hm,总头结点中存放稀疏矩阵的行数和列数等信息
采用上述过程创建的稀疏矩阵B的十字链表如图6.8所示。每个非零元素就好比在一个十字路口,由此称为十字链表。
在稀疏矩阵的十字链表中包含两种类型的结点,一种是存放非零元素的数据结点,其结构如图6.9(a)所示;另一种是头结点,其结构如图6.9(b)所示。
为了方便算法设计,将两种类型的结点统一起来,设计稀疏矩阵的十字链表的结点类型MatNode如下:
#define M<稀疏矩阵行数>
#define N<稀疏矩阵列数>
#define Max((M)>(N)?(M):(N)) //矩阵行列较大者
typedef struct mtxn
{int row; //行号或者行数int col; //列号或者列数struct mtxn *right,*down; //行、列指针union{ElemType value; //非零元素值struct mtxn *link; //指向下一个头结点}tag;
}MatNode; //十字链表的结点类型
从中可以看出,在十字链表中行、列头结点是共享的,而且采用头结点数组存储,通过头结点h[i]的h[i]->right指针可以逐行搜索行下标为i的所有非零元素,h[i]->down指针可以逐列搜索列下标为i的所有非零元素。每一个非零元素同时包含在两个链表中,方便算法中行方向和列方向的搜索,因而大大降低了算法的时间复杂度。
对于一个m*n的稀疏矩阵,总的头结点个数为。
//稀疏矩阵的十字链表表示
#include <stdio.h>
#include <malloc.h>
#define M 3 //矩阵行
#define N 4 //矩阵列
#define Max ((M)>(N)?(M):(N)) //矩阵行列较大者
typedef int ElemType;
typedef struct mtxn
{ int row; //行号int col; //列号struct mtxn *right,*down; //向右和向下的指针union {ElemType value;struct mtxn *link;} tag;
} MatNode; //十字链表类型void CreatMat(MatNode *&mh,ElemType a[][N]) //创建a的十字链表
{int i,j;MatNode *h[Max],*p,*q,*r;mh=(MatNode *)malloc(sizeof(MatNode));//创建十字链表的头结点mh->row=M;mh->col=N;r=mh; //r指向尾结点for (i=0;i<Max;i++) //采用尾插法创建头结点h1,h2,…循环链表{h[i]=(MatNode *)malloc(sizeof(MatNode));h[i]->down=h[i]->right=h[i]; //将down和right方向置为循环的r->tag.link=h[i]; //将h[i]加到链表中r=h[i];}r->tag.link=mh; //置为循环链表for (i=0;i<M;i++) //处理每一行{for (j=0;j<N;j++) //处理每一列{if (a[i][j]!=0) //处理非零元素{p=(MatNode *)malloc(sizeof(MatNode)); //创建一个新结点p->row=i;p->col=j;p->tag.value=a[i][j];q=h[i]; //查找在行表中的插入位置while (q->right!=h[i] && q->right->col<j) q=q->right;p->right=q->right;q->right=p; //完成行表的插入q=h[j]; //查找在列表中的插入位置while (q->down!=h[j] && q->down->row<i) q=q->down;p->down=q->down;q->down=p; //完成列表的插入}}}
}void DestroyMat(MatNode *&mh) //销毁十字链表
{MatNode *pre,*p,*mp;mp=mh->tag.link; //mp指向h[i]while (mp!=mh) //释放所有数据结点{pre=mp->right; //pre指向h[i]的行首结点if (pre!=mp) //h[i]不空{p=pre->right; //p指向结点pre的后继结点while (p!=mp){free(pre);pre=p; p=p->right;}}mp=mp->tag.link; //mp指向下一个头结点}//释放所有的头结点pre=mh->tag.link; //pre指向h[i]p=pre->tag.link; //p指向h[i+1]while (p!=mh){free(pre);pre=p; p=p->tag.link;}free(mh);
}void DispMat(MatNode *mh) //输出十字链表
{MatNode *p,*q;printf("行=%d 列=%d\n", mh->row,mh->col);p=mh->tag.link;while (p!=mh) { q=p->right;while (p!=q) //输出一行非零元素{printf("%d\t%d\t%d\n", q->row,q->col,q->tag.value);q=q->right;}p=p->tag.link;}
}//本主程序用于调试
int main()
{ElemType a[M][N]={{1,0,0,2},{0,0,3,0},{0,0,0,4}};MatNode *mx;CreatMat(mx,a);printf("a的十字链表:\n");DispMat(mx);DestroyMat(mx);return 1;
}
三、广义表
1.广义表的定义
广义表(generalized table)是线性表的推广,是有限个元素的序列,其逻辑结构采用括号表示法表示如下:
其中n表示广义表的长度,即广义表中所含元素的个数,。若n=0,称为空表。为广义表中的第i个元素,如果属于原子类型(原子类型的值是不可分解的,如C/C++语言中的整数、实型和字符型),称为广义表GL的原子;如果又是一个广义表,称为广义表GL的子表(subgeneralized table)。
广义表具有以下重要的特性:
- 广义表的数据元素是有相对次序的
- 广义表的长度定义为最外层包含元素的个数
- 广义表的深度定义为所含括弧的重数,其中原子的深度为0,空表的深度为1
- 广义表可以共享,一个广义表可以被其他广义表共享,这种共享广义表称为再入表
- 广义表可以是一个递归的表,一个广义表可以是自己的子表,这种广义表称为递归表。递归表的深度是无穷值,而长度是有限值
广义表的抽象数据类型的定义如下:
为了简单起见,下面讨论的广义表不包括前面定义的再入表和递归表,即只讨论一般的广义表。另外,规定用小写字母表示原子,用大写字母表示广义表的表名。例如:
A=():A是一个空表,其长度为0
B=(e):B是一个只含有单个原子e的表,其长度为1
C=(a,(b,c,d)):C中有两个元素,一个是原子,另一个是子表,C的长度为2
D=(A,B,C)=((),(e),(a,(b,c,d))):D中有三个元素,每个元素又都是一个子表,D的长度为3
E=((a,(a,b),((a,b),c))):只含有一个元素,该元素是一个子表,E的长度为1
如果把每个表的名称(若有)写在其表的前面(没有给出名称的子表为匿名表,用“·”表示),则上面的5个广义表可相应的表示如下:
A()
B(e)
C(a,·(b,c,d))
D(A(),B(e),C(a,(b,c,d)))
E=(·(a,·(a,b),·(·(a,b),c)))
若用圆圈和方框分别表示表和原子,并用线段把表和它的元素(元素结点应在其表结点的下方)连接起来,则可得到一个广义表的图形表示。
广义表GL的表头为第一个元素,其余部分为GL的表尾,分别记为和。显然,一个广义表的表尾始终是一个广义表。空表无表头、表尾。
A无表头,无表尾
其中,广义表A,B的深度为1(他们均只有一重括号),C,D,E的深度为2,3,4。
2.广义表的存储结构
广义表是一种递归的数据结构,因此很难为每个广义表分配固定的存储空间,所以其存储结构只能采用链式存储结构。
从图6.11可以看到,广义表有两类结点,一类为圆圈结点,在这里对应子表;另一类为方形结点,在这里对应原子。
为了使子表和原子两类结点既能在形式上保持一致,又能进行区别,可采用以下结构形式:
其中,tag域为标志字段,用于区分两类结点,即由tag决定是使用sublist还是data域:
- 若tag=0,表示该结点为原子结构,则第2个域为data,存放相应原子元素的信息
- 若tag=1,表示该结点为表/子表结点,则第2个域为sublist,存放相应表/子表中第一个元素对应节点的地址
link域存放同一层的下一个元素对应结点(兄弟结点)的地址,当没有兄弟结点时,其link域为NULL。
例如,前面的广义表C的链式存储结构如下:
采用C/C++语言描述广义表的结点类型GLNode,其声明如下:
typedef struct lnode
{int tag; //结点类型标识union{ElemType data; //存放原子值struct lnode *sublist; //指向子表的指针}val;struct lnode *link; //指向下一个元素
}GLNode; //广义表的结点类型
3.广义表的运算
为了使算法方便,在广义表的逻辑表示中用“(#)”表示空表。
(1)广义表的算法设计方法
在广义表的链式存储结构中,tag=1的结点可以看成是一个单链表的表头,由sublist域指向它的所有元素构成的单链表的首结点,link域指向它的兄弟结点。从中可以看到,广义表的链式存储结构具有递归性,可以从两个方面来理解这种递归性,从而得到广义表的两种递归算法方法。
- 【解法1】一个非空广义表的基本存储结构如图所示,将其看成是带头结点的单链表,如果它是原子结点,可以直接进行处理以实现原子操作;如果它是子表结点,由于子表的存储结构和整个广义表的存储结构是相似的,因此子表的处理和整个广义表的处理是相似的。从这个角度出发设计求解广义表递归算法的一般格式如下:
void fun1(GLNode *g) //g为广义表头结点指针
{GLNode *g1=g->val.sublist; //g1指向第一个元素while(g1!=NULL) //元素未处理完循环{if(g1->tag==1) //为子表时fun1(g1); //递归处理子表else //为原子时原子处理语句; //实现原子操作g1=g1->link; //处理兄弟,即处理后继元素}
}
- 【解法2】一个非空广义表存储结构中两类结点的基本结构如图6.14所示。每个原子结点的data域为原子值,link域指向其兄弟;每个表/子表结点的sublist指向它的元素,link指向兄弟。因此,对于原子结点,其兄弟的处理与整个广义表的处理是相似的;对于表/子表结点,其元素和兄弟的处理与整个广义表的处理是相似的。从这个角度出发设计求解广义表递归算法的一般格式如下
void fun2(GLNode *g)
{if(g!=NULL){if(g->tag==1) //为子表时fun2(g->val.sublist); //递归处理其元素else //为原子时原子处理语句; //实现原子操作fun2(g->link); //递归处理其兄弟(link部分)}
}
(2)求广义表的长度
- 在广义表中,同一层次的每个结点是通过link域链接起来的,将其看成是带头结点的单链表,如图6.13所示,这样求广义表的长度就是求单链表的长度。对应的非递归算法如下
int GLLength(GLNode *g) //求广义表的长度
{int n=0; //累计元素个数,初始值为0GLNode *g1;g1=g->val.sublist; //g1指向广义表的第一个元素while(g1!=NULL) //扫描所有元素结点{n++; //元素个数增1g1=g1->link;}return n; //返回元素个数
}
(3)求广义表的深度
- 对于广义表g,其深度等于所有元素的最大深度加1.若g为原子,其深度为0,求广义表深度的递归模型f(g)如下:
若g为原子
若g为空表
其他情况
int GLDepth(GLNode *g) //求广义表g的深度
{GLNode *g1;int maxd=0,dep;if(g->tag==0) //为原子时返回0return 0;g1=g->val.sublist; //g1指向第一个元素if(g1==NULL) //为空表时返回1return 1;while(g1!=NULL) //遍历表中每一个元素{if(g1->tag==1) //元素为子表的情况{dep=GLDepth(g1); //递归调用求出子表的深度if(dep>maxd) //maxd为同一层的子表中深度的最大值maxd=dep;}g1=g1->link; //使g1指向下一个元素}return(maxd+1); //返回表的深度
}
(4)输出广义表
- 输出广义表g的过程f(g)为:若g不为NULL,先输出g的元素,当有兄弟时再输出兄弟。输出g的元素的过程是,如果该元素为原子,直接输出原子值,若为子表,输出'(',如果为空表则输出'#',如果为非空子表则递归调用f(g->val.sublist)以输出子表,再输出')'。输出g的兄弟过程是输出',',递归调用f(g->link)以输出兄弟
void DispGL(GLNode *g) //输出广义表g
{if(g!=NULL) //表不为空判断{if(g->tag==0) //g的元素为原子时printf("%c",g->val.data); //输出原子值else //g的元素为子表时{printf("("); //输出(if(g->val.sublist==NULL) //为空表时printf("#");else //为非空子表时DispGL(g->val.sublist); //递归输出子表printf(")"); //输出)}if(g->link!=NULL){printf(",");DispGL(g->link); //递归输出g的兄弟}}
}
(5)建立广义表的链式存储结构
假设广义表的逻辑结构采用括号表示,其中的元素类型ElemType为char类型,每个原子的值被限定为单个英文字母。其格式为元素之间用一个逗号分隔,表元素的起止符号分别为左、右圆括号,空表为“(#)”。例如“(a,(b,c,d),(#))”就是一个符合上述规定的广义表格式。
建立广义表链式存储结构的算法是一个递归算法,它使用一个广义表括号表示字符串参数s,返回创建的广义表链式存储结构的头结点指针g。
算法的执行过程是从头到尾扫描s的每一个字符。当遇到'(',表明它是一个表/子表的开始,应创建一个由g指向的表/子表结点,并用它的sublist域作为子表的表头指针进行递归调用来创建子表的存储结构;当遇到一个英文字母时,表明它是一个原子,则应创建一个由h指向的原子结点;当遇到一个‘)’字符时,表明前面的表/子表已处理完毕,则将g值为空;当遇到一个‘#’字符时,表明前面的表/子表是空表。则将g->val.sublist置为空。
当建立了一个由h指向的结点后,接着遇到‘,’时,表明还存在兄弟,需要建立当前结点(即由g指向的结点)的兄弟结点;否则表明当前结点没有兄弟了,将当前结点的link域置为空。
GLNode *CreateGL(char *&s) //返回由括号表示s建立的广义表存储结构
{GLNode *g;char ch=*s++; //取一个字符if(ch!='\0') //若s未扫描完{g=(GLNode *)malloc(sizeof(GLNode)); //创建一个新结点if(ch=='(') //当前字符为左括号时{g->tag=1; //新结点作为表/表头结点g->val.sublist=CreateGL(s); //递归构造子表并链接到表头结点}else if (ch==')') //遇到)字符,g置为空g=NULL;else if(ch=='#') //遇到#字符,表示空表g=NULL;else //为原子字符{ g->tag=0; //新结点作为原子结点g->val.data=ch;}}else //若s扫描完,g置为空g=NULL;ch=*s++; //取下一个字符if(g!=NULL){if(ch==',') //当前字符为,g->link=CreateGL(s); //递归构造兄弟结点else //没有兄弟了,将兄弟指针置为NULLg->link=NULL;}return g; //返回广义表g
}
(6)销毁广义表
- 该运算扫描广义表链式存储结构g的所有结点并逐个释放。将广义表g看成是带头结点的单链表,扫描g的所有元素结点,如果为原子结点,直接释放;如果为子表结点,释放该子表的过程与整个广义表是相似的。
void DestroyGL(GLNode *&g) //销毁广义表
{GLNode *g1,*g2;g1=g->val.sublist; //g1指向广义表的第一个元素while(g1!=NULL) //遍历所有元素{if(g1->tag==0) //若为原子结点{g2=g1->link; //g2临时保存兄弟结点free(g1); //释放g1所指的原子结点g1=g2; //1指向后继兄弟结点}else //若为子表{g2=g1->link; //g2临时保存兄弟结点DestroyGL(g1); //递归释放g1所指子表的空间g1=g2; //g1指向后继兄弟结点}}free(g); //释放头结点空间
}
【数据结构】CH6 数组和广义表相关推荐
- 数据结构05数组和广义表
第五章 数组 和 广义表 数组和广义表可以看成是线性表在下述含义上的扩展:表中的数据元素本身也是一个数据结构. 5.1 数组的定义 n维数组中每个元素都受着n个关系的约束,每个元素都有一个直接后继元素 ...
- 数据结构之数组与广义表
目录 联系 数组 广义表 联系 数组和广义表可看作一种扩展的线性数据结构,其特殊性在于数据元素的构成上.从组成线性表的元素角度看,数组是由具有某种结构的数据元素构成,广义表则是由单个元素或子表构成的. ...
- 数据结构:数组和广义表
数组 数组这部分内容在写<线性表>的的时候介绍过,所以这里就略过一部分内容(略过的内容在这里),写一写前边没写过的. 由于数组中各元素具有统一的类型,并且数组元素的下标一般具有固定的上界和 ...
- C语言数据结构学习——数组和广义表
数组和广义表 数组 数组定义 特点 常见运算及声明方式 数组的顺序表示和实现 矩阵的压缩存储 概念 稀疏矩阵 对称矩阵 三角矩阵 广义表 数组 数组定义 数组(Array)是有序的元素序列.若将有限个 ...
- 六、考研数据结构笔记——数组与广义表
一.数组的定义 一维数组:就是线性表,前面有 二维数组:准备考研的应该都知道,就是一个矩阵. 数组一旦被定义其,维数(行)和维界(列)就不能改变.只会读取加修改元素. 二.二维数组的按行(列)优先 事 ...
- 【数据结构】数组和广义表
感觉数组这一段没讲什么太多的东西. 先是讲了下定义,就是每个维度上都有对应的前驱后继,首尾元素例外.操作只有初始化 销毁 取元素 修改元素.然后讲了下适合用顺序存储结构,多维情况下根据下标(j1 j2 ...
- Java数据结构和算法:字符串、数组和广义表
数组和广义表是与前述的线性表有所区别的数据结构.它们可以看成是线性表在下述含义上的扩展:线性表中的元素本身也是一个数据结构 字符串 字符串的定义.存储结构 字符串(string)是由n (n≥0) 个 ...
- 【数据结构总结】第四章:串、数组和广义表(线性结构)
第四章:串.数组和广义表(线性结构) 提示:本文主要是以思维导图的形式概括数据结构第一章的精华内容,基本不会用到文字性的内容,目的是为了给大家梳理每个重要的知识点的相关概念,方便大家在复盘的时候快速阅 ...
- 数据结构数组计算机中的应用,2018考研计算机:数据结构数组和广义表复习重点...
2018考研计算机:数据结构数组和广义表复习重点 2017-08-17 16:00 | 考研集训营 <数据结构(C语言版)>复习重点在二.三.六.七.九.十章,考试内容两大类:概念,算法, ...
最新文章
- oracle 取系统当前年份_oracle查询以当前年份为准的近些年数据
- linux svn命令
- 必须掌握的八个【cmd 命令行】[
- CodeForces-1294B排序+pair使用
- 每天一道LeetCode-----摩尔投票法寻找给定数组中出现个数大于n/2或n/3的元素
- burpsuite下载使用详讲
- 让僵冷的翅膀飞起来—从实例谈OOP、工厂模式和重构[by Wayfarer]
- Error: Cannot find module 'json-schema-faker' YAPI部署
- debian9为什么默认是pip2_VirtualBox内刚刚安装完Debian9系统,也无法设置共享文件夹。解决的方法就是安装VirtualBox客户端增强包。...
- 用最简单的操作,做最精准的AI模型!
- CPU里都有几十亿个晶体管,万一坏掉几个还能用吗?
- Undo log日志详解
- redis key 操作大全
- k2p B1版本官改关闭路由器ipv6分配
- 大数据的处理是怎样的过程
- 集群分布式存储-MFS
- 【PR #2】史莱姆(值域分段)
- Turbo编码相关知识
- 《Windows 8 权威指南》——2.4 Aero与Metro的触摸对比
- Linux连接MySQL出现1045错误 解决方法