【数据结构-广义表】广义表代码实现及“修正”——《数据结构C语言》(严蔚敏)

目录

前言

广义表理解 

表头和表尾的理解:

表的本质:

表的实现:

首先是结构体

预备函数: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;

第二种是更加抽象一点,也更加实用

  1. 因为不需要用结构体把两个指针包起来,引用的时候,NODE结构体里东西都能一步引用
  2. 逻辑正确。其实你画出图就会发现,对于同一级的元素,们之间的关系必然是用tp链接的,也就是说,其实tp可以代表同一级链接,如果tp==NULL,那么就是到了这一级的尽头。这个理解的优势在于遍历一层的时候非常直观,就是用tp去遍历 
  3. 而且还可以使用第一种逻辑,两不误(其实第一种逻辑是用的比较少,所以我们常用第二种结构)
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 = '\0';
}

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. 你不要太认真,递归这种东西诞生就是为直观理解而生,你去从底层理解,就输了,至少不能一上来就这么干

1,2件老生常谈,3,4却没人说,可惜可叹。

然后就可以开始学习建立了

总体思路(按照第二种结构):

  1. 给出基本项:S为空字符串的时候(屁话,空字符串就是(),S如果不是单个原子,那就一定是个表!),S就为NULL。

    另一个就是S为单原子,那就把节点变成原子节点(此时已经建立节点了,其实也可以多些一点,让代码可读性更高)
  2. 给出归纳项:如果不是“()”或者“a”这种单原子,那么字符串长必定>=3(我猜的,具体实现就一个else即可)这种情况,必然有外括号。归纳项在下面具体写    出
  3. 建立节点,用L指向当前空间(q保存上一层表);
    赋值tag=LIST ;
    脱掉外括号;
    do while循环不断取出表头,对表每个头建立子表;
    最后取光,sub变成空串,就结束;
    对该层最后一个节点进行封口q->tp=NULL;
  4. 循环过程解析:
    取出表头放到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:%c\n", L->atom);
		return;
	}
	//L为表
	char sub[100], hsub[100]; //sub是去括号后串,hsub放表头
	SubString(sub, str, 1, strlen(str) - 2);//去括号
	//printf("sub: %s\n", 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 %s\n", hsub);
		pre_p = p;
		CreatList(p->hp, hsub);//填充hp
		if (sub[0] != '\0')//填充tp,p移动到下一级
		{
			p = (List)malloc(sizeof(Node));//申请空间
			if (!p) exit(-1);
			p->tag = LIST;//填充tag
			pre_p->tp = p;
		}
	} while (sub[0] != '\0');//空sub退出
	pre_p->tp = NULL;//封口
	//printf("close\n");
}

广义表输出

之前这道题还给了个模板,乐学的,写的稀烂,后来看不下去了,用这个代码的1/3的代码量,我自己写了20行就搞定了,我真不知道怎么就写到60行的,怎么会有这种人。。。

我们同样是使用递归思路去解决

  1. 首先是定义个p=L去准备遍历
  2. 如果是原子,就直接输出
  3. 如果是列表,我们就输出(内容),至于内容就用p去遍历
  4. 需要解决的问题是逗号的输出问题和括号输出,括号已经从列表结构搞定了,逗号的话,逗号的作用是用来分割同层元素的,注意是同层,那么就可以把逗号添加到同层遍历的循环里,就是定义一个是否为为第一个元素的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(')');
	}	
}

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亦梦亦醒乐逍遥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值