目录
前言
广义表理解
表头和表尾的理解:
表的本质:
表的实现:
首先是结构体
预备函数:SubString和sever函数
广义表建立
广义表输出
前言
说实话,稀疏矩阵那块也不难,画个图就搞定了,代码也没什么问题(主要还是书里都给了,也可以比较容易地实现)
但是到了广义表这块,是真的让人头大,说的太含糊不清了,没有一个清晰的定义,这里做一个个人思路的记录,对这本书做一个更详细的解释。(要不是找不到好文章,我也懒得自己去写)
1 广义表理解广义表的基本概念,因为有子表的存在,所以是递归的概念,其 *** 作也必然有大量递归,我这里就不赘述了,自己看书即可,我做一个重点突出和补充。
广义表本身没啥大用,但是对于递归的理解加深非常有用,所以很多看起来没用的东西,仔细想想,还是有用的。
不过话说回来,我TM直接去找一些递归专项的题去做不好吗?
表头和表尾的理解:拿A=(a,b,c,d)和B=(a,(b,c,d))还有C=((a,b,c,d))举例
表头:A:a B:a C:(a,b,c,d)
也就是说,表头就是“去掉表最外层括号后,括号对匹配后第一个逗号前面的的所有部分”
听起来有点拗口,其实就是,先把外面括号用Substring去掉,然后从前往后匹配括号,左括号就计数器counter++,右括号就计数器counter--,然后当counter==0而且碰到‘,’的时候,那么逗号前面就是表头,不信你试试,就是这样。
哦对了,如果没有碰到合适的逗号呢(不排除碰到逗号却括号不匹配,匹配了已经没逗号了),那这种情况就是,表尾是空表,表头是全部,就用个strncpy即可
表尾:A:(b,c,d) B:((b,c,d)) C:()
是不是看起来有点不一样,我们说表头那个逗号前面,后面就是表尾,但是书中隐含了一个细节,就是:表尾必定是表,那如果让A的b,c,d直接拿出来,这不叫表,表是有外括号的,所以,用GetTail()每次构造表尾,都要在串头尾加括号
如果使用hp和tp往出拿表头和表尾(而这个 *** 作非常简单,就一个赋值即可),那么其实不需要加括号,因为表指针本身已经具有表 的含义了,输出的时候自然会按照表来输出,我们的输出函数不是吃素的。
有了这些基础,在自己画图理解表结构的时候,就轻松多了。
表的本质:用指针关系表示括号结构层次,真正的原子节点就那么几个,剩下一大堆都是没有实际意义的,只是用来表示层次的。
表的实现: 首先是结构体第一种是传统意义上的,原子节点就是一个Tag和一个原子,表就是一个Tag和两个指针域,为了保持和atom的同级,所以把两个指针用struct包起来。
其中有一个union,这个类型不用名字,可以直接调用里面的成员,说白了就是为了节省空间,把两个变量放到一个框里了。
typedef enum { ATOM, LIST }Tag; typedef struct NODE { Tag tag; union { char atom; struct { struct NODE* hp; struct NODE* tp; }ptr; }; }*List, Node;
第二种是更加抽象一点,也更加实用
- 因为不需要用结构体把两个指针包起来,引用的时候,NODE结构体里东西都能一步引用
- 逻辑正确。其实你画出图就会发现,对于同一级的元素,们之间的关系必然是用tp链接的,也就是说,其实tp可以代表同一级链接,如果tp==NULL,那么就是到了这一级的尽头。这个理解的优势在于遍历一层的时候非常直观,就是用tp去遍历
- 而且还可以使用第一种逻辑,两不误(其实第一种逻辑是用的比较少,所以我们常用第二种结构)
typedef enum { ATOM, LIST }Tag; typedef struct NODE { Tag tag; union { char atom; struct NODE* hp; }; struct NODE* tp; }*List, Node;预备函数:SubString和sever函数
广义表建立void SubString(char sub[], char str[], int pos, int len) { char* p = str, * q = sub; //p指向原串,q指向sub p += pos; //到起点 while (len--)//循环len次 *q++ = *p++; *q = ''; } void sever(char str[], char hstr[]) { int len = strlen(str); //sever处理的是已经去括号的,所以从头 int i = 0, k = 0; char ch; for (i = 0; i < len; i++) //遍历str,找到合理逗号 { ch = str[i]; if (ch == '(') k++; else if (ch == ')') k--; if (k == 0 && ch == ',')//匹配到 break; } if (i == len)//正常走出循环,没有匹配到 { strcpy(hstr, str);//全部给hstr memset(str, 0, sizeof(str)); } else //匹配到了合适的逗号,此时i指向这个逗号 { SubString(hstr, str, 0, i - 0); SubString(str, str, i + 1, len - (i + 1)); } }
首先是一个递归思路建立。
说来搞笑,本来建立广义表应该是第一步干的,然而那个递归思路却需要其他的 *** 作函数来加深理解,所以一上来干这个有点太早,可以选择先加深递归 *** 作的理解(hanoi塔之类的)
我对递归的理解:刚开始学,就是汉诺塔,当时学不明白,昨天自己竟然一下就敲出来了,递归这玩意吧,就是和数学归纳是一样的,你只需要明白四件事
- 给出递归的基本项(终止条件)
- 给出递归的归纳项
- 所有递归过程一定可以到达终止
- 你不要太认真,递归这种东西诞生就是为直观理解而生,你去从底层理解,就输了,至少不能一上来就这么干
1,2件老生常谈,3,4却没人说,可惜可叹。
然后就可以开始学习建立了
总体思路(按照第二种结构):
- 给出基本项:S为空字符串的时候(屁话,空字符串就是(),S如果不是单个原子,那就一定是个表!),S就为NULL。
另一个就是S为单原子,那就把节点变成原子节点(此时已经建立节点了,其实也可以多些一点,让代码可读性更高) - 给出归纳项:如果不是“()”或者“a”这种单原子,那么字符串长必定>=3(我猜的,具体实现就一个else即可)这种情况,必然有外括号。归纳项在下面具体写 出
- 建立节点,用L指向当前空间(q保存上一层表);
赋值tag=LIST ;
脱掉外括号;
do while循环不断取出表头,对表每个头建立子表;
最后取光,sub变成空串,就结束;
对该层最后一个节点进行封口q->tp=NULL; - 循环过程解析:
取出表头放到hsub,sub是剩下的部分;
(注意我们不用表头表尾的逻辑,所以剩下这些不用加括号,他们不是表尾
我们的目的是把同层的东西都找出来)
对把表头部分作为串,对当前表(p)的表头指针(p->hp)建立一个子表(递归);
用q保存p;
//接下来对剩下的部分的处理。
如果剩余部分非空,就建立下一个同级节点,用p指向新节点;
用LIST标注新节点tag;
用q->tp=p来链接同级上下节点;
退出循环后,因为没有建立新的同级节点,所以p==q,都指向最后一个同级节点,那么就用q->tp=NULL来封口;
void CreatList(List& L, char str[]) { //采用更清晰的结构书写 if (strcmp(str, "()") == 0) //空表 { L = NULL; return; } if (strlen(str) == 1) //单原子情况 { L = (List)malloc(sizeof(Node));//申请空间 if (!L) exit(-1); L->tag = ATOM;//填充Tag L->atom = str[0];//填充atom //printf("atom:%cn", L->atom); return; } //L为表 char sub[100], hsub[100]; //sub是去括号后串,hsub放表头 SubString(sub, str, 1, strlen(str) - 2);//去括号 //printf("sub: %sn", sub); L = (List)malloc(sizeof(Node));//申请空间 if (!L) exit(-1); Node* p = L, * pre_p = L; //pre_p保存p前节点 L->tag = LIST; //填充tag do { //逐个提取hsub,建立hp,而tp指向同层下一个 sever(sub, hsub); //printf("hsub %sn", hsub); pre_p = p; CreatList(p->hp, hsub);//填充hp if (sub[0] != '')//填充tp,p移动到下一级 { p = (List)malloc(sizeof(Node));//申请空间 if (!p) exit(-1); p->tag = LIST;//填充tag pre_p->tp = p; } } while (sub[0] != '');//空sub退出 pre_p->tp = NULL;//封口 //printf("closen"); }广义表输出
之前这道题还给了个模板,乐学的,写的稀烂,后来看不下去了,用这个代码的1/3的代码量,我自己写了20行就搞定了,我真不知道怎么就写到60行的,怎么会有这种人。。。
我们同样是使用递归思路去解决
- 首先是定义个p=L去准备遍历
- 如果是原子,就直接输出
- 如果是列表,我们就输出(内容),至于内容就用p去遍历
- 需要解决的问题是逗号的输出问题和括号输出,括号已经从列表结构搞定了,逗号的话,逗号的作用是用来分割同层元素的,注意是同层,那么就可以把逗号添加到同层遍历的循环里,就是定义一个是否为为第一个元素的bool isfirst
void JiuZhe(List L) { Node* p = L; if (p && p->tag == ATOM)//如果是原子,就直接输出 putchar(p->atom); else //是列表,就输出一层 { bool isfirst = true; putchar('('); while (p) //遍历一层 { if (!isfirst) putchar(','); JiuZhe(p->hp); isfirst = false; p = p->tp; } putchar(')'); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)