数据结构与算法(五)—— 广义表

        广义表是线性表的推广,又称列表。线性表的元素仅限于原子项,即每个数据元素只能是一个数或一个记录,如果放松对线性表元素的这种限制,允许它们自身具有结构,由此就产生了广义表的概念。

一、广义表的定义

        广义表是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;

四、广义表基本运算的实现

1、建立广义表的存储结构

        基本思想:在广义表表达式中,遇到"("时递归构造子表,否则构造原子结点;遇到逗号时递归构造后续广义表,直到表达式字符串输入结束。

代码如下:

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;
}

2、输出广义表

        基本思想:若遇到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); //递归调用输出下一个节点
		}
	}
}

3、广义表的查找

        基本思想是:当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
}

 

你可能感兴趣的:(数据结构与算法)