408数据结构-树与森林 自学知识点整理

前置知识:树的基本概念与性质


树的存储结构

树既可以采用顺序存储结构,又可采用链式存储结构。但无论采取哪种方式,都要求能够唯一地反映树中各结点之间的逻辑关系。

1. 双亲表示法

这种存储结构采用一组连续空间来存储每个结点,同时在每个结点增设一个伪指针,指示其双亲结点在数组中的位置。

#define MAX_TREE_SIZE 100
typedef struct ElemType {int value;
}ElemType;//预处理typedef struct {ElemType data;int parent;//双亲位置域
}PTNode;
typedef struct {//树的类型定义PTNode nodes[MAX_TREE_SIZE];//双亲表示int n;//结点数
}PTree;

双亲表示法利用了除根结点以外每个结点只有唯一双亲的性质,优点是可以很快地得到每个结点的双亲结点,但缺点也很明显,求结点的孩子时需要遍历整个结构。
使用双亲表示法存储树时,删除结点共有两个方法:

  • 一是直接把需要删除的结点 p a r e n t parent parent值置 − 1 -1 1,表示当前结点为空(根结点默认存放在 n o d e s [ 0 ] 的位置 nodes[0]的位置 nodes[0]的位置,且 p a r e n t parent parent值也为 − 1 -1 1,需要特别判断)。但是这种方法在遍历找孩子时,会使得遍历过程要经过一个或多个空结点,徒增时间复杂度。
  • 二是把需要删除的结点和数组末尾元素交换,然后结点数n--。这种方法要优于方法一。

2. 孩子表示法

孩子表示法是将每个结点的孩子视为一个线性表,且以单链表作为数据结构,这样 n n n个结点就有 n n n个孩子链表(叶结点的孩子链表视为空表)。

struct CTNode {//单链表(B站弹幕说是邻接表)int child;//孩子节点在数组中的位置struct CTNode* next;//下一个孩子
};
typedef struct {ElemType data;struct CTNode* firstChild;//第一个孩子
}CTBox;
typedef struct {CTBox nodes[MAX_TREE_SIZE];//孩子表示法int n, r;//结点数和根的位置
}CTree;

与双亲表示法相反,孩子表示法寻找结点孩子的操作非常方便,但是寻找双亲的操作则需要遍历 n n n个结点中孩子链表指针域所指向的 n n n个孩子链表。同样的,可以顺着这个思路思考,如何实现孩子表示法存储的树的增删查改等基本操作。

3.孩子兄弟表示法⭐

孩子兄弟表示法又称二叉树表示法,即以二叉链表作为树的存储结构。孩子兄弟表示法使每个结点包括三部分内容:结点值、指向结点第一个孩子结点的指针,以及指向结点下一个兄弟结点的指针。

typedef struct CSNode {//孩子兄弟表示法ElemType data;//数据域struct CSNode* firstchild, * nextsibling;//第一个孩子和右兄弟指针
}CSNode, * CSTree;

使用这种方法最大的优点就是可以实现将树转换为二叉树的操作,优缺点与二叉树的链式存储结构相同,这里不再展开。

树、森林与二叉树的相互转换

从物理结构上看,树的孩子兄弟表示法和二叉树的二叉链表表示法是相同的。因此可以用同一存储结构的不同解释将一棵树转换为二叉树。

1 ) 1) 1)树转换为二叉树

树转换为二叉树的规则:每个结点的左指针指向它的第一个孩子,右指针指向它在树中的相邻右兄弟,这个规则也称“左孩子右兄弟”。由于根结点没有兄弟,因此树转换得到的二叉树没有右子树,如图所示。 (图片来自王道考研408数据结构2025)
图片来自王道考研408数据结构2025
树转换为二叉树的画法:
①在兄弟结点之间加一条线;
②对每个结点,只保留它与第一个孩子的连线,与其他孩子的全部抹掉;
③以树根为轴心,顺时针旋转45°。
二叉树转换为树逆着来就行。

2 ) 2) 2)森林转换为二叉树

