树
此文主要介绍了,树、二叉树、森林的基本定义、特点、存储结构、遍历等基础知识。
树的结构特点
- 结构特点
树是一个层次结构,“有且仅有一个根结点无前驱(第一层);有一或多个叶结点无后继;其余结点有唯一前驱和若干后继” 。
- 递归定义
树由根结点(唯一)及该根结点的若干(零或多个)“子树”组成。不含任何结点也是树,称为空树。
树空则各项操作易完成,否则可假设规模小的时候(子树、根)可以完成,进而可将对整棵树的操作递归分解成对根结点及其各子树的操作进行(分治)。
- 相关名词及定义
空树、根结点(可标识一颗树)、叶子结点、度(叶子结点的度为几)、终端结点、非终端(分支)结点、内部结点、结点的孩子/双亲/兄弟/祖先/子孙/堂兄弟、结点的层次(从1始)、 树的深度、宽度、无序树 有序树
森林:互不相交的树的集合
路径与路径长度,如A->D->J长为2
从根结点到任意结点存在唯一路径
上面是关于树的整体介绍,都是一些基本概念,下面介绍二叉树。
二叉树
二叉树是度不大于2的有序树(每个结点最多两棵子树,子树有左右之分),具体定义类树可得。
二叉树的几种遍历方式
- 先(根)序遍历
树非空则访问根结点,后递归地先序遍历其左子树;再递归地先序遍历其右子树;空则无操作。
- 中(根)序遍历
树非空则递归地中序遍历根左子树;后访问根结点,再递归地中序遍历右子树;空则无操作。
- 后(根)序遍历
树非空则递归地后序遍历根右子树;后递归地后序遍历右子树,再访问根结点;空则无操作。
- 层次遍历
由上到下,由左到右,不宜递归。
例子
已知树,求树的各种序
先序输出:1 2 3 4
中序输出:2 3 1 4
后序输出:3 2 4 1
层次输出:1 2 4 3
通过数的序列求树
二叉树先序序列为EBADCFHGIKJ,中序序列为ABCDEFGHIJK,画出该二叉树。
分析思路:
- 根据先序序列可知E为根,根据中序序列可知E左边的[ABCD]为E的左子树。
- 根据先序可知B为[ABCD]的根,根据中序可知A为B的左子树,[CD]在B的右子树上。
- 根据先序中为’DC’的顺序,可知D为C的根。
- 根据中序中为’CD’的顺序,可知C为D的左子树。
- 到此E左子树,便出来了。同样的道理下面分析右子树[FGHIJK]。
- [FGHIJK]在先序中F排在最前,故F为E的右子树的根。
- [FGHIJK]在中序中F排在最前,故[GHIJK]都在F的右子树的上。
- [GHIJK]在先序中H排在最前,故H为F的右子树的根。
- [GHIJK]在中序中G排在H前面,故G为H的左子树,[IJK]都在H的右子树的上。
- [IJK]根据先序可知I为根,根据中序可知[JK]在I的右子树。
- [JK]根据先序可知K为根,根据中序可知J在左子树。
表达式的二叉树表示
存储结构
- 二叉链表
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
- 顺序存储
//二叉树的静态分配顺序存储结构
#define MAX_TREE_SIZE 100
typedef TElemType SqBiTree[MAX_TREE_SIZE];
//零号元素存根结点
大部分情况下我们都是用的链式存储。顺序存储就是静态分配顺序存储结构,空间利用率低。
- 三叉链表
链式存储结构有二叉链表,三叉链表,线索链表等。
typedef struct TriTNode {
struct TriTNode *parent;
TElemType data;
struct TriTNode *lchild, *rchild;
} TriTNode, *TriTree;
二叉链表,求双亲结点慢。三叉链表,求双亲结点快但空间利用率低。两者均不易扩展到普通树。
为了更快速的找到二叉树的双亲,更快速的遍历二叉树,我们可以将二叉链表线索化,于是有了线索二叉树。
基本操作
- 求树深
思路:如果树为空树则深度为0,否则,先递归计算出左子树的深度,再计算出右子树的深度,最后,树的深度为两子树深度的最大值加1
int TreeDepth(BiTree T){//递归法求树的深度
if(T==NULL) d=0;
else{
d1=TreeDepth(T->lchild1);d2=TreeDepth(T->rchild);
if(d1>d2)d=d1+1;
else d=d2+1;
}
return d;
}
- 遍历二叉树
先(根)序遍历:树空无操作,非空则先访问根结点,后递归地先序遍历其左子树;再递归地先序遍历其右子树。
- 中序遍历的非递归描述
先思考递归方式:树空时无操作;树不空时先递归访问左子树,中间访问根节点,最后递归访问右子树
思考如何转化:何时入栈?何时出栈?入栈、出栈时作什么操作
总结:设指针p指向根结点:{如果p不空,入栈,之后p指向左孩子,开始下次循环;否则p空,出栈访问,令指针指向出栈元素的右孩子,开始下次循环}重复至p和栈空
Status InOrderTraverse_NonRecur(BiTree T, Status (*visit)(ElemType)){
SqStack S;
InitStack(S);
BiTNode* p = T;
while (p || !EmptyStack(S)) {
if (p) {
Push(S, p);
p = p->lchild;
}
else{
Pop(S, p);
if (!(*vist)(p->data))
return ERROR;
p = p->rchild;
}
}
return OK;
}
- 后续递归销毁二叉树
算法思路:树为空什么都不作,否则,先递归释放左子树,后递归释放右子树,最后释放根结点。
满二叉树与完全二叉树
- 满二叉树:设深度为k,含2k-1个结点的二叉树。结点数达最大
- 完全二叉树:设树中含n个结点, 它们和满二叉树中编号为1至n的结点位置上一一对应(编号规则为由上到下,从左到右)。相比满二叉树仅少“最后的”若干个,不能少中间的
- 叶子结点出现在最后2层或1层
- 对于任意结点,若其右分支下的子孙的最大层次为L,则左分支下的子孙的最大层次为L或L+1
二叉树的性质
- 在二叉树的第i层上最多有 2 i − 1 2^{i}-1 2i−1个结点(i≥1)
- 深度为K的二叉树最多有 2 K − 1 2^{K}-1 2K−1个结点(K≥1)
- 对于任意一棵二叉树BT,如果度为0的结点个数为 n 0 n_0 n0,度为2的结点个数为 n 2 n_2 n2,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1。
- 含n个结点的完全二叉树深度为 l o g 2 n 下取整 + 1 log_2^n\,\text{下取整}\,+1 log2n下取整+1。
- 若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中编号为 i 的结点:
- 若 i=1,则该结点是二叉树的根,无双亲,否则,编号为 i / 2 i/2 i/2下取整的结点为其双亲结点;
- 若 2i>n,则该结点无左孩子,否则,编号为 2i 的结点为其左孩子结点;
- 若 2i+1>n,则该结点无右孩子,否则,编号为2i+1 的结点为其右孩子结点。
线索二叉树
遍历二叉树时结点访问的先后顺序信息只有在遍历的动态过程中得到。若经常遍历或者要求取遍历时节点的前驱、后继信息则应修改二叉链表的结构以保存该遍历顺序信息(线索),加上线索的二叉树称线索二叉树。
分先序/中序/后序线索二叉树。
中序线索二叉树
方法:每个节点的基础上增设两个标记位, 标记位为0(Link)代表指针存储孩子信息;标记位为1(Thread)代表指针存储线索信息,若是左指针则指向前驱,右指针则指向后继。增设头结点,左标记为Link,左指针指根结点,右标记为Thread,右指针指向最后被访问的结点,树空则均指向头结点.又称双向线索链表。先写出遍历序列,后添加头结点,再据序列增加线索即可。
线索二叉树的链式存储称为线索链表
typedef enum {
Link, //0, 标记为0代表为指向孩子的指针
Thread //1, 标记为1代表为指向前驱后继的线索
} PointerTag; //指针标记类型
typedef struct BiThrNode {
TElemType data;
struct BiThrNode *lchild, *rchild; // 左右指针
PointerTag LTag, RTag; // 指针性质Link或Thread
} BiThrNode, *BiThrTree;
注:红蓝线为为线索,红线为前驱,蓝线为后继
由上图可以看出:中序线索二叉树,就是将树的每个结点的左右子树指针都利用起来了。
- 当无左子树,就让左指针指向前驱。
- 当无右子树,就让右指针指向后继。
- 同时增加两个标记为,分别标记左右指针,所表示的含义(子树or线索)。
中序线索化
- 方法一
网上找到的方法,以及各大参考资料,基本都是此方法的思路。
思路:
(1) 开辟头结点并赋初值
(2)遍历原二叉树, 修改标记值并增加线索信息.
(3) 最后一个结点再单独处理
template <typename T>
void BiThrTree<T>::InOrderThreading2() {
//_root为原二叉树,_thread指向新增的线索化头节点
if (!_thread) {
_thread = new NodeType;
}
_thread->lTag = Link;
_thread->lchild = _thread;
_thread->rTag = Thread;
_thread->rchild = _thread;
if (_root) {
_thread->lchild = _root;
PNodeType pre = _thread;
InThreading2(_root, pre);
pre->rTag = Thread;
pre->rchild = _thread;//处理最后一个结点
_thread->rchild = pre;
}
}//中序线索化
思路:
函数InThreading(BiThrTree p,BiThrTree &pre)用于遍历以p为根的树并添加前驱和后继线索信息. pre指向遍历时第一个访问结点的前驱.函数返回时pre指向树p中最后一个访问的结点。
template <typename T>
void BiThrTree<T>::InThreading(PNodeType p, PNodeType& pre) {
//递归求解。空时无操作。
//树非空则先递归地向左子树中添加线索信息(线索化作子树),后处理与根结点p相关的线索信息,最后递归的线索化右子树.
//注意处理左右子树时pre的取值
if (p) {
InThreading(p->lchild, pre); //线索化左子树
if (p->lchild == NULL) {
p->lTag = Thread;
p->lchild = pre;
}
else p->lTag = Link;
if (pre->rchild == NULL) {
pre->rTag = Thread;
pre->rchild = p;
}
else pre->rTag = Link;
pre = p;
InThreading(p->rchild, pre);//线索化右子树
}
}
- 方法二
由于个人感觉方法一,不太容易理解,所以另外实现了一种个人以为便于理解的实现思路。此方法我已经测试过,结果是正确的。
思路:
对于任一节点,为空直接返回,不为空按如下处理:
1、判断左子树是否为空,为空则标为线索,指向前驱;不为空则标为Link,线索化左子树,此时传入的前驱不变,后继为本节点。
2、判断右子树是否为空,为空则标为线索,指向后继;不为空则标为Link,线索化右子树,此时传入的前驱为本节点,后继不变。
template <typename T>
void BiThrTree<T>::InOrderThreading()
{
if (!_thread) {
_thread = new NodeType;
}
_thread->lTag = Link;
_thread->lchild = _thread;
_thread->rTag = Thread;
_thread->rchild = _thread;
if (_root) {
_thread->lchild = _root;
InThreading(_root, _thread, _thread);
}
}
/**
* 中序线索化二叉树。
* 思路:对于任一节点,为空直接返回,不为空按如下处理:
* 1、判断左子树是否为空,为空则标为线索,指向前驱;不为空则标为Link,线索化左子树,此时传入的前驱不变,后继为本节点。
* 2、判断右子树是否为空,为空则标为线索,指向后继;不为空则标为Link,线索化右子树,此时传入的前驱为本节点,后继不变。
*/
template <typename T>
void BiThrTree<T>::InThreading(PNodeType node, PNodeType preNode, PNodeType postNode)
{
if (node) {
if (NULL == node->lchild) {
node->lTag = Thread;
node->lchild = preNode;
}
else {
node->lTag = Link;
InThreading(node->lchild, preNode, node);
}
if (NULL == node->rchild) {
node->rTag = Thread;
node->rchild = postNode;
}
else {
node->rTag = Link;
InThreading(node->rchild, node, postNode);
}
}
}
中序遍历线索二叉链表
算法思路:
对于任意节点,只要不为头结点,就进入如下循环:
1、先循环找到第一个没有左子树的节点,访问此节点。
2、再循环判断右子树是否为线索,若是则指向线索,并访问此节点。
3、此时右子树不为线索,应该指向右子树,进入下一轮循环(即第1步)。
/**
* 中序遍历线索二叉树,模板实现。
*/
template <typename T>
Status BiThrTree<T>::InOrderTraverse_Thr(Status(*visit)(T e))
{
PNodeType p = this->_threat->lchild; // 指针指向中序遍历的第一个结点
while (p != this->_threat) {
while (Link == p->lTag) {
p = p->lchild;
}
(*visit)(p->data);
if (Link == p->rTag) {
p = p->rchild;
continue;
}
while (Thread == p->rTag && p->rchild != this->_threat) {
p = p->rchild;
(*visit)(p->data);
}
p = p->rchild;
}
return OK;
}// 复杂度也为O(n),但是没有递归,不需要栈
树和森林
树的存储结构
双亲表示法、孩子表示法、孩子兄弟表示法。
双亲表示法
以一组连续空间存储结点,各结点附设指示器指示其双亲结点的位置(数据域+双亲下标域)
typedef char TElemType;
#define MAX_TREE_SIZE 100
typedef struct PTNode {//结点结构
TElemType data;
int parent;//下标
} PTNode;
typedef struct {
PTNode nodes[MAX_TREE_SIZE];
int r, n; //根结点下标和结点个数
} PTree;
孩子表示法1—多重链表
结点中为其每个孩子附设一个指针.具体定义时各结点指针的个数可取最大,也可根据各自的度不同而不同.前者同构,实现简单;后者需动态开辟指针域,实现复杂但空间省
孩子表示法2—孩子(双亲)链表表示法
链式存储与顺序存储结合,将各结点存储在一个数组中,每个数组元素附加一指针域指向结点的孩子形成的链表。
typedef struct CTNode{
int Child; //孩子结点的下标
struct CTNode* next;//指下一孩子
}*ChildPtr;
typedef struct {
TElemType data;
int parent; ChildPtr firstchild;
} CTBox;
typedef struct {
CTBox nodes[MAX_TREE_SIZE];
int n, r;
} CTree;
注:若经常进行访问双亲结点的操作则可向数组元素追加双亲位置域
孩子-兄弟表示法
链式存储,每个结点包括数据域和两个指针域,左指针指向第一个孩子结点,右指针指向兄弟结点.又称二叉链表存储法。
树采用孩子兄弟表示法在内存中实际形成一个二叉链表,与二叉树的二叉链表存储结构同构,但对指针含义的解释不同
typedef struct CSNode{
TElemType data;
struct CSNode *firstchild, *nextsibling;
} CSNode, * CSTree;
森林的存储结构
森林的孩子兄弟表示法或二叉链表存储法
思路:单颗树的二叉链表存储结构中根结点的右指针必为空,若要存储多颗树组成的森林,可将后一颗树的根结点看成前一颗树根结点的兄弟,即将后一颗树对应的二叉链表拼接到前一颗树根结点的右指针上,这称为森林的孩子兄弟表示法或二叉链表存储法。
森林的该种表示法也形成一个二叉链表, 与树、二叉树的二叉链表存储结构同构,但指针含义注意区分
typedef struct CSNode{
TElemType data;
struct CSNode *firstchild, *nextsibling;
} CSNode, *CSTree;
树、森林与二叉树的转换
以二叉链表存储结构为转换依据,将左右指针所指结点理解为左右孩子结点则得到二叉树;将左指针所指结点理解为孩子,右指针所指结点理解为当前结点的兄弟则得树或森林
树和森林的遍历
树的遍历
树的先序遍历 == 其二叉链表存储的先序遍历
如上图:
树的先序为:ABHCEFGD
其二叉链表存储的先序为:ABHCEFGD
树采用二叉链表存储,对树进行遍历即对二叉链表进行遍历。先序遍历二叉链表(先根结点后左子树再右子树),对应到树上是“先根,后自左到右递归遍历各子树”,称为树的先根序遍历,代码与二叉树的先序遍历基本同,将lchild与rchild换作firstchild和nextsibling即可.写遍历序列也可直接根据先根规则.
树的后序遍历 == 其二叉链表存储的中序遍历
如上图:
树的后序为:HBEGFCDA
其二叉链表存储的中序为:HBEGFCDA
对二叉链表进行中序遍历(先左子树中根再右子树)对应到树上是“先从左到右各子树,后根”,称为树的后根序遍历,代码与二叉树的“中序”遍历基本同,惟lchild与rchild换作firstchild和nextsibling。也可直接根据后根规则写遍历序列。
树无中序遍历
森林的遍历
森林的先序遍历 == 其二叉链表存储的先序遍历
如上图:
森林的先序为:BEFKL,CG,DHIJ
其二叉链表存储的先序为:BEFKLCGDHIJ
森林采用二叉链表存储(孩子兄弟表示法),先序遍历二叉链表 (先根结点后左子树再右子树),对应到树上为“从左到右依次先根遍历各颗树”,称为森林的先序遍历,代码与二叉树的先序遍历基本同,惟定义中lchild与rchild换作firstchild和nextsibling。写遍历序列也可根据“先根序”规则
森林的中序遍历 == 其二叉链表存储的中序遍历
如上图:
森林的中序为:EKLFB,GC,HIJD
其二叉链表存储的中序为:EKLFBGCHIJD
森林采用二叉链表存储(孩子兄弟表示法),对二叉链表“先左子树中根再右子树”中序遍历,对应到森林为“从左到右依次后根遍历各颗树”,称为森林的中序遍历,代码与二叉树的中序遍历基本同,惟lchild与rchild换作firstchild和nextsibling。写遍历序列也可根据“后根序”规则
森林无后序遍历
由树的先根和后根遍历序列确定树
-
方法一(间接法,借助二叉链表)
树的先根序列对应二叉链表的先序序列、后根序列对应二叉链表的中序序列,由先序序列与中序序列可确定出二叉链表,再根据此二叉链表按照孩子兄弟表示法的含义可得此二叉链表对应的树 -
方法二(直接法,根据先根后根)
先根序列中第1个X一定是整颗树的根结点,而后根序列中唯有遍历完X的所有子树后方访问X,故X必然出现在最后;先根序列中第二个顶点必然是第一个子树的根结点,后根序列中该结点前的结点为此子树中各结点;除去第一颗子树中的结点后,先根序列中第一个结点必为第二颗子树的根结点,而后根序列中此结点前的结点构成第二颗子树,以此类推,最终可确定。(自己总结的性质:在树中为兄弟的任意两个节点,在先序和后序中它两者的顺序是相同的,且在后序中都在双亲之前)
树的先根序和后根序如下:
先根:A B E F C D G H I J K
后根:E F B C I J K H G D A
求出对应的树为:
由森林的先序和中序遍历序列确定森林
-
方法一(间接法,借助二叉链表)
森林的先序序列对应二叉链表的先序序列、中序序列对应二叉链表的中序序列,由先序序列与中序序列可确定出二叉链表,再根据此二叉链表按照孩子兄弟表示法的含义可得此二叉链表对应的森林 -
方法二(直接法,根据先根后根)
先序序列中第1个X一定是第一颗树的根结点,而中序序列中在遍历完第一颗树的最后访问X,故中序序列中X之前的结点构成森林的第一颗树,这些结点在先序和中序序列中的出现次序即为第一颗树的先根序列和后根序列,根据树的确定方法可得第一颗树;除去第一颗树的结点后,先序序列中余下的第一个结点为第二颗树的根结点,中序序列中该结点前结点的构成第二颗树,依次类推,最终可得森林
森林的先根序和后根序如下:
先序:BEFKLCGDHIJ
中序:EKLFBGCHIJD
求出对应的森林为:
求树或森林的深度的算法(类似思考求叶子数)
思路:树也是森林。对于森林而言,森林或者为空,或者分为两部分:第一颗树、其余树组成的子森林。求森林的深度可以递归,先递归求第一颗树的深度(子树森林深度加1),后递归求其余树所组成的森林的深度
int TorFDepth(CSTree F) {
//采用二叉链表存储结构
if(!F) return 0;
else {
h1 = TreeDepth( F->firstchild )+1;
h2 = TreeDepth( F->nextsibling);
return(max(h1, h2));
}
}
相关代码
这里我主要实现了二叉链表存储结构,对树和森林的存储用的是孩子兄弟表示法。
因为在对树的某些操作可以非递归的方式,这时候往往是要用到栈的,于是还有一部分栈操作的实现。
栈
stack_BiT.h
//针对树所建立的 栈 的相关操作
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef int Status;
typedef int TElemType;
typedef struct BiTNode{
TElemType data;
BiTNode*rchild,*lchild;
}BiTNode,*BiTree;
typedef BiTree SElemType;
typedef struct {SElemType*base;SElemType*top;int stacksize;}Stack;
void CopySElemType(SElemType&s,SElemType e)
{
s=e;
}
Status InitStack(Stack &S)
{
//建立一个空栈;
S.base=(SElemType*)malloc(sizeof(SElemType));
if(!S.base)exit(OVERFLOW);
S.top=S.base;S.stacksize=STACK_INIT_SIZE;return OK;
}
Status DestroyStack(Stack&S)
{
free(S.base);S.top=NULL;S.base=NULL;S.stacksize=0;return OK;
}
void ClearStack(Stack&S)
{
S.top=S.base;S.stacksize=STACK_INIT_SIZE;
}
Status EmptyStack(Stack S)
{
if(S.top-S.base)return FALSE;
return OK;
}
Status Push(Stack &S,SElemType e)
{
if(S.top-S.base==S.stacksize)
{
Stack SS;SS.top=(SElemType*)realloc(S.base,sizeof(S.stacksize+STACKINCREMENT));
if(!SS.base)exit(OVERFLOW);
free(S.base);S.base=SS.base;S.top=S.base+S.stacksize;S.stacksize+=STACKINCREMENT;
}
CopySElemType(*S.top,e);S.top++;return OK;
}
Status Pop(Stack &S,SElemType &e)
{
if(S.top==S.base)return FALSE;
CopySElemType(e,*(S.top-1));--S.top;
return OK;
}
Status GetTop(Stack S,SElemType&e)
{
if(S.top==S.base)return FALSE;
CopySElemType(e,*(S.top-1));return OK;
}
Status SetTop(Stack&S,SElemType e)
{
if(EmptyStack(S))return FALSE;
CopySElemType(*(S.top-1),e);return OK;
}
二叉链表
Tree.h
//二叉树的二叉链表存储结构
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#include"stack_BiT.h"
//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
int count=0;
Status InitBiTree(BiTree&T)
{
T=(BiTree)malloc(sizeof(BiTNode));
if(!T)exit(OVERFLOW);
T->lchild=NULL;T->rchild=NULL;return OK;
}
Status CreateBiTree(BiTree&T)
{
//次二叉树的创建顺序为 :先创建根节点,然后左子树,最后右子树;
TElemType e;scanf("%d",&e);
if(e)
{
T=(BiTree)malloc(sizeof(BiTNode));
if(!T)exit(OVERFLOW);
T->data=e;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
return OK;
}
else T=NULL;return OK;
}
Status PrintTree(TElemType e)
{
printf("%4d",e);return OK;
}
void PrintTElem(TElemType e)
{
printf("%4d",e);
}
Status DestroyBiTree(BiTree&T)
{
if(T)
{
DestroyBiTree(T->lchild);DestroyBiTree(T->rchild);
free(T);T=NULL;return OK;
}
else return OK;
}
/*
void DesBiT_FeiDiGui(BiTree &T)
{
//非递归的二叉树删除,有一点问题 。。。
Stack S,D;InitStack(S);InitStack(D);BiTNode*b=T;
while(b||!EmptyStack(S))
{
if(b){Push(S,b);b=b->lchild;}
else{
GetTop(S,b);b=b->rchild;
if(b)continue;
else {Pop(S,b);free(b);b->rchild=NULL;b->lchild=NULL;
if(!Pop(S,b))return OK;b->rchild=NULL;b->lchild=NULL;
}
}
}
}
*/
Status BiTreeEmpty(BiTree T)
{
//二叉树已经存在,判断树空;操作结果:空返回TRUE;不空返回FALSE;
if(T)return FALSE;
return TRUE;
}
int BiTreeDepth(BiTree T)
{
//二叉树已经存在。操作结果:返回二叉树的深度 ;
int r;
if(T)
{
int a,b;
a=BiTreeDepth(T->lchild);b=BiTreeDepth(T->rchild);
r=a>b?a:b;++r;
}
else r=0;
return r;
}
Status InsertChild(BiTree T, BiTree p,int LR,BiTree c)
{
//LR为0或1,插入右子树为空的二叉树c为T中p所指结点的左或右子树
//0为 lchild;1为rchild
BiTree a;
if(LR){
a=p->rchild;
p->rchild=c;
c->rchild=a;
}
else {
a=p->lchild;p->lchild=c;c->rchild=a;
}return OK;
}
Status DeleteChild(BiTree T,BiTree p,int LR)
{
// 删除结点p的左或右子树,注意p为指针类型。
BiTNode *a,*b;
if(LR){b=p->rchild;p->rchild=NULL; }else {b=p->lchild;p->lchild=NULL;}
DestroyBiTree(b);
return OK;
}
Status Assign(BiTree T,BiTree cur_e,TElemType value)
{
//给cur_e结点赋值
cur_e->data=value;return OK;
}
/*
Status Root(BiTree T)
Status Value(BiTree T, cur_e)
Status Parent(BiTree T, cur_e)
Status LeftChild(BiTree T,cur_e)返回T中cur_e结点的左孩子,作为左子树的根可代表左子树
Status RightChild(BiTree T,cur_e)求T中cur_e结点的右孩子,作为右子树的根可代表右子树
Status RightSibling(BiTree T, cur_e) 求cur_e结点的右兄弟
{
}*/
Status InOrderTraverse_NonRecur_1(BiTree T,Status (*visit)(TElemType))
{
//中序非递归遍历。
//思路 :只要树根节点不空,就将根节点入栈,指向左孩子,左子树作为根节点。
//否则将上一个根节点弹出,访问根节点的data;然后 指向右孩子,就将右孩子作为根节点,进行下一次循环;
//停止的条件是:当前节点空,并且栈为空;
Stack S;InitStack(S);SElemType p=T;
while(p||!EmptyStack(S)) {
while(p){
Push(S,p);p=p->lchild;
}
Pop(S,p);(*visit)(p->data);p=p->rchild;
}
return OK;
}
Status InOrderTraverse_NonRecur_2(BiTree T,Status (*visit)(TElemType))
{
//中序非递归遍历。
//当根节点不空时,将他的左孩子入栈,进行下一次循环。
//否则,当前空节点出栈, 访问栈顶元素,将右孩子入栈,
//结束条件:栈为空。
Stack S;InitStack(S);SElemType p=T;
if(!p){printf("\n空树。\n");return ERROR;}
else{
Push(S,p);
while(!EmptyStack(S)){
while(GetTop(S,p)&&p)Push(S,p->lchild);
Pop(S,p);
if(!EmptyStack(S)){
Pop(S,p);(*visit)(p->data);Push(S,p->rchild);
}
}
}
return OK;
}
Status PreOrderTraverse(BiTree T,Status (*Visit)(TElemType e))
{
//先序遍历
if(T)
{
if((*Visit)(T->data))
if(PreOrderTraverse(T->lchild,*Visit))
if(PreOrderTraverse(T->rchild,*Visit))return OK;
else return ERROR;
}
return OK;
}
Status InOrderTraverse(BiTree T,Status (*Visit)(TElemType e))
{
//中序遍历
if(T)
{
if(InOrderTraverse(T->lchild,*Visit))
if((*Visit)(T->data));
if(InOrderTraverse(T->rchild,*Visit))return OK;
else return ERROR;
}
else return OK;
}
Status PostOrderTraverse(BiTree T,Status (*Visit)(TElemType e))
{
//后序遍历
if(T)
{
if(PostOrderTraverse(T->lchild,*Visit))
if(PostOrderTraverse(T->rchild,*Visit))
if((*Visit)(T->data))return OK;
else return ERROR;
}
else return OK;
}
Status LevelOrderTraverse(BiTree T,Status(*Visit)(TElemType e))
{
//层次遍历
}
/*
Status Traverse(BiTree T,Status(*f)(BiTree,BiTree),BiTree&e)
{
//遍历二叉树 ,找到第一个满足条件f的节点,用e吧节点地址带回;
Stack S;InitStack(S);BiTNode*p=T;
while(p||!EmptyStack(S))
{
if(p){
Push(S,p);p=p->lchild;
}
else {
Pop(S,p);if((*f)(p,))
}
}
}*/
Status CopyBiTree(BiTree T,BiTree &X)
{
//复制树T得到树X,T保持不变
if(T){
X=(BiTree)malloc(sizeof(BiTNode));
if(!X)exit(OVERFLOW);
X->data=T->data;
CopyBiTree(T->lchild,X->lchild);CopyBiTree(T->rchild,X->rchild);
}
else X=NULL;
return OK;
}
Status LocateNode(BiTree T, TElemType x,BiTree &p)
{
//在树T中查找(按先序)第一个结点值等于x的结点,若找到则函数返回TRUE,
//p带回该结点的地址;若找不到则函数返回FALSE ,p赋值为NULL
if(T)
{
if(T->data==x){p=T;return OK;}
else {
if(LocateNode(T->lchild,x,p))return OK;
else if(LocateNode(T->rchild,x,p))return OK;
else {p=NULL;return ERROR;}
}
}
else p=NULL;return ERROR;
}
Status LocateParent(BiTree T,TElemType x,BiTree &parent_p,int &LR)
{
//已知树T中存在某结点值等于x,定位到该结点的双亲结点,若双亲存在(说明x结点非根结点)则parent_p带回双亲位置,
//flag为代表是双亲的左孩子,为代表是双亲的右孩子,同时函数返回TRUE;否则函数返回FALSE
}
Status DeleteChild(BiTree &T,TElemType x)
{
//删除树T中以x为根结点的子树,若存在x结点则删除成功后返回OK,若不存在x结点返回ERROR;
Stack S,D;InitStack(S);InitStack(D);SElemType e;BiTNode* p=T,*q;
while(p||!EmptyStack(S))
{
if(p->data==x)
while(p||!EmptyStack(D))
{
if(p&&p->lchild&&p->rchild){
Push(D,p);p=p->lchild;
}
}
}
}
Status ChangeLR(BiTree T)
{
if(T)
{
BiTNode*c=T->rchild;T->rchild=T->lchild;T->lchild=c;
ChangeLR(T->lchild);ChangeLR(T->rchild);
}
return OK;
}
int LeafCount(BiTree T)
{
//计算二叉树T 的叶子树;并返回
int r;
if(T) r=0;
else if(!T&&!T->rchild&&!T->lchild)r=1;
else r=LeafCount(T->lchild)+LeafCount(T->rchild);
return r;
}
int BiTNodeCount(BiTree T)
{
//返回二叉树的节点数
int r;
if(T) {
r=BiTNodeCount(T->lchild)+BiTNodeCount(T->rchild)+1;
}
else r=0;
return r;
}
void PrintBiTree(BiTree T)
{
//凹式输出一个二叉树;采用全局变量的方法count为全局变量;初值为0;
BiTNode *r=T;
if(r)
{
for(int i=0;i<count;i++)printf(" ");//打印空格数为count次。
PrintTElem(r->data);printf("\n");count++;
PrintBiTree(r->lchild);
PrintBiTree(r->rchild);count--;
}
// else{for(int i=0;i<count;i++)printf(" "); printf("%4d\n",0); }
}
树和森林–孩子兄弟表示法
CSTree.h
//孩子兄弟表示法
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#include"Tree.h"
//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef struct CSNode{
TElemType data;
struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;
Status BiTreetoTreeorForest(BiTree BiT,CSTree &T)
{
//根据已经存在的二叉树BiT转换得到孩子兄弟法表示的树或者森林T,
//原二叉树BiT保持不变。注意思考何时得到树,何时得到森林
if(!BiT)T=NULL;
else{
T=(CSTree)malloc(sizeof(CSNode));if(!T)exit(OVERFLOW);
T->data=BiT->data;
BiTreetoTreeorForest(BiT->lchild,T->firstchild);
BiTreetoTreeorForest(BiT->rchild,T->nextsibling);
}
return OK;
}
Status PostRootTraverse(CSTree T,Status (*visit)(TElemType)){
//后根序遍历树T(对森林则是中序遍历),相当于中序遍历二叉链表存储结构
if(T){
PostRootTraverse(T->firstchild,(*visit));
(*visit)(T->data);
PostRootTraverse(T->nextsibling,(*visit));
}
return OK;
}
int TreeorForestDepth(CSTree T)
{
//求树或森林的深度
int h1,h2;
if(!T)return 0;
else {
h1=TreeorForestDepth(T->firstchild)+1;
h2=TreeorForestDepth(T->nextsibling);
return h1>h2?h1:h2;
}
}
int TreeorForestLeafCount(CSTree T)
{
//求树或森林的叶子数
//思路:节点为空,返回零;
//不空时,判断firstchild:若为空,返回 1+右兄弟叶子数;否则,返回孩子+兄弟叶子数
if(!T)return 0;
else if(!T->firstchild)return 1+TreeorForestLeafCount(T->nextsibling);
else return TreeorForestLeafCount(T->firstchild)+TreeorForestLeafCount(T->nextsibling);
}
void PrintBiTree(BiTree T,int level){
//仿照题集题凹式打印树的形式打印二叉树
//注意是逐行打印,采用先序,凹入深度由结点所在层次控制,根结点位于第1层,故最初level为1
if(T){
for(int i=1;i<=level;i++)printf(" ");
PrintTElem(T->data);printf("\n");
PrintBiTree(T->lchild,level+1);
PrintBiTree(T->rchild,level+1);
}
}
void PrintTree(CSTree T,int level){
//按题集题凹式打印树
//注意是逐行打印,采用先根序,凹入深度由结点所在层次控制,根结点位于第1层,故最初level为1
if(T){
for(int i=0;i<level;i++)printf(" ");
PrintTElem(T->data);printf("\n");
PrintTree(T->firstchild,level+1);
PrintTree(T->nextsibling,level);//因为是孩子兄弟表示法,所以不能加 1。
}
}
测试代码
main.cpp
#include"CSTree.h"
int main()
{
BiTree T;CSTree F;
CreateBiTree(T);
printf("先序输出为:\n");
PreOrderTraverse(T,PrintTree);
printf("\n中序输出为:\n");
PostOrderTraverse(T,PrintTree);
printf("\n二叉链表树的凹式输出:\n");
PrintBiTree(T);
DeleteChild(T,T,0);
printf("\n删除根节点的左子树后的二叉链表树为:\n");
PrintBiTree(T);
printf("\孩子兄弟表示法:\n");
BiTreetoTreeorForest(T,F);
PrintTree(F,1);
printf("\n叶子节点数为:%d\n",TreeorForestLeafCount(F));
return 0;
}
结语
后面还会讲关于霍夫曼树的实现,以及基于霍夫曼编码的文件压缩的实现。
以上代码,均为作者自己编写,有错之处,还望多多指教。