广义表是线性表的推广,又称列表。线性表的元素仅限于原子项,即每个数据元素只能是一个数或一个记录,如果放松对线性表元素的这种限制,允许它们自身具有结构,由此就产生了广义表的概念。
广义表是n(n>=0)个元素的有限序列,其中每个元素是原子项或者是一个广义表,通常记作GL=(a1,a2,a3,...,an)。GL是广义表的名字,n是它的长度。为了区分原子项和广义表,在书写上习惯用大写字母表示广义表,用小写字母来表示原子。通常用圆括号将广义表括起来,用逗号分隔其中的元素。当广义表非空时,称第一个元素a1是GL的表头(head),其他元素组成的表称为GL的表尾(tail)。
下面列举一些广义表的例子:
(1) A=() —— A是一个空表,其长度为零;
(2) B=(a) —— B是一个只有一个原子的广义表,其长度为1;
(3) C=(a,(b,c)) —— C是一个长度为2的广义表,第一个元素是原子,第二个元素是子表;
(4) D=(A,B,C) = ((),(a),(a,(b,c))) —— D是一个长度为3的广义表,其中三个元素均为子表;
(5) E=(C,d) = ((a,(b,c)),d) —— E是一个长度为2的广义表,第一个元素是子表,第二个元素是原子;
(6) F=(e,F) = (e,(e,(e,...))) —— F是一个递归的表,它的长度为2,第一个元素是原子,第二个元素是表自身,展开后它是一个无限的广义表。
一个表展开后所含括号的层数称为广义表的深度。例如,表A、B、C、D、E的深度分别为1、1、2、3、3,而表F的深度为无穷大。
对上面举例的广义表进行运算:
(1) 空表A没有表头也没有表尾,length(A) = 0,depth(A) = 1
(2) head(B) = a,tail(B) = (),length(B) = 1,depth(B) = 1
(3) head(C) = a,tail(C) = ((b,c)),length(C) = 2,depth(C) = 2
(4) head(D) = A,tail(D) = (B,C),length(D) = 3,depth(D) = 3
(5) head(E) = C,tail(E) = (d),length(E) = 2,depth(E) = 3
(6) head(F) = e,tail(F) = (F),length(F) = 2,depth(F) = ∞
由于广义表中的元素本身又可以具有结构,是一种带有层次的非线性结构,因此难以用顺序存储结构表示,通常采用链式存储结构,每个元素可用一个结点表示。每个结点由三个域构成,其中tag是一个标志位,用来区分当前结点是原子还是子表,当tag为1时,该结点是子表,第二个域为slink,用以存放子表的地址;当tag为0时,该结点是原子结点,第二个域为data,用以存放元素值。next域是用来存放与本元素同一层的下一个元素结点的地址,当该元素为最后一个元素时,next的值为NULL。
广义表结点的代码结构描述如下:
//定义广义表的数据结构
typedef struct GList{
NodeTag tag; //用以区分是原子结点还是子表结点
union{
DataType data; //用以存放原子结点值,其类型由用户自定义
GList *slink; //指向子表的指针
};
GList *next; //指向下一个表结点
} *GListPtr;
基本思想:在广义表表达式中,遇到"("时递归构造子表,否则构造原子结点;遇到逗号时递归构造后续广义表,直到表达式字符串输入结束。
代码如下:
GListPtr GreateGList(GListPtr gl){
char c;
scanf("%c", &c);
if(c != ' '){
gl = (GListPtr)malloc(sizeof(GListPtr));
if(c == '('){
gl->tag = list;
gl->slink = GreateGList(gl->slink); //递归构造子表结点
}else{
gl->tag = atom; //构造原子结点
gl->data = c;
}
}else{
gl = NULL;
}
scanf("%c", &c);
if(gl != NULL){
if(c == ','){
gl->next = GreateGList(gl->next); //构造后续广义表
}else{
gl->next = NULL; //遇到其他符号,如")"或";"时,无后续表
}
}
return gl;
}
基本思想:若遇到tag=1的结点,则是一个子表的开始,先打印输出一个"("号,如果该子表为空,则输出一个空格,否则递归调用输出该子表,子表打印输出完后,再打印一个")"号;若遇到tag=0的结点,则直接输出其数据域的值。若还有数据元素,则递归调用打印后续每个元素,直到遇到next域为NULL。
代码如下:
void PrintGList(GListPtr gl){
if(gl != NULL){
if(gl->tag == list){
printf("(");
if(gl->slink == NULL){
printf("");
}else{
PrintGList(gl->slink); //递归调用输出子表
}
}else{
printf("%c", gl->data); //输出结点数据域值
}
if(gl->tag == list){
printf(")");
}
if(gl->next != NULL){
printf(",");
PrintGList(gl->next); //递归调用输出下一个节点
}
}
}
基本思想是:当tag=0时为原子结点,如果是要找的结点,则查找成功;否则,若还有后续元素,则递归调用本过程查找后续元素,直到next域为NULL。若遇到tag=1的结点,则递归调用本过程在该子表中查找,若还有后续元素,则递归调用本过程查找后续每个元素,直到next域为NULL。
代码如下:
int FindGList(GListPtr gl,DataType t){
int mark = 0;
if(gl != NULL){
if(gl->tag == 0 && gl->data == t){
mark = 1;
}else {
if(gl->tag == 1){
mark = FindGList(gl->slink,t);
}else{
mark = FindGList(gl->next,t);
}
}
}
return mark; //若查找成功返回1
}