西工大NOJ数据结构理论——014.求广义表深度(严5.30)

news/2024/11/26 5:22:06/

第一下拿到这道题,我的脑袋轰一下就大了。如果说用”括号匹配“来做的话,这其实很简单。但是如果要用广义表来做的话,难度哗哗哗的就涨上来了。为什么呢?首先,要把读入的字符串存到广义表里面去,这里就需要第一次用到递归;然后,对已经构造好了的广义表,还需要进行求深度的递归,这就用到了第二次递归。上面这两次递归就已经足够让人头疼的了,更不用说处理输入中的逗号了。

当然,在这片文章的最后会有完整的代码实现,但是我还是更想去和大家分享一下,在这道题中,应该怎么去构建递归函数,当然这是我今天下午摸索出的一套构建的方法,也有了一些自己的思考,总结了一些好用的技巧。下面跟大家分享一下:

老师曾经教过说读代码可以使用”走读“的方式,也就是说不用电脑,而是只在纸上写写画画。同样的道理,我们在写程序中的算法时,也可以采用这种方法。以两个递归函数中的”字符串存到广义表“为例,我们可以先拿出来一个综合性很强的例子,比如说“(a,(b,c)),(c,(d,e)))",然后沉下心来一步一步地画它的广义表,在画的时候,你就会情不自禁的想把你发现的东西给写下来,没错,你的感觉是对的!把你发现的东西写到纸上(比如,你会发现”如果表头是a、b、c之类的字母,那么表头上要新创一个tag为0并且变量值为a、b、c这些字母的结点“)。等到你画完了一个完整的广义表,你也会在纸上写下了很多东西。没错,下一步就是编制程序。把你在纸上写下来的东西转换成为具体的代码(这时我发现最有效的方法),你就会发现写程序明显没有之前那么痛苦了。为什么这很有效呢?因为你要知道,电脑是根据人脑制造出来的,所以当你走到程序进程中的某一个地方时,你对于下一步的下意识想法或者行动,很有可能就是你程序中的那个困扰你的那个步骤。

此外,很多情况下,我们写一个成熟的程序经常需要很长时间,经常是一不小心一个上午就过去了,而且临近中午的时候,脑子几乎就是不动的,效率会慢慢变得越来越低。为了应对这种困境,我发现了一种特别好用的方法,就是把一个问题拆分成为几个小问题,然后分时间段完成这几个小问题,最后就能解决这个大问题。以之前的实验”高精度pi值得求解“为例,我是怎么拆分的呢?看下面:

1.搭建用来存放高精度小数的双链表;

2.完成双链表表示的小数与一个整数的乘法(没有发生进位的那种);

3.完成双链表表示的小数与一个整数的乘法(有发生进位的那种);

4.完成双链表表示的小数与一个整数的除法;

5.构建主函数。

通过这5步,我总共用了4个小时的时间,从无到有得做完了这个实验。有人会说,可其他不这样做的人,他们也是用的这种方法,思路和你一样,而且用的时间也没有比你慢多少,你能有多少优势?对,没错,优势确实很小,但这是一个很有效的思维工具。当面对简单问题的时候,这种分解复杂问题的思维会拖累你的思维,让问题复杂化,做的还不如其它人快,在考试时如果你用这种方法,那你十有八九会做不完卷子,最后拿一个不及格,因为考试题都是极简单不需要过多思考的题,这是它的局限性。但是当问题很复杂,或者问题综合性特别强,抑或者说这是一个你从来没有见过的工程问题时,这种思维工具就会变得特别有用。当别人束手无策不知从何下手时,你已经明确了自己需要完成几个小任务,每个小任务又需要完成什么,而且因为这些小任务往往比较简单,不需要很多的思考,往往都是可以完成的,这也给了你完成任务之后的成就感。这样子成就感给了你积极的反馈,你就会有更大的信心去完成下一个小任务。这样子相比其他人,你就有了更大更多的优势,最后你就是第一。

话题回到这道题的递归上,就我个人来说,咱们初学者没必要也不应该追求简洁的递归实现,也不应该执着于”直接调用“上,相反,”间接调用“是学习递归的一个很好的桥梁,它能够很好地帮你完成程序的编制,最后很快很好的写出行之有效的程序来。

