第4章 数组和广义表
本章主要介绍下列内容:
1.数组的定义、基本运算和存储结构
2.特殊矩阵的压缩存储
3.广义表的定义、术语、存储结构、运算
4.递归算法设计
课时分配:
1两个学时,2两个学时,3两个学时, 4两个学时
重点、难点:
特殊矩阵的压缩存储、广义表的存储结构、递归算法设计
第一节 数组
1.数组的定义和基本运算
数组的特点是每个数据元素可以又是一个线性表结构。因此,数组结构可以简单地定义为:若线性表中的数据元素为非结构的简单元素,则称为一维数组,即为向量;若一维数组中的数据元素又是一维数组结构,则称为二维数组;依次类推,若二维数组中的元素又是一个一维数组结构,则称作三维数组。
结论:线性表结构是数组结构的一个特例,而数组结构又是线性表结构的扩展。举例:
其中,A是数组结构的名称,整个数组元素可以看成是由m个行向量和n个列向量组成,其元素总数为m×n。
在C语言中,二维数组中的数据元素可以表示成a[表达式1][表达式2],表达式1和表达式2被称为下标表达式,比如,a[i][j]。
数组结构在创建时就确定了组成该结构的行向量数目和列向量数目,因此,在数组结构中不存在插入、删除元素的操作。
二维数组结构的基本操作:
(1)给定一组下标,修改该位置元素的内容 Assign(A,elem,index1,index2)
(2)给定一组下标,返回该位置的元素内容 Value(A,elem,index1,index2)
2.数组的存储结构
从理论上讲,数组结构也可以使用两种存储结构,即顺序存储结构和链式存储结构。然而,由于数组结构没有插入、删除元素的操作,所以使用顺序存储结构更为适宜。换句话说,一般的数组结构不使用链式存储结构。
组成数组结构的元素可以是多维的,但存储数据元素的内存单元地址是一维的,因此,在存储数组结构之前,需要解决将多维关系映射到一维关系的问题。举例: LOC(i,j)=LOC(0,0)+(n*i+j)*L
数组结构的定义:
#define MAX_ROW_INDEX 10
#define MAX_COL_INDEX 10
typedef struct{Elemtype elem[MAX_ROW_INDEX][MAX_COL_INDEX] } ARRAY;
基本操作算法举例:
(1)给数组元素赋值
void Assign(ARRAY *A,Elemtype elem,int index1,int index2)
{ if (index1<0||index1>=MAX_ROW_INDEX||index2<0||index2>=MAX_COL_INDEX) exit(ERROR);
else A->elem[index1][index2]=elem; }
(2)返回给定位置的元素内容
int Value(ARRAY A,Elemtype *elem,int index1,int index2)
{ if (index1<0||index1>=MAX_ROW_INDEX|| index2<0||index2>=MAX_COL_INDEX) return FALSE;
else { *elem= A.elem[index1][index2]; return OK;
3.矩阵的压缩存储
矩阵是在很多科学与工程计算中遇到的数学模型。在数学上,矩阵是这样定义的:它是一个由s×n个元素排成的s行(横向)n列(纵向)的表。下面就是一个矩阵
3.1特殊矩阵
对于这些特殊矩阵,应该充分利用元素值的分布规律,将其进行压缩存储。选择压缩存储的方法应遵循两条原则:一是尽可能地压缩数据量,二是压缩后仍然可以比较容易地进行各项基本操作。
三种特殊矩阵的压缩方法:
(1)对称矩阵
对称矩阵的特点是aij=aji。一个n×n的方阵,共有n2个元素,而实际上在对称矩阵中有n(n-1)/2个元素可以通过其他元素获得。压缩的方法是首先将二维关系映射成一维关系,并只存储其中必要的n(n+1)/2个(主对角线和下三角)元素内容,这些元素的存储顺序以行为主序。举例:
假设定义一个数组型变量:int A[10];
k是对称矩阵位于(i,j)位置的元素在一维数组中的存放位置。
操作算法的实现:
int Value(int A[],Elemtype *elem,int i,int j) {
if(i<1||i>MAX_ROW_INDEX|| j<1||j>MAX_COL_INDEX) return FALSE;
else { if (i>=j) k=i*(i-1)/2+j-1;
else k=j*(j-1)/2+i-1;
*elem=A[k];
return TRUE; }
}
(2)下(上)三角矩阵
下三角矩阵的压缩存储与上面讲述的对称矩阵的压缩存储一样,只是将上三角部分的常量值存储在0单元,下三角和主对角上的元素从1号单元开始存放。
举例:
操作算法的实现:
int Value(int A[],Elemtype *elem,int i,int j)
{
if(i<1||i>MAX_ROW_INDEX||j<1||j>MAX_COL_INDEX) return FALSE;
else { if (i>=j) k=i*(i-1)/2+j;
else k=0;
*elem=A[k];
return TRUE;
}
}
(3)对角矩阵
我们以三阶对角矩阵为例讨论一下它的压缩存储方法。对于对角矩阵,压缩存储的主要思路是只存储非零元素。这些非零元素按以行为主序的顺序,从下标为1 的位置开始依次存放在一维数组中,而0位置存放数值0。
操作算法的实现:
int Value(int A[ ],Elemtype *elem,int i,int j)
{
if(i<1||i>MAX_ROW_INDEX||j<1||j>MAX_COL_INDEX) return FALSE;
else { if (j>=(i-1)&&j<=(i+1)) k=3*(i-1)+j-i+1;
else k=0;
*elem=A[k];
return TRUE;
}
}
3.2 稀疏矩阵的压缩存储
类型定义:
#define MAX_SIZE 100 最大的非零元素个数
typedef struct{
int i,j; //行序号、列序号
Elemtype value; //非零元素值
}Three_Elem;
typedef struct {
Three_Elem Elem[MAXSIZE]; //存储非零元素的一维数组
int rows,cols,tu; //稀疏矩阵的总行数、列数及非零元素个数
}Matrix;
操作算法的实现:
(1)返回元素内容
int Value(Matrix M,Elemtype *elem,int i,int j)
if (i<1||i>rows||j<1||j>cols) exit(ERROR);
else { for (p=0;p<M.tu;p++)
if(M.elem[p].i==i&&M.elem[p].j==j)
{ *elem=M.elem[p].value; return OK; }
else if (M.elem[p].i>i||M.elem[p].i==i&&M.Elem[p].j>j) break;
*elem=0;
return OK;
}
}
(2)输出三元组表示的稀疏矩阵
void Print(Matrix M)
{
for (p=0,i=1;i<=M.rows;i++) {
for (j=1;j<=M.cols;j++)
if(p<M.tu&&M.elem[p].i==i&&M.elem[p].j==j) printf("%4d",M.elem[p++].value;);
else printf("%4d",0);
printf("\n");
}
}
第二节 广义表
1. 广义表的定义
广义表(Lists,又称列表)是线性表的推广。在第2章中,我们把线性表定义为n>=0个元素a1,a2,a3,…,an的有限序列。线性表的元素仅限于原子项,原子是作为结构上不可分割的成分,它可以是一个数或一个结构,若放松对表元素的这种限制,容许它们具有其自身结构,这样就产生了广义表的概念。
广义表是n(n>=0)个元素a1,a2,a3,…,an的有限序列,其中ai或者是原子项,或者是一个广义表。通常记作LS=(a1,a2,a3,…,an)。LS是广义表的名字,n为它的长度。若ai是广义表,则称它为LS的子表。
通常用圆括号将广义表括起来,用逗号分隔其中的元素。为了区别原子和广义表,书写时用大写字母表示广义表,用小写字母表示原子。若广义表LS(n>=1)非空,则a1是LS的表头,其余元素组成的表(a1,a2,…an)称为LS的表尾。
显然广义表是递归定义的,这是因为在定义广义表时又用到了广义表的概念。广义表的例子如下:
(1)A=()--A是一个空表,其长度为零。
(2)B=(e)--表B只有一个原子e,B的长度为1。
(3)C=(a,(b,c,d))--表C的长度为2,两个元素分别为原子a和子表(b,c,d)。
(4)D=(A,B,C)--表D的长度为3,三个元素都是广义表。显然,将子表的值代入后,则有D=(( ),(e),(a,(b,c,d)))。
(5)E=(E)--这是一个递归的表,它的长度为2,
E相当于一个无限的广义表E=(a,(a,(a,(a,…)))).
从上述定义和例子可推出广义表的三个重要结论:
(1)广义表的元素可以是子表,而子表的元素还可以是子表。由此,广义表是一个多层次的结构,可以用图形象地表示。
(2)广义表可为其它表所共享。例如在上述例(4)中,广义表A,B,C为D的子表,则在D中可以不必列出子表的值,而是通过子表的名称来引用。
(3)广义表的递归性。
综上所述,广义表不仅是线性表的推广,也是树的推广。
2. 广义表的存储结构
由于广义表(a1,a2,a3,…an)中的数据元素可以具有不同的结构,(或是原子,或是广义表),因此,难以用顺序存储结构表示,通常采用链式存储结构,每
个数据元素可用一个结点表示。
由于广义表中有两种数据元素,原子或广义表,因此,需要两种结构的结点:一种是表结点,一种是原子结点。
下面介绍两种广义表的链式存储结构。
表结点由三个域组成:标志域、指示表头的指针域和指示表尾的指针域;而原子域只需两个域:标志域和值域。
其类型定义如下:
typedef enum{ATOM,LIST}elemtag;
typedef struct glnode{
elemtag tag;
union{
atomtype atom;
struct {
struct glnode *hp,*tp;
}ptr;
};
} *glist;
例见书。
3. 分析求广义表深度和长度的递归算法(见教材)
这部分内容比较难,用1个课时讲解,用1个课时答疑