数据结构与算法 复习笔记 第六章 树

6.1树的定义和基本术语

6.1.1树和森林

树的定义如下:
树(tree)是包括n个结点的有限集合T(n≥1),使得:
(1)有且仅有一个特定的称为根(root)的结点。
(2)除根以外的其他结点被分成m个(m≥0)不相交的有限集合T,T.,··T。,而每一个集合又都是树。其中,树T,Tz,···,T。称做这个根的子树(subtree)。
这个定义是递归的,即在树的定义中又用到了树的概念。
自然界中树的子结点次序没有必要加以区别,称为无序树。但计算机的存储是有序的,为方便计算机处理,往往把子结点按从左到右的次序顺序编号,即把树作为有序树(ordered tree)看待。
注意:度为2的有序树并不是二叉树,因为有序树中在第一子结点被删除后,第二子结点自然顶替成为第一子结点。因此,度为2并且严格区分左右两个子结点的有序树才是二叉树。
森林(forest)是零棵或多棵不相交的树的集合(通常是有序集合)。对于树中的每个结点,其子树组成的集合就是森林;而加入一个结点作为根,森林就可以转化成一棵树。
树形结构在客观世界中是大量存在的,有多种逻辑表示方法,例如树形表示法、凹入表示法、文氏图表示法、嵌套括号表示法等。

数据结构与算法 复习笔记 第六章 树

6.1.2森林与二叉树的等价转换

树与二叉树、森林与二叉树之间可以相互转化,而且这种转换是一一对应的。树和森林转化成二叉树后,森林或树的相关操作都可以转换成对二叉树的操作。
树和森林到二叉树的转换过程可用连线、切线、旋转“三步曲”来说明:
•连线:将兄弟结点用线连接起来。
•切线:保留父结点与其第一个子结点的连线,将父结点到其他子结点的连线切掉。
•旋转:以根为轴,平面向下顺时针方向旋转一定的角度。旋转只是为了调整画面,使得化后的二叉树看起来比较规整。
可以证明,树和森林做这样的转换所构成的二叉树是唯一的。数据结构与算法 复习笔记 第六章 树

反过来,自然也可以把二叉树转换为树或森林:形象地说是上面三步的逆操作。
•旋转:以根为轴,平面逆时针方向旋转。
•补线:若结点x是父结点的左子结点,则把x的右子结点、右子结点的右子结点,依次类推,直到最右子结点,用连线与y连起来。
•删线:去掉所有父结点到右子结点的连线。
数据结构与算法 复习笔记 第六章 树

6.1.3数的抽象数据类型

