前言 在线性表中,结点存储的是单个元素,而在广义表中,结点可以存储单个元素,也可以存储一个广义表。当存储的是单个元素时,称其为原子;存储的是整个广义表时,称其为子表。
可见广义表是一个递归的概念,因此说广义表是线性表的推广。
当广义表非空时,称其第一个元素为表头,其余均为表尾。
广义表的存储结构 通过广义表的概念可以发现,广义表是很难用顺序存储结构来表示的,通常采用链式存储结构,每个数据元素可以用一个结点来表示。
那么如何设计这个结点的结构呢?我们知道广义表中的结点有两种类型,一种是存储数据的原子结点,另一种则是存储表的表结点。因为链式存储结构的特性,要储存一整个表,实际上只需要记录下该表的表头地址即可,其余部分用类似于next指针就能找到。
可以想到一个结点需要有以下部分:
指针域,用于保存指向下一个结点的next指针 数据域,用于存储原子结点的值或表结点的表头地址。这是两种不同类型的值,但是可以用共用体来存储 由于使用共用体来存储数据,因此需要设置一个是原子节点或表结点的标记(用于区分存储区内是什么类型的数值
这样设计出来的广义表结构示例如下:
当然,结点的结构不止一种设计方式,分别设计两个不同的结点也是可行的。
在实际代码的实现中,我们多设计了一个头结点(参考链表中的头结点),只是多使用了一个标记值标识头结点,区别不大。
广义表的初始化 同链表的初始化一样,实质上就是创建头结点的过程。
//初始化广义表 void InitGenList(GenList &gl){
//创建头结点
gl = (GLNode *)malloc(sizeof(GLNode));
assert(gl != NULL);
gl->tag = HEAD;
gl->hp = gl->tp = NULL;
} 1 2 3 4 5 6 7 8 9 广义表的创建 这里的创建指将一个字符串表示的广义表用我们刚才设计的结构进行存储。
由于广义表可以”表中有表“,表内的一个元素可能是原子,也可能是子表,字符串的可能性有很多,如图所示。
基本思路是:我们读到一个元素,判断它的类型(原子或子表),然后分别处理(直接存储或递归调用创建方法)。注意括号和逗号是不存储的,只存储字符串中的数据。
现在的问题是,如何正确读完一个元素?
我们知道,在广义表中,称其第一个元素为表头,其余均为表尾。那么上面的问题就可以转换为表头和表尾的分离问题:将字符串分成表头和表尾,处理表头;然后将剩下的部分(表尾)继续分离成表头和表尾,处理表头,以此类推。
因为是字符串,所以还要对括号进行处理,实际上一进入创建方法中,就要把两头的括号给去除。
//删除最外层括号
strncpy(sub, str+1, n-2);
//标志字符串结束
sub[n-2] = '\0';
1 2 3 4 下面说明将表头和表尾分离的方法。
表头表尾分离法 刚才说到,一进入创建方法中,就需要把字符串两头的括号去除,因此在分离方法中,我们遇到的字符串是这样的:
观察可以发现,表头实际上只有三种情况:
表头为原子,则字符串开头为数字字符 表头为子表,则字符串开头为 “ ( "符号 表头为空 当表头为原子时,我们遇上 ” , “ 就可以直接分割,而当表头为子表时,需要寻找到 ” ) “ 符号后才可以分割。
因此,我们需要对首个字符进行判断,并且用标志进行记录:设用k来记录首个字符的状态,若首个字符非括号,则k = 0,此后遇到” , “就停止;若首个字符是“ ( "符号,则k = 1,此时继续遍历,遇到 ” , “ 也不停止;直到遇到 ” ) “ 符号,设置k = 0,此后遇到” , “就停止。
int n = strlen(sub);
//用于遍历整个字符串
int i = 0;
char ch = sub[0];
//用于标记括号
int k = 0;
while(i<n && (ch!=',' || k!= 0)){
if(ch == '('){
k ++;
}else if(ch == ')'){
k --;
}
i++;
ch = sub[i];
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 当这个循环结束时,也有不同的情况需要处理
if(k != 0){
//内部括号不匹配
printf("字符串有误!\n");
return false;
}else if(i < n){
//表头表尾正常分离
sub[i] = '\0';
strcpy(hsub,sub);
strcpy(sub,sub+i+1);
}else{
//整个表都是表头
//"(1,2)" hsub = "(1,2)" sub=""
strcpy(hsub,sub);
sub
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
———————————————