05 树
定义
-
度:结点拥有的子树数称为结点的度(Degree)
-
孩子:结点的子树的根称为该结点的孩子(Child),相应的,该结点称为孩子的双亲(Parent)
-
兄弟:同一个双亲的孩子之间互称兄弟(Sibling)
-
结点的祖先:从根到该结点所经分支上的所有结点
-
子孙:以某结点为根的子树中的任一结点都称为该结点的子孙
-
结点的层次:从根开始定义,根为第一层,根的孩子为第二层
-
堂兄弟:双亲在同一层的结点互为堂兄弟
-
深度:树中结点的最大层次称为树的深度(Depth)或高度
-
有序树:如果将树中结点的各子树看成从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。
-
森林:森林(Forest)是m(m>=0)棵互不相交的树的集合
ADT 树(Tree) Data 树是由一个根结点和若干棵子树构成。树中结点具有相同数据类型及层次关系。 Operation InitTree(*T):构造空树T. DestroyTree(*T):销毁树T. CreateTree(*T,definition):按difinition中给出树的定义来构造树。 ClearTree(*T):若树T存在,则将树T清为空树。 TreeEmpty(T):若T为空树,返回true,否则返回false. TreeDepth(T):返回T的深度。 Root(T):返回T的根结点。 Value(T,cur_e):cur_e是树T中的一个结点,返回此结点的值。 Assign(T,cur_e,value):给树T的结点cur_e赋值为value. Parent(T,cur_e):若cur_e是树的非根结点,则返回它的双亲,否则返回空。 LeftChild(T,cur_e):若cur_e是树的非叶结点,则返回它的最左孩子,否则返回空。 RightSibling(T,cur_e):若cur_e有右兄弟,则返回它的右兄弟,否则返回空。 InsertChild(*T,*p,i,c):其中p指向树的某个结点,i为所指结点p的度加上1,非空树c与T不相交,操作结果为插入c为树T中p指结点的第i棵子树。 DeleteChild(*T,*p,i):其中p指向树T的某个结点,i为所指结点的p的度,操作结果为删除T中p所指结点的第i棵子树。 endADT
存储结构
- 双亲表示法
我们假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指示其双亲结点在数组中的位置
- 孩子表示法
把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
- 孩子兄弟表示法
任意一棵树,它的节点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
二叉树
- 定义
二叉树(Binary Tree)是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
-
特点
-
特殊二叉树
- 斜树:
所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
- 满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
- 完全二叉树
对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
-
5个性质
- 二叉树性质1
在二叉树的第i层上至多有2i-1个结点(i>=1)
- 二叉树性质2
深度为k的二叉树至多有2k-1个结点(k>=1)
- 二叉树性质3
对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1
- 二叉树性质4
具有n个结点的完全二叉树的深度为[log2n] +1([x]表示不大于x的最大整数)
- 二叉树性质5
如果一棵有n个结点的完全二叉树(其深度为[log2n] + 1)的结点按层序编号(从第1层到第[log2n]+1层,每层从左到右),对任一结点i(1<=i<=n)有:
1 . 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]。
2 . 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
3 . 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。 -
顺序存储结构
顺序存储结构一般只用于完全二叉树 -
链式存储结构(二叉链表)
二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是比较自然的想法,我们称这样的链表叫做二叉链表。
typedef struct BiTNode /* 结点结构 */ { TElemType data; /* 结点数据 */ struct BiTNode *lchild,*rchild; /* 左右孩子指针 */ }BiTNode,*BiTree;
-
遍历二叉树
-
原理
二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问依次且仅被访问一次
-
二叉树遍历的方法
-
前序遍历
规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。ABDGHCEIF
-
中序遍历
规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。GDHBAEICF
-
后序遍历
规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。GHDBIEFCA
-
-
前序遍历算法
/* 初始条件: 二叉树T存在 */ /* 操作结果: 前序递归遍历T */ void PreOrderTraverse(BiTree T) { if(T==NULL) return; printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */ PreOrderTraverse(T->lchild); /* 再先序遍历左子树 */ PreOrderTraverse(T->rchild); /* 最后先序遍历右子树 */ }
-
中序遍历算法
/* 初始条件: 二叉树T存在 */ /* 操作结果: 中序递归遍历T */ void InOrderTraverse(BiTree T) { if(T==NULL) return; InOrderTraverse(T->lchild); /* 中序遍历左子树 */ printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */ InOrderTraverse(T->rchild); /* 最后中序遍历右子树 */ }
-
后序遍历算法
/* 初始条件: 二叉树T存在 */ /* 操作结果: 后序递归遍历T */ void PostOrderTraverse(BiTree T) { if(T==NULL) return; PostOrderTraverse(T->lchild); /* 先后序遍历左子树 */ PostOrderTraverse(T->rchild); /* 再后序遍历右子树 */ printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */ }
-
推导遍历结构
- 已知前序遍历序列和中序遍历序列,可以唯一确定一棵二叉树
- 已知后序遍历序列和中序遍历序列,可以唯一确定一棵二叉树
- 注意:已知前序和后序遍历,是不能确定一棵二叉树的。
-
-
二叉树的建立
和遍历二叉树差不多,以下是使用前序遍历的方式来创建二叉树/* 按前序输入二叉树中结点的值(一个字符) */ /* #表示空树,构造二叉链表表示二叉树T。 */ void CreateBiTree(BiTree *T) { TElemType ch; /* scanf("%c",&ch); */ ch=str[index++]; if(ch=='#') *T=NULL; else { *T=(BiTree)malloc(sizeof(BiTNode)); if(!*T) exit(OVERFLOW); (*T)->data=ch; /* 生成根结点 */ CreateBiTree(&(*T)->lchild); /* 构造左子树 */ CreateBiTree(&(*T)->rchild); /* 构造右子树 */ } }
-
线索二叉树
-
原理
指向前驱和后继的指针称为线索,加上线索的二叉树链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree),其实线索二叉树,等于是把一棵二叉树变为一个双向链表。
-
线索二叉树结构实现
typedef enum {Link,Thread} PointerTag; /* Link==0表示指向左右孩子指针, */ /* Thread==1表示指向前驱或后继的线索 */ typedef struct BiThrNode /* 二叉线索存储结点结构 */ { TElemType data; /* 结点数据 */ struct BiThrNode *lchild, *rchild; /* 左右孩子指针 */ PointerTag LTag; PointerTag RTag; /* 左右标志 */ } BiThrNode, *BiThrTree;
-
中序遍历线索化的递归函数代码如下
BiThrTree pre; /* 全局变量,始终指向刚刚访问过的结点 */ /* 中序遍历进行中序线索化 */ void InThreading(BiThrTree p) { if(p) { InThreading(p->lchild); /* 递归左子树线索化 */ if(!p->lchild) /* 没有左孩子 */ { p->LTag=Thread; /* 前驱线索 */ p->lchild=pre; /* 左孩子指针指向前驱 */ } if(!pre->rchild) /* 前驱没有右孩子 */ { pre->RTag=Thread; /* 后继线索 */ pre->rchild=p; /* 前驱右孩子指针指向后继(当前结点p) */ } pre=p; /* 保持pre指向p的前驱 */ InThreading(p->rchild); /* 递归右子树线索化 */ } }
-
遍历代码:
/* 中序遍历二叉线索树T(头结点)的非递归算法 */ Status InOrderTraverse_Thr(BiThrTree T) { BiThrTree p; p=T->lchild; /* p指向根结点 */ while(p!=T) { /* 空树或遍历结束时,p==T */ while(p->LTag==Link) p=p->lchild; if(!visit(p->data)) /* 访问其左子树为空的结点 */ return ERROR; while(p->RTag==Thread&&p->rchild!=T) { p=p->rchild; visit(p->data); /* 访问后继结点 */ } p=p->rchild; } return OK; }
-
-
树、森林与二叉树的转换
-
树转为二叉树
步骤
1 . 加线。在所有兄弟结点之间加一条连线
2 . 去线。对树中每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线。
3 . 层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子。 -
森林转为二叉树
步骤
1 . 把每个树转换成为二叉树
2 . 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连起来。当所有的二叉树连接起来后就得到了由森林转换来的二叉树。 -
二叉树转为树
步骤
1 . 加线。若某结点的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点。。。。反正就是左孩子的n个右孩子结点都作为此结点的孩子。将该结点与这些右孩子结点用线连接起来。
2 . 去线。删除原二叉树中所有结点与其右孩子结点的连线
3 . 层次调整。使之结构层次分明。 -
二叉树转换为森林
判断一棵二叉树能够转换成一棵树还是森林,标准很简单,那就是只要看这棵二叉树的根结点有没有右孩子,有就是森林,没有就是一个树。步骤
1 . 从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,若右孩子存在,则连线删除。。。,直到所有右孩子连线都删除为止,得到分离的二叉树。
2 . 再将每棵分离后的二叉树转换为树即可。
-