最后,大一作为一个过渡阶段,纸和笔还是有着它无可替代的作用,不要眼高手低,低下头来踏踏实实做事情,在纸上写写画画,往往能提供给你写程序的关键思路。

最后还是真心求赞啦,你们的鼓励是对我创作的最大支持!

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
typedef enum{ATOM,LIST}ElemTag;
//ATOM==0,原子,LIST==1,子表
typedef char AtomType;
typedef struct _GLNode{ElemTag tag;union{AtomType atom;struct{struct _GLNode* head;struct _GLNode* tail;}ptr;};
}*GList;GList InitGList(){//创建空的广义表LGList L=(GList)malloc(sizeof(struct _GLNode));if(L==NULL){printf("Out of space!\n");return NULL;}else{L->ptr.head=NULL;L->ptr.tail=NULL;return L;}
}void StringDividing(GList L,char* ori_str,char* s_head,char* s_tail);
void Head_Recursion(GList L,char* s_head){if(*s_head=='('){char sub_s_head[50]={0};char sub_s_tail[50]={0};StringDividing(L,s_head,sub_s_head,sub_s_tail);}
}void Tail_Recursion(GList L,char* s_tail){if(*s_tail!='0'){char sub_s_head[50]={0};char sub_s_tail[50]={0};StringDividing(L,s_tail,sub_s_head,sub_s_tail);}
}void StringDividing(GList L,char* ori_str,char* s_head,char* s_tail){char* temp=ori_str+1;//将字符串分成“表头字符串”和“表尾字符串”//然后用"s_head"存放表头字符串//再用"s_tail"存放表尾字符串 //下面先处理"s_head": //如果s_head中只有一个字符,那么: if(*temp!='('){*s_head=*temp;//并且创建一个原子结点 GList Atom=InitGList();Atom->tag=ATOM;Atom->atom=*temp;//然后让当前结点的表头指针指向它L->ptr.head=Atom;}//但如果s_head中不止一个字符,就应该: else{//首先,确定"s_head"中要放多少字符: int fre=0;while(1){temp++;if((*temp!='(')&&(*temp!=')'));else if(*temp=='(') {fre++;}else if(*temp==')') {if(0!=fre) fre--;else break;}}//确定好了之后,就可以往"s_head"中放字符了: char* record=ori_str+1;//记录s_head的初始位置,如果不这样做,那么递归传过去的数组指针就有问题for(char *p=ori_str+1;p<=temp;){*s_head=*p;p++;s_head++;}//最后不要忘记末尾置0 *s_head='0';//并且新建一个子表结点GList list=InitGList();list->tag=LIST;//然后让当前结点的表头指针指向它 L->ptr.head=list;//再然后,还需要把当前的"s_head"送去递归分割 Head_Recursion(list,record);//record记录的是s_head的初始位置 }//然后再处理"s_tail"://分两种情况来讨论://1.如果表尾是空表的话,那么:if(*(temp+1)==')'){//当前结点的表尾指针置为NULL即可L->ptr.tail=NULL; }//2.如果表尾不是空表的话,那么: else{//因为表尾一定是表,所以还需要用"()",把它再给包起来 //先在数组头儿上放"(":char* record=s_tail; //记录s_tail的初始位置,如果不这样做,那么递归传过去的数组指针就有问题 *s_tail='(';s_tail++;//然后放中间,因为很容易确定"s_tail"中要放多少字符,所以直接往里面放就可以:for(char *p=temp+1;*p!='\0';){*s_tail=*p;p++;s_tail++;}//最后需要在数组尾儿上放")",但是因为ori_str最后自带一个")",所以不用多费心 *s_tail='0';//并且新建一个子表结点GList list=InitGList();list->tag=LIST;//然后让当前结点的表尾指针指向它L->ptr.tail=list; //再然后,还需要把当前的"s_tail"送去递归分割 Head_Recursion(list,record);//record记录的是s_tail的初始位置 }
}int GListDepth(GList L){//递归获取广义表深度if(L==NULL) return 1;//空表深度为1else if(L->tag==ATOM) return 0;int max=0;GList pp=L;for(;pp!=NULL;pp=pp->ptr.tail){int dep;dep=GListDepth(pp->ptr.head);if(dep>max) max=dep;}return (max+1);
}int main(){//创建最初的广义表 GList L=InitGList();L->tag=LIST;//从键盘中录入原始字符串 char ori[250]={0};scanf("%s",ori);//对原始字符串进行处理,去除里面的逗号char ori_str[250]={0};char* p1=ori;char* p2=ori_str;while(*p1!='\0'){if(*p1==','){p1++;}else{*p2=*p1;p1++;p2++;}}char s_head[50]={0};char s_tail[50]={0};StringDividing(L,ori_str,s_head,s_tail);int depth=0;depth=GListDepth(L);//获取广义表深度 printf("%d\n",depth);printf("%d\n",depth);return 0;
}

 