类似于二叉树,树的抽象数据类型包括结点类和树类,分别由代码6.1和代码6.2给出。抽象数据类型中没有给出类中成员函数的具体实现。
【代码 6.1】 树结点的抽象数据类型。
template < class T >
class TreeNode
public:
TreeNode(const T& value); //拷贝构造函数
virtual ~TreeNode(){1; // 析构函数
bool isLeaf(); // 判断当前结点是否为叶结点
T Value( ); // 返回结点的值
TreeNode*LeftMostChild(); // 返回第一个左子结点
TreeNode *RightSibling(); // 返回右兄弟
设置当前结点的值
void setValue( const T& value);
void setChild(TreeNode * pointer); // 设置左子结点
//设置右兄弟
void setSibling(TreeNode * pointer); // 以第一个左子结点身份插入结点
void InsertFirst(TreeNode * node);
void InsertNext(TreeNode *node); // 以右兄弟的身份插入结点
【代码6.1结束】

【代码6.2】 树的抽象数据类型。
template
class Tree 1
public:
Tree(); // 构造函数
virtual ~ Tree(); // 析构函数
TreeNode * getRoot(); // 返回树中的根结点
void CreateRoot( const T& rootValue); / 创建值为 rootValue 的根结点
bool isEmpty(); // 判断是否为空树
TreeNode * Parent(TreeNode * current); // 返回当前结点的父结点
TreeNode * PrevSibling(TreeNode * current);//返回当前结点的前一个兄弟
void DeleteSubTree(TreeNode * subroot); // 删除以 subroot 为根的子树
void RootFirstTraverse(TreeNode * root); // 先根深度优先周游树
void RootLastTraverse(TreeNode * root); // 后根深度优先周游树
void WidthTraverse( TreeNode *root); // 广度优先周游树
1;
【代码6.2结束】

6.1.4树的周游

由树和森林的定义可以引出两种周游树或森林的方法,既可以按深度的方向周游树和森林也可以按广度的方向周游树和森林。

  1. 深度优先周游树
    对于树或森林,一个结点可能具有多于两个子树,因此不能像二叉树的中序周游法那样给:
    树的中根次序周游方式,但是仍然可以考虑树或森林的前序和后序周游算法。
    仿照周游二叉树深度优先算法的前序法和后序法可以类似地定义周游树和森林的先根次!
    和后根次序周游。
    先根次序周游森林:
    (1)访问森林中第一棵树的根结点。
    (2)在先根次序下周游第一棵树根结点的子树森林。
    (3)在先根次序下周游其他的树构成的森林。
    后根次序周游森林:
    (1)在后根次序下周游第一棵树根结点的子树森林。
    (2)访问森林中第一 棵树的根结点。
    (3)在后根次序下周游其他树。
    数据结构与算法 复习笔记 第六章 树
    2.广度优先周游
    广度优先(breadth-fist)周游也称“宽度优先周游”,或者“层次周游”从树时第0层( 根节点)开始,自上至下逐层周游;在同一层中,则按照从左到右的顺序对结点逐一访问。

6.2树的链式存储结构
看书

6.3树的顺序存储结构
看书

6.4K叉树

前面讨论的树是各结点有任意度数的树。在一般的情况下,允许一棵树的结点有不同的度。
本节简单介绍一种特殊的树——K叉树。K叉树T是具有下列性质的有限结点集:
(1)集合可以为空。
(2)非空集合是由一个根结点root及K棵互不相交的K叉树构成。也就是说,其余结点被划分成T0,T1,···,Tk-1(K≥1)个子集,每个子集都是K叉树,使得T={R,To,T1,…,Tk-1}。
二叉树的许多性质可以推广到K叉树。在考虑K叉树中叶子结点数目与分支结点数目之间的关系时,同样可推出类似的定理。K叉树的各分支结点都有K个子结点,子结点的数目是确定的,因此实现K叉树比较容易,可以使用前面介绍的链式或顺序存储方式。

本章小结

本章介绍了树和森林的概念、术语、表示方法及重要的算法。
对于树的概念,本书给出了两种等价的定义方式:
(1)从递归来看,树可以看做是由根结点和子树构成,而每一棵子树也是树。
(2)从二元组的逻辑结构来,树是结点的有穷集合以及在该集合上定义的一个二元关系。
在树的基本概念中,还给出了树及森林的相关概念,例如树叶、根结点、边、层数、度数、有序树等。这些概念基本上沿用二叉树一章中的相关术语。
树与二叉树、森林与二叉树之间存在一—对应的关系。本书介绍了树与二叉树和森林与二叉树相互转化的方法。
树和森林的周游是十分重要的运算。
按深度优先周游树和森林包括先根次序和后根次序周游。按先根次序周游森林等同于按前序法周游树或森林对应的二叉树,按后根次序周游森林等同于按中序法周游对应的二叉树。
广度优先周游树,是指从树的第零层(根结点)开始,自上至下逐层周游;在同一层中,按照从左到右的顺序对结点逐一进行访问,直到访问完最下一层的所有结点。
本章重点介绍了树的存储结构。联系到具体的应用,可以选择不同的存储方式。
常用的链式存储结构有“子结点表”、静态“左子/右兄”、动态表示法、“左子/右兄”二叉链表表示法和父指针表示法等。“左子/右兄"二叉链表是采用得最多的表示法。父指针表示法可以方便地实现并查集,常用于维护由一些不相交子集构成的集合,判断两个结点是否在同一个集合中以及归并两个集合。
顺序存储表示法把树中结点按照一定的顺序存储到一片连续的存储单元中。“带双标记的先根次序表示”、“带度数的后根次序表示”和“带双标记的层次次序表示”都是与树/森林遍历次序相关的顺序存储结构,根据存储方法的性质加入了必要的附加信息。
在本章的最后还介绍了K叉树的概念及性质。K 叉树是由一个根结点及K棵互不相交的K叉树构成。二叉树的许多性质可以推广到K叉树,通常用数组实现完全K叉树。