森林转换为二叉树的规则与树类似,只需要把每一棵树的根结点都看成兄弟,依次连在第一棵树根结点的右子树上即可。
森林转换为二叉树的画法:
①将森林中的每棵树转换成相应的二叉树;
②每棵树的根也可视为兄弟关系,在每棵树的根之间加一根连线;
③以第一棵树的根为轴心,顺时针旋转45°。
二叉树转换为森林同样逆着来就行,这里就不再展开。


树和森林的遍历

前置知识:二叉树的遍历

1. 树的遍历

树的遍历是指用某种方式访问树中的每个结点。主要有两种方法:

1 ) 1) 1)先根遍历。若树非空,则按如下规则遍历:
  • 先访问根结点。
  • 再依次遍历根结点的每棵子树,遍历子树时仍遵循先根后子树的规则。
void Visit(CSNode* T) {cout << T->data.value << " ";return;
}void PreOrder(CSTree T) {//先根遍历(使用孩子兄弟表示法)if (T != NULL) {Visit(T);//访问根结点CSTree C = T->firstchild;//C记录当前树根结点的第一个孩子do {//首先对以C为根的子树进行先根遍历PreOrder(C);C = C->nextsibling;//再依次对C的右兄弟为根的子树进行先根遍历} while (C != NULL);//当C还有其他右兄弟时循环继续}return;
}

其遍历序列与这棵树对应的二叉树的先序序列相同。

2 ) 2) 2)后根遍历。若树非空,则按如下规则遍历:
  • 先依次遍历根结点的每棵子树,遍历子树是仍遵循先子树后根的规则。
  • 再访问根结点。
void PostOrder(CSTree T) {//后根遍历if (T != NULL) {CSTree C = T->firstchild;do {PostOrder(C);//对以C为根的子树后根遍历C = C->nextsibling;//依次访问C的右兄弟结点} while (C != NULL);Visit(T);//最后访问根结点}return;
}

其遍历序列与这棵树对应二叉树的中序序列相同。
例如,前文中图 5.23 5.23 5.23中的树,先根遍历序列为 A B E F C D G ABEFCDG ABEFCDG,后根遍历序列为 E F B C G D A EFBCGDA EFBCGDA

3)层次遍历

基本思想与二叉树的层次遍历相同。首先构造辅助队列;根结点入队;队头元素出队,访问当前结点,再将该结点的所有孩子结点入队;依次类推,直到队列为空。

2. 森林的遍历

由于森林和树相互递归的定义,可以得到森林的两种遍历方法:

1 ) 1) 1)先序遍历森林。若森林非空,则按如下规则遍历:
  • 先访问森林中第一棵树的根结点。
  • 先序遍历第一棵树中根结点的子树森林。
  • 先序遍历除去第一棵树之后剩余的树构成的森林。

其实就是按顺序对森林里每棵树依次进行先根遍历,然后把遍历序列按顺序连起来;或者把森林转换成二叉树再进行先序遍历。

2 ) 2) 2)中序遍历森林。若森林非空,则按如下规则遍历:
  • 中序遍历第一棵树中根结点的子树森林。
  • 访问森林中第一棵树的根结点。
  • 中序遍历除去第一棵树之后剩余的树构成的森林。

其实就是按顺序对森林里每棵树依次进行后根遍历,然后把遍历序列按顺序连起来;或者把森林转换成二叉树再进行中序遍历。


对树的遍历,层次遍历又被称为广度优先遍历,先根、后跟又被称为深度优先遍历。
408考研初试中,需掌握手推树与森林的遍历序列的能力,如果运气太差碰到代码实现题,可以先转换成二叉树,再用熟悉的方法处理问题。
以上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/670897.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DW PCIE LINUX的初始化分析

一些定义 PCIE复位&#xff1a;一些PCIE复位的知识链接 PCIE初始化&#xff1a;初始化相关定义看下面链接和下图 C语言简化初始化看本人的《DW PCIE的PCIE的RC和EP最简初始化学习笔记》文章。 Sticky Registers&#xff1a;与传统的复位方式相同&#xff0c;FLR方式不能复位…