http://www.ppmy.cn/news/426941.html

相关文章

逆波兰表达式(严7.38)

Description 一个四则运算算术表达式以有向无环图的邻接表方式存储&#xff0c;每个操作数原子都由单个字母表示。编写程序输出其逆波兰表达式。 Input 输入四则运算算术表达式。 Output 输出其逆波兰表达式。 Sample Input (ab)*c Sample Output abc* 这个题要求是用有向…

一秒教你搞定前端打包上传后路由404的问题!

1、问题描述 前端实现权限管理后&#xff0c;本地路由跳转正常&#xff0c;打包上传线上出现前404找不到路由路径问题 报如下错误: 2、错误原因 打包之后根路径变化&#xff0c;前端没有将获取到的用户菜单权限中的component进行转换&#xff0c;导致上传后路径错误 3、解决…

什么是RISC-V

本文转自什么是RISC-V 向作者致敬。 RISC-V读作RISC Five&#xff0c;也即第五代精简指令处理器 什么是RISC和CISC&#xff1f; RISC(精简指令集计算机&#xff0c;Reduced Instruction Set Computer-RISC)和CISC(复杂指令集计算机&#xff0c;Complex Instruction Set Comp…

汉诺塔(Hanoi)问题归纳总结

一.汉诺塔问题及其递归算法 1.问题阐述 经典汉诺塔&#xff1a; 外文算法书对汉诺塔问题的描述&#xff1a; 2.算法步骤 三阶汉诺塔问题解题步骤 共需7步。 四阶汉诺塔问题解题步骤 共需15步 五阶汉诺塔问题解题步骤 可以清晰的看出分治思想以及递归过程 算法采用了分治的…

第五章 政策问题与议程设定

整体架构 第一节 政策问题的概念、属性与分类 第二节 公共问题的提出 第三节 问题认定与政策议程第一节 政策问题的概念、属性与分类 公共问题的形成或认定及其被纳入政府的政策议程是政策制定过程的起点&#xff0c;也是它的一个十 分重要的环节。美国学者邓恩就特别强调问题构…

【时间序列分析】03.正态时间序列与严平稳序列

文章目录 三、.正态时间序列与严平稳序列1.多元统计基础2.多维正态分布与正态时间序列3.严平稳序列回顾总结 三、.正态时间序列与严平稳序列 1.多元统计基础 首先对多元统计中的基本概念作简要介绍。如果有一个 n n n维随机向量 X ( X 1 , ⋯ , X n ) ′ X(X_1,\cdots,X_n) …

从互联网金融平台下架银行存款产品,看金融监管为何越来越严?

最近各大互联网金融平台纷纷下架了银行存款产品&#xff0c;当然这也不是今年监管对于互联网金融的第一次出手了。 前些天蚂蚁集团上市前夕&#xff0c;互联网小额信贷监管环境发生变化&#xff0c;P2P平台的清零等等的。 当然了&#xff0c;有人叫好&#xff0c;说&#xff…

OAuth安全相关问题

初识OAuth 开放授权(OAuth)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用.目前广泛使用的版本是OAuth 2.0.而OAuth2.0存在认证缺陷-即第三方帐号快捷登录授权可能被劫持。 OAuth…