当前位置: 首页 > news >正文

数据结构中的宝藏秘籍之广义表

广义表,也被称作列表(Lists),是一种递归的数据结构。它就像一个神秘的盒子,既可以装着单个元素(原子),也可以嵌套着其他的盒子(子列表)。比如广义表 (a (b c) d),其中 a 和 d 是原子,而 (b c) 则是一个子列表。这种嵌套结构使得广义表能够轻松表示各种复杂的关系,就像一位万能的画家,能描绘出树形结构、图结构等多样的 “画作”。

与普通的线性表相比,广义表的元素类型更加丰富多样,不仅可以是相同类型的元素,还能包含不同类型的元素,甚至可以嵌套其他的广义表。这种灵活性让广义表在处理复杂数据时游刃有余,成为了数据结构中的 “多面手”。

先上代码:

#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;// 如果是原子节点,深度为 0for (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);		// 创建广义表 L1GList_Traverse(L1);				// 遍历并打印广义表 L1printf("%d\n", GList_Depth(L1));// 计算并打印广义表 L1 的深度L2 = GList_Copy(L1);			// 复制广义表 L1 到 L2GList_Traverse(L2);				// 遍历并打印广义表 L2printf("%d\n", GList_Depth(L2));// 计算并打印广义表 L2 的深度return 0;
}

(一)数据结构定义:搭建广义表的基石

typedef struct GLNode 
{ElemTag tag;  // 标记该节点是原子还是列表union {AtomType atom;  // 如果是原子,存储原子的值struct GLNode *child;  // 如果是列表,指向子列表} UNION;struct GLNode *next;  // 指向下一个节点
} GLNODE;

这段代码定义了广义表的节点结构 GLNODEtag 字段就像一个神奇的标签,能够区分节点是原子还是列表。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 函数用于复制广义表。它遍历原广义表的每个节点,为新节点分配内存,并根据节点类型复制原子或递归地复制子列表。这样,我们就可以得到一个与原广义表结构完全相同的新广义表,就像克隆技术一样,复制出一个一模一样的 “双胞胎”。

运行:

四、广义表的应用场景:无处不在的 “魔法工具”

(一)编译器设计:解析代码的得力助手

在编译器中,广义表可以用于表示语法树。语法树是源代码的一种抽象表示,它反映了代码的语法结构。广义表的嵌套结构正好可以用来表示语法树的层次关系,方便编译器进行语法分析和代码生成。就像一位聪明的翻译官,将源代码翻译成计算机能够理解的机器语言。

(二)人工智能:构建知识图谱的强大武器

在人工智能领域,广义表可以用于表示知识图谱。知识图谱是一种语义网络,用于表示实体之间的关系。广义表可以将实体和关系表示为节点和子列表,从而方便进行知识的存储和推理。就像一位智慧的导师,帮助人工智能系统更好地理解和处理知识。

(三)图形处理:绘制复杂图形的神奇画笔

在图形处理中,广义表可以用于表示复杂的图形结构。例如,一个三维模型可以由多个子模型组成,每个子模型又可以由多个基本图形组成。广义表可以很好地表示这种层次结构,方便进行图形的渲染和处理。就像一位天才的画家,用神奇的画笔绘制出绚丽多彩的图形世界。

五、总结与展望

通过递归的方式,我们可以方便地实现广义表的创建、深度计算、遍历和复制等操作。

相关文章:

  • 3个实用的脚本
  • 使用Lombok @Builder 收参报错提示没有无参构造方法的原因与解决办法
  • Zookeeper介绍与安装配置
  • conversation_template | conversation_actors | conversation_line_template
  • Yarn的安装及环境配置
  • 专精特新政策推动,B端UI设计如何赋能中小企业创新发展?
  • GCD算法的学习
  • MySQL内置函数:字符串函数,数值函数,日期函数,流程控制函数
  • 基于VS Code 为核心平台的python语言智能体开发平台搭建
  • Oracle 19c部署之RMP一键安装初始化(五)
  • 微前端框架QianKun
  • 开源AI守护每一杯------奶茶咖啡店视频安全系统的未来之力
  • 20250418 一个正定矩阵的引理
  • 算法-链表
  • Docker Image export and load and tag
  • Xcode16 调整 Provisioning Profiles 目录导致证书查不到
  • React 函数组件和类组件的区别
  • dify本地部署,docker-plugin_daemon-1启动不了,一直报错
  • OpenSPG/KAG V0.7发布,多方面优化提升,事实推理效果领先且构建成本降至11%
  • 科技天眼守望农田:珈和卫星遥感监测赋能智慧农业,护航粮食安全新未来
  • 女子伸腿阻止列车关门等待同行人员,被深圳铁路警方行政拘留
  • 上海这台人形机器人完成半马:无故障、无摔倒,冲过终点不忘挥手致意
  • 对话地铁读书人|来自法学教授的科普:读书日也是版权日
  • 为博眼球竟编造一女孩被活埋,公安机关公布10起谣言案件
  • 不断深化“数字上海”建设!上海市数据发展管理工作领导小组会议举行
  • 美国开始从叙利亚撤出数百人,分析人士担忧“伊斯兰国”威胁再起