Linux环境下的事件驱动力量:探索Libevent的高性能I/O架构

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的Linux高性能服务器编程系列之《Linux环境下的事件驱动力量&#xff1a;探索Libevent的高性能I/O架构》&#xff0c;在这篇文章中&#xff0c;你将会学习到Libevent的高性能I/O原理以及应用&#xff0c;并且我会给出源码…

java中的字符串(String)常量池理解

下面创建String对象的方式一样吗&#xff1f; 上述程序创建对象类似&#xff0c;为什么s1和s2引用对象一样&#xff0c;但是s3和s4不一样呢&#xff1f; 在java程序中&#xff0c;许多基本类型的字面常量会经常用到&#xff0c;例如2,3.11&#xff0c;“hyy”等。为了提升程序…

一文读懂Vue生命周期(Vue2)

一文读懂Vue生命周期&#xff08;Vue2&#xff09; 目录 一文读懂Vue生命周期&#xff08;Vue2&#xff09;1 前言2 Vue生命周期2.1 基本生命周期2.1.1 8个生命周期2.1.2 案例 2.2 组件生命周期2.2.1 父子生命周期2.2.2 案例 2.3 keep-alive生命周期2.3.1 案例 2.4 其他 3 总结…

OpenHarmony实战开发-应用侧调用前端页面函数

应用侧可以通过runJavaScript()方法调用前端页面的JavaScript相关函数。 在下面的示例中&#xff0c;点击应用侧的“runJavaScript”按钮时&#xff0c;来触发前端页面的htmlTest()方法。 前端页面代码。 <!-- index.html --> <!DOCTYPE html> <html> <…

迭代器解释(C++)

一、什么是迭代器 为了提高C编程的效率&#xff0c;STL&#xff08;Standard Template Library&#xff09;中提供了许多容器&#xff0c;包括vector、list、map、set等。然而有些容器&#xff08;vector&#xff09;可以通过下标索引的方式访问容器里面的数据&#xff0c;但是…

php基础知识快速入门

一、PHP基本知识 1、php介绍&#xff1a; php是一种创建动态交互性的强有力的服务器脚本语言&#xff0c;PHP是开源免费的&#xff0c;并且使用广泛。PHP是解释性语言&#xff0c;按顺序从上往下执行&#xff0c;无需编译&#xff0c;直接运行。PHP脚本在服务器上运行。 2、ph…

【STM32嵌入式系统设计与开发】——18StaticNixite(静态数码管应用)

这里写目录标题 STM32资料包&#xff1a; 百度网盘下载链接&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1mWx9Asaipk-2z9HY17wYXQ?pwd8888 提取码&#xff1a;88881、函数编辑&#xff08;1&#xff09;主函数编辑&#xff08;2&#xff09;主函数头文件函数&#x…

Vue 介绍

【1】前端发展史 前端的发展史可简述为&#xff1a; 从最初的静态页面编写&#xff0c;依赖后端模板渲染逐步演化为通过JavaScript&#xff08;特别是Ajax技术&#xff09;实现前后端分离&#xff0c;使得前端能够独立地加载数据和渲染页面随后&#xff0c;Angular、React、Vu…

CTF(Web)中关于执行读取文件命令的相关知识与绕过技巧

在我遇到的题目中&#xff0c;想要读取文件必然是要执行cat /flag这个命令&#xff0c;但是题目当然不会这么轻松。让你直接cat出来&#xff0c;必然会有各种各样的滤过条件&#xff0c;你要做的就是尝试各种方法在cat /flag的基础上进行各种操作构建出最终的payload。 下面我…

Vite构造Vue3

环境安装 node.js安装-CSDN博客 初始化Vue项目安装脚手架_vue init webpack安装脚手架-CSDN博客 选择Vue框架 &#xff0c;项目名称可以自定义&#xff0c;我使用默认的 vite-project 选择JS 进入项目安装依赖 安装路由

Leetcode编程练习

面试题-消失的数字 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:void reverse(vector<int>& nums, int start, int end) {while (start < end) {swap(nums[start], nums[end]);start 1;end - 1;}}void rotate(vector<int>& …