假设你已经知道了广义表长什么样(一堆括号、图像长得挺像二叉树。网课可以很快理解) 且对递归有一点熟悉了,
接下来我们审题:
广义表类型GList的定义:
-
typedef enum {ATOM, LIST} ElemTag;
: 定义了一个枚举类型ElemTag
,其中ATOM
表示原子元素,LIST
表示子表。 -
typedef struct GLNode { ... } *GList;
: 定义了一个结构体GLNode
,并将其指针类型命名为GList
。这个结构体表示广义表的节点。 -
ElemTag tag
: 表示节点的类型,可以是ATOM
或LIST
。 -
union { ... } un;
: 定义了一个联合体un
,用于存储节点的数据。如果节点是ATOM
,则存储char atom
;如果节点是LIST
,则存储struct { GLNode *hp, *tp; } ptr;
,其中hp
指向子表的头节点,tp
指向子表的尾节点。
要求实现以下函数:
-
int GListDepth(GList ls);
: 这是一个函数声明,要求实现一个名为GListDepth
的函数,该函数接受一个GList
类型的参数ls
,并返回一个整数,表示广义表的深度。
广义表的定义:
-
GList
是一个指向广义表节点的指针。 -
每个节点有两种类型:
ATOM
表示原子元素,LIST
表示子表。 -
如果节点是
ATOM
类型,它包含一个字符atom
。 -
如果节点是
LIST
类型,它包含两个指针:hp
指向子表的头节点,tp
指向子表的尾节点(剩下元素的下一个节点,一般箭头向右。横着看像链表一样)。
广义表的深度:
-
广义表的深度是从根节点到最深子表的嵌套层数。
-
例如,一个只包含原子元素的广义表的深度为1。
-
如果广义表包含子表,则需要递归计算每个子表的深度,并取最大值。
好了,接下来看图我的注释
depth初始化随便设。
初始值为什么可以随意设定?
因为在递归的过程中,depth都会被重新赋值。所以它们的初始值无论是多少,都不会影响最终结果。
看一下代码:
一开始你声明了 depth1
和 depth2
,但在紧接着的两行代码里,depth1
和 depth2
被赋值为递归调用的结果。所以,无论它们初始值是多少,在接下来的赋值操作中都会被覆盖。而在递归操作“递”的尽头,是开头return 1和return 0所给的初始值。也就是说,代码在每一次递归调用时,都会重新计算并设置 depth,之前的初始值不会对结果产生任何影响。
为什么需要if比较大小?
虽然广义表不是严格的二叉树,但它具有层次结构。通过递归调用,我们需要计算每个子表的深度,然后返回最大深度。以当前节点ls为起始,depth1
代表表头(向下箭头)部分的深度,而 depth2
代表表尾(向右箭头)部分的深度,只有取这两者的最大值,才能得到整个广义表的实际深度。否则,只考虑了表头或者表尾的一部分,可能会漏掉广义表中的深层嵌套结构。
A = (a, (b, c), d)
在这个例子里,A
是一个表,里面有元素a
,一个子表(b, c)
,还有d
。我们要做的是计算这个表的深度,也就是有几层盒子被嵌套了。
表头(向下箭头hp)和表尾(向右箭头tp)
hp
就像你打开这个表后的第一个东西,可能是一个元素,也可能是另一个子表。比如这里的a
。tp
是剩下的部分,也就是(b, c)
和d
。
再举个例子
A = ((a), (b, (c)), d)
比如这个例子,假设我们处在第一次递归,向下箭头会返回depth1 = 1(不是2,2是最后一次递归的“归”时+1,不必担心)这是因为表头是(a)。
而向右箭头,即剩下元素,才是真正能找到最深深度的地方。对,就是那个c。所以这要靠向右箭头的递归操作来记录。且如果向右箭头tp不跟向下箭头hp比较,岂不是a成了最深的地方?
if(!ls)
return 1;
为什么要返回1,而不是返回0?
在递归算法中,ls
为空(NULL
)表示我们已经遍历到一个“没有内容”的地方,也就是递归的最底层。在计算广义表的深度时,这种空的情况代表一个“结束的层次”,因此返回1。
为什么返回1?你可以把空表看成一个特殊情况,虽然它是空的,但它仍然占据一层。因此,当tp
指向NULL
时,表示你已经遍历到这一层的最底部了,虽然没有更多内容,但广义表的结构还在,所以深度应该是1。
别忘了,你虽然进了这一层深度的链表,但还没给它计数呢,而这一行链表的深度如何结算?靠的就是最右边的NULL返回1。我们需要返回1,表示我们在这个层次已经“结束”了,准备通过递归的“归”一步步回到上一层。
如果这里返回0,那你会漏掉这一层空表的深度,导致计算出来的深度比实际的要少1层。
为什么GListDepth(ls->un.ptr.hp) + 1,而二叉树不用+1?
是因为广义表的深度计算涉及到子表的嵌套层次。具体来说,广义表的深度是从当前节点到最深子表的嵌套层数。
+1
表示向下箭头方向当前节点是一个子表,需要在其子表头节点的深度基础上再加1
其实和二叉树求深度没什么不同,只是左孩子得+1。但有些教学顺序(比如我这)是先学广义表,这其实挺不合理的