1. 广义表到底是什么?从“购物清单”到“俄罗斯套娃”
如果你学过数据结构,肯定对“线性表”不陌生,它就像一张简单的购物清单,上面一项一项地列着“苹果”、“牛奶”、“面包”。每一项都是一个独立的、不可再分的“原子”。广义表,你可以把它理解为这张购物清单的“超级进化版”。
想象一下,你的购物清单上,有一项写的是“周末野餐套装”。点开这项,里面又是一个小清单:“三明治”、“水果拼盘”、“饮料”。而“水果拼盘”这一项,点开又能看到“苹果”、“香蕉”、“葡萄”。你看,清单里套着清单,这就是广义表最核心的思想:它的元素既可以是一个不可分割的“原子”(比如“苹果”),也可以是另一个完整的“广义表”(比如“周末野餐套装”及其包含的子清单)。
用更技术一点的话说,广义表是n (n≥0)个数据元素组成的有限序列。它用圆括号()括起来,元素之间用逗号,分隔。比如(a, b, c)是一个简单的广义表,(a, (b, c), d)则是一个包含了子表(b, c)的广义表。空表就是一对空括号()。
我刚开始学的时候,总觉得这概念有点绕。后来一个老师打了个绝佳的比方:广义表就像“俄罗斯套娃”。最大的娃娃(最外层的括号)是广义表本身,打开它,里面可能是一个实心的小娃娃(原子),也可能是一个能继续打开的套娃(子表)。这个嵌套可以一直进行下去。这个类比一下子让我豁然开朗,理解嵌套结构再也不头疼了。
那么,这东西到底有啥用呢?为什么我们要费劲去学它?简单来说,当你要处理的数据本身就有层次关系、树形关系时,用线性表就太吃力了。比如表示一个公司的组织架构(公司下面有部门,部门下面有小组)、表示一个数学表达式(表达式里有子表达式)、或者我们今天要重点讲的——表示一棵二叉树。广义表提供了一种非常简洁、直观且与人类思维接近的方式,来描述这些复杂的关系。它不仅是理论上的优美,在编译器设计、人工智能的知识表示等领域,都有实实在在的应用。
2. 玩转广义表:拆解表头、表尾与计算深度长度
理解了广义表是个“套娃”,我们再来学两个操作它的“工具”:取表头(Head) 和 取表尾(Tail)。这是广义表最经典、也最容易让人迷糊的操作。
表头(Head):指的是广义表的第一个元素。注意,是第一个“元素本身”,不管它是原子还是另一个子表。比如广义表 L = (a, (b, c), d),它的表头 Head(L) 就是原子 a。
表尾(Tail):指的是除去表头之后,剩余所有元素构成的新广义表。这里是最关键的点:表尾永远是一个广义表,哪怕剩余部分只有一个元素,它也会被括号包起来成为一个表。接上面的例子,Tail(L) 不是 (b, c), d,而是 ((b, c), d)。你看,外面多了一层括号,因为它表示的是“剩下的元素组成的那个新表”。
我当年在这里踩过一个坑。假设有个简单的表 S = (a)。Head(S) 是 a,这没问题。那 Tail(S) 呢?不是 (),也不是空,而是 () 吗?不对。根据定义,去掉表头a,剩下的元素组成的表——因为a是唯一元素,去掉后就没元素了,所以剩下的是一个空表,记作 ()。所以 Tail((a)) = ()。一定要记住,表尾操作的结果永远带着最外层的括号,它本身就是一个广义表。
我们来看个复杂点的例子,手动拆解一下,这就像玩一个解套娃的游戏。设广义表 A = (((a)), (b), c, (a, ((d, e))))。现在想用 Head 和 Tail 的组合操作,把最里面的原子 e 取出来。步骤是这样的:
Tail(A):去掉第一个元素((a)),得到((b), c, (a, ((d, e))))。Tail(Tail(A)):再去掉第一个元素(b),得到(c, (a, ((d, e))))。Tail(Tail(Tail(A))):再去掉c,得到((a, ((d, e))))。注意,现在剩下的是一个只包含一个元素(a, ((d, e)))的表。Head(Tail(Tail(Tail(A)))):取这个单元素表的表头,得到(a, ((d, e)))。Tail(Head(...)):对(a, ((d, e)))取表尾,去掉


5887

被折叠的 条评论
为什么被折叠?



