广义表,也被称作列表(Lists),是一种递归的数据结构。它就像一个神秘的盒子,既可以装着单个元素(原子),也可以嵌套着其他的盒子(子列表)。比如广义表 (a (b c) d)
,其中 a
和 d
是原子,而 (b c)
则是一个子列表。这种嵌套结构使得广义表能够轻松表示各种复杂的关系,就像一位万能的画家,能描绘出树形结构、图结构等多样的 "画作"。
与普通的线性表相比,广义表的元素类型更加丰富多样,不仅可以是相同类型的元素,还能包含不同类型的元素,甚至可以嵌套其他的广义表。这种灵活性让广义表在处理复杂数据时游刃有余,成为了数据结构中的 "多面手"。
先上代码:
cpp
#include <stdio.h>
#include <stdlib.h>
// 定义原子类型为字符型
typedef char AtomType;
// 定义元素标签类型,用于区分原子和列表
typedef enum { ATOM, LIST } ElemTag;
// 定义广义表节点结构体
typedef struct GLNode
{
ElemTag tag; // 标记该节点是原子还是列表
union
{
AtomType atom; // 如果是原子,存储原子的值
struct GLNode *child; // 如果是列表,指向子列表
} UNION;
struct GLNode *next; // 指向下一个节点
} GLNODE;
// 定义元素标志类型,用于读取字符串时识别不同元素
typedef enum { LP = 1, RP = 2, Atom = 3, End = 4 } ElemFlag;
// 从 str[*pi] 开始读入一个元素
ElemFlag GetElem(char str[], int *pi, AtomType *pe)
{
while (str[*pi] == ' ') (*pi)++;// 跳过空格
if (str[*pi] == '\0') return End;// 如果到达字符串末尾,返回 End 标志
if (str[*pi] == '(')// 如果遇到左括号,返回 LP 标志,并将指针后移
{
(*pi)++;
return LP;
}
if (str[*pi] == ')') // 如果遇到右括号,返回 RP 标志,并将指针后移
{
(*pi)++;
return RP;
}
// 如果是原子,将字符赋值给 pe,并将指针后移,返回 Atom 标志
*pe = str[*pi];
(*pi)++;
return Atom;
}
// 建广义表
GLNODE *Glist_Create(char str[], int *pi)
{
GLNODE *p;
AtomType e;
// 根据读取的元素类型进行不同处理
switch (GetElem(str, pi, &e))
{
case Atom:
// 为原子节点分配内存
p = (GLNODE *)malloc(sizeof(GLNODE));
if (p == NULL)
{
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
p->tag = ATOM;
p->UNION.atom = e;
p->next = Glist_Create(str, pi);// 递归创建下一个节点
return p;
case LP:
// 为列表节点分配内存
p = (GLNODE *)malloc(sizeof(GLNODE));
if (p == NULL)
{
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
p->tag = LIST;
p->UNION.child = Glist_Create(str, pi);// 递归创建子列表
p->next = Glist_Create(str, pi);// 递归创建下一个节点
return p;
case RP:
return NULL;
case End:
return NULL;
}
return NULL;
}
// 求表深度
int GList_Depth(GLNODE *L)
{
GLNODE *p;
int depth1, max = 0;
if (L == NULL || L->tag == ATOM) return 0;// 如果是原子节点,深度为 0
for (p = L->UNION.child; p; p = p->next) // 遍历子列表
{
depth1 = GList_Depth(p);// 递归计算子列表的深度
if (depth1 > max) max = depth1;
}
return max + 1;// 列表深度为子列表最大深度加 1
}
// 遍历广义表,打印层次括号
void GList_Traverse(GLNODE *L)
{
GLNODE *p;
for (p = L; p != NULL; p = p->next)
{
if (p->tag == ATOM)
{
printf("%c ", p->UNION.atom);// 打印原子节点的值
}
else
{
printf("(");// 遇到列表节点,打印左括号
GList_Traverse(p->UNION.child);// 递归遍历子列表
printf(")");// 打印右括号
}
}
}
// 复制广义表
GLNODE *GList_Copy(GLNODE *L)
{
GLNODE *head = NULL, *p, *newNode, *tail = NULL;
if (!L) return NULL;
for (p = L; p != NULL; p = p->next)
{
// 为新节点分配内存
newNode = (GLNODE *)malloc(sizeof(GLNODE));
if (newNode == NULL)
{
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
if (head == NULL)
{
head = tail = newNode;
} else
{
tail->next = newNode;
tail = newNode;
}
if (p->tag == ATOM)
{
newNode->tag = ATOM;// 复制原子节点
newNode->UNION.atom = p->UNION.atom;
} else
{
newNode->tag = LIST;// 复制列表节点,递归复制子列表
newNode->UNION.child = GList_Copy(p->UNION.child);
}
}
if (tail)
{
tail->next = NULL;
}
return head;
}
int main()
{
char str[30] = "(a (b c) d)"; // 广义表的字符串表示
int i = 0;
GLNODE *L1, *L2;
L1 = Glist_Create(str, &i); // 创建广义表 L1
GList_Traverse(L1); // 遍历并打印广义表 L1
printf("%d\n", GList_Depth(L1));// 计算并打印广义表 L1 的深度
L2 = GList_Copy(L1); // 复制广义表 L1 到 L2
GList_Traverse(L2); // 遍历并打印广义表 L2
printf("%d\n", GList_Depth(L2));// 计算并打印广义表 L2 的深度
return 0;
}
(一)数据结构定义:搭建广义表的基石
typedef struct GLNode
{
ElemTag tag; // 标记该节点是原子还是列表
union
{
AtomType atom; // 如果是原子,存储原子的值
struct GLNode *child; // 如果是列表,指向子列表
} UNION;
struct GLNode *next; // 指向下一个节点
} GLNODE;
这段代码定义了广义表的节点结构 GLNODE
。tag
字段就像一个神奇的标签,能够区分节点是原子还是列表。UNION
联合体则根据 tag
的值,巧妙地存储原子或指向子列表的指针。next
指针则负责将各个节点连接起来,形成一个有序的链条。这种设计让广义表能够灵活地表示不同类型的元素和复杂的嵌套结构,为后续的操作奠定了坚实的基础。
(二)广义表的创建:从字符串到数据结构的神奇转变
GLNODE *Glist_Create(char str[], int *pi)
{
// ...
}
Glist_Create
函数是广义表创建的核心。它通过 GetElem
函数从输入的字符串中逐个读取元素,就像一位细心的工匠,根据元素的类型(原子、左括号、右括号、字符串结束)进行不同的处理。遇到原子时,为其创建一个原子节点;遇到左括号时,创建一个列表节点,并递归地创建子列表。递归的方式让代码简洁而高效,能够轻松应对广义表的嵌套结构,就像一位技艺高超的魔术师,将字符串神奇地转化为广义表的数据结构。
(三)广义表的深度计算:探索广义表的 "深度" 秘密
int GList_Depth(GLNODE *L)
{
// ...
}
GList_Depth
函数用于计算广义表的深度。对于原子节点,其深度为 0;对于列表节点,则递归地计算子列表的深度,并取子列表最大深度加 1 作为当前列表的深度。递归的思想在这里再次发挥了重要作用,让我们能够轻松地探索广义表的 "深度" 秘密,就像一位勇敢的探险家,深入广义表的内部,测量其嵌套的层次。
(四)广义表的遍历:揭开广义表的 "庐山真面目"
void GList_Traverse(GLNODE *L)
{
// ...
}
GList_Traverse
函数就像一位导游,带领我们遍历广义表并打印其层次括号表示。遇到原子节点时,直接打印原子的值;遇到列表节点时,先打印左括号,递归地遍历子列表,再打印右括号。通过递归遍历,我们能够清晰地看到广义表的层次结构,揭开它的 "庐山真面目",仿佛置身于一个神秘的迷宫中,一步步揭开它的神秘面纱。
(五)广义表的复制:克隆一个一模一样的广义表
GLNODE *GList_Copy(GLNODE *L)
{
// ...
}
GList_Copy
函数用于复制广义表。它遍历原广义表的每个节点,为新节点分配内存,并根据节点类型复制原子或递归地复制子列表。这样,我们就可以得到一个与原广义表结构完全相同的新广义表,就像克隆技术一样,复制出一个一模一样的 "双胞胎"。
运行:

四、广义表的应用场景:无处不在的 "魔法工具"
(一)编译器设计:解析代码的得力助手
在编译器中,广义表可以用于表示语法树。语法树是源代码的一种抽象表示,它反映了代码的语法结构。广义表的嵌套结构正好可以用来表示语法树的层次关系,方便编译器进行语法分析和代码生成。就像一位聪明的翻译官,将源代码翻译成计算机能够理解的机器语言。
(二)人工智能:构建知识图谱的强大武器
在人工智能领域,广义表可以用于表示知识图谱。知识图谱是一种语义网络,用于表示实体之间的关系。广义表可以将实体和关系表示为节点和子列表,从而方便进行知识的存储和推理。就像一位智慧的导师,帮助人工智能系统更好地理解和处理知识。
(三)图形处理:绘制复杂图形的神奇画笔
在图形处理中,广义表可以用于表示复杂的图形结构。例如,一个三维模型可以由多个子模型组成,每个子模型又可以由多个基本图形组成。广义表可以很好地表示这种层次结构,方便进行图形的渲染和处理。就像一位天才的画家,用神奇的画笔绘制出绚丽多彩的图形世界。
五、总结与展望
通过递归的方式,我们可以方便地实现广义表的创建、深度计算、遍历和复制等操作。