数据结构
–树和二叉树
一.一些概念
- 树的定义
树是有n(n≥0)个结点的有限集合。
如果n=0,称为空树;
如果n>0,称为非空树,对于非空树,有且仅有一个特定的称为根(Root)的节点(无直接前驱);
如果n>1,则除根以外的其它结点划分为 m (m>0)个互不相交的有限集 T1, T2 ,…, Tm,其中每个集合本身又是一棵树,并且称为根的子树(SubTree)。
每个结点都有唯一的直接前驱,但可能有多个后继.(一对多) - 结点:包含一个数据元素及若干指向其子树的分支.(A结点)
- 结点的度:结点拥有的子树数.(A结点的度为3)
- 叶结点:度为0的结点[没有子树的结点].(K结点)
- 分支结点:度不为0的结点[包括根结点],也称为非终端结点。除根外称为内部结点.
- 孩子:结点的子树的根[直接后继,可能有多个].(A的孩子为BCD)
- 双亲:孩子的直接前驱[最多只能有一个].(B的双亲为A)
- 兄弟:同一双亲的孩子.(B的兄弟为CD)
- 子孙:以某结点为根的树中的所有结点.(B的子孙为EFKL)
- 祖先:从根到该结点所经分支上的所有结点.(C的祖先为BA)
- 层次:根结点为第一层,其孩子为第二层,依此类推.(B的层次为2)
- 深度:树中结点的最大层次.(该树的深度为4)
- 有序树:子树之间存在确定的次序关系.
- 无序树:子树之间不存在确定的次序关系.
- 森林:互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。
任何一棵非空树是一个二元组
Tree = (root,F)
其中:root 被称为根结点, F被称为子树森林
二.二叉树(Binary Tree)
二叉树是一种特殊的树,每个结点最多有2棵子树,其子树有左右之分。
- 在二叉树的第i层上至多有2i-1个结点.
- 深度为k的二叉树至多有2k-1个结点.
- 如果二叉树终端结点数为n0,度为2的结点数为n2,则n0=n2+1.
三.满二叉树
一个深度为k且有2k-1个结点的二叉树,每层上的结点数都是最大数,可以自上而下、自左至右连续编号。

四.完全二叉树
当且仅当每一个结点都与深度相同的满二叉树中编号从1到n的结点一一对应的二叉树,其叶子结点只在最大两层上出现,左子树深度与右子树深度相等或大1。(满二叉树可以看作是完全二叉树的特殊状态,完全二叉树是满二叉树“未满”的状态)

- 具有n个结点的完全二叉树,其深度为[log2n] +1.
- 在完全二叉树中,结点i的双亲为 i/2, 结点i的左孩子LCHILD(i)=2i, 结点i的右孩子RCHILD(i)=2i+1(结点从1开始,从0为(i-1)/2, 2i+1, 2i+2)
五.二叉树的存储结构
- 顺序存储结构
用一组连续的存储单元依次自上而下, 自左至右存储结点空结点用0或#表示,缺点是浪费空间。
顺序表示为1 2 3 4 0 5 6 7 8 0 0 9 10
#include<iostream>
#define MAX_TREE_SIZE 100
using namespace std;
class BiTree{
private:
char *Tree;
int len;
public:
BiTree(){ Tree = new int[MAX_TREE_SIZE]; len=0; } //用数组存储即可
void CreateTree(int *t, int l){ Tree = t; len = l; }
};
- 链序存储结构
采用数据域加上左、右孩子指针。


#include<iostream>
using namespace std;
class BiNode{
char data;
BiNode *lchild;
BiNode *rchild;
public:
BiNode():lchild(NULL),rchild(NULL){}
BiNode(char e):data(e), lchild(NULL),rchild(NULL){}
friend class BiTree;
};
class BiTree{
private:
BiNode *Root;
void Create(BiNode *&t){
char c;
cin >> c;
if(c!='0'){
t = new BiNode(c);
Create(t->lchild);
Create(t->rchild);
}else
t=NULL;
}
public:
BiTree():Root(NULL){}
void Create(){ Create(Root); }
};
- 三叉链表
采用数据域加上左、右孩子指针及双亲指针。


六.遍历二叉树
树的遍历就是按某种次序访问树中的结点,要求每个结点访问一次且仅访问一次(非线性结构线性化)。
- 层次遍历输出(先上后下):队列实现
遍历结果:ABCDEFG
private:
void LevelOrder(BiNode *t){
queue<BiNode*> tq;
BiNode* p = t;
tq.push(Root);
while(!tq.empty()){
p = tq.front();
tq.pop();
cout << p->data;
if(p->lchild)
tq.push(p->lchild);
if(p->rchild)
tq.push(p->rchild);
}
}
public:
void LevelOrder(){ LevelOrder(Root); }
- 先左子树后右子树
- 先序遍历(DLR)
若二叉树为空,则返回;否则:1> 访问根结点(D) 2> 先序遍历左子树(L) 3> 先序遍历右子树(R)
遍历结果:ABDEGCF
class BiTree{
private:
int *Tree;
int len;
void PreOrderTraverse(BiNode *t){
if (t) {
cout << t->GetNode() << " ";
PreOrderTraverse(t->GetLChild());
PreOrderTraverse(t->GetRChild());
}
}
public:
BiTree();
void PreOrderTraverse(){ PreOrderTraverse(Root); }
};
- 中序遍历(LDR)
若二叉树为空,则返回;否则:1> 中序遍历左子树(L) 2> 访问根结点(D) 3> 中序遍历右子树(R)

遍历结果:DBGEAFC
class BiTree{
private:
int *Tree;
int len;
void MidOrderTraverse(BiNode *t){
if(t){
MidOrderTraverse(t->GetLChild());
cout << t->GetNode();
MidOrderTraverse(t->GetRChild());
}
}
public:
BiTree();
void MidOrderTraverse(){ MidOrderTraverse(Root); }
};
- 后序遍历(LRD)
若二叉树为空,则返回;否则:1> 后序遍历左子树(L) 2> 后序遍历右子树(R) 3> 访问根结点(D)

遍历结果:DGEBFCA
class BiTree{
private:
int *Tree;
int len;
void PostOrderTraverse(BiNode *t){
if(t){
PostOrderTraverse(t->GetLChild());
PostOrderTraverse(t->GetRChild());
cout << t->GetNode();
}
}
public:
BiTree();
void PostOrderTraverse(){ PostOrderTraverse(Root); }
};
- 先右子树后左子树
七.先序遍历创建二叉树

遍历结果:AB#C##D##
class BiTree{
private:
BiNode *Root;
void Create(BiNode *&t){
char c;
cin >> c;
if(c!='#'){
t = new BiNode(c);
Create(t->lchild);
Create(t->rchild);
}else
t=NULL;
}
public:
BiTree():Root(NULL){}
void Create(){ Create(Root); }
};
八.应用
- 表达式
(1)先序遍历得到前缀表达式:波兰式:- x + a b c / d e
(2)中序遍历得到中缀表达式:(a+b)xc-d/e
(3)后序遍历得到后缀表达式:逆波兰式:a b + c x d e / - - 根据给出的两个遍历顺序创建二叉树
例如:给出先序遍历和中序遍历,有:
先序序列:根 左子树 右子树; 中序序列:左子树 根 右子树
先序序列:a b c d e f g; 中序序列:c b d a e g f
九.线索二叉树
储存结点先后关系的信息(某个结点在序列中的前驱和后继等信息)。
- 方式
(1)增加新指针:在每个结点中,增加前驱(fwd)和后继指针(bkwd)
(2)利用空指针:在每个结点中增加两个标记位(LTag和RTag)
LTag=0, lChild域指示结点的左孩子;
LTag=1, lChild域指示结点的前驱结点;
RTag=0, lChild域指示结点的右孩子;
RTag=1, lChild域指示结点的后继结点。
- 在有n个结点的二叉树中,必定存在n+1个空链域。
- 因为每个结点有两个链域(左、右孩子指针),因此共有2n个链域。
- 除根结点外,每个结点都有且仅有一个分支相连,即n-1个链域被使用。
- 线索链表:以这种结点结构构成的二叉链表作为二叉树的存储结构。
- 线索:指向结点前驱和后继的指针。
- 线索二叉树(Threaded Binary Tree):加上线索的二叉树。
- 线索化:对二叉树以某种次序遍历使其变为线索二叉树的过程。
- 建立线索二叉树:
十.树和森林
- 树的存储结构
(1)双亲表示法
采用一组连续的存储空间;由于每个结点只有一个双亲,只需要一个指针。
(2)孩子表示法
可以采用多重链表,即每个结点有多个指针;最大的缺点是空链域太多(d-1)n+1个。
(3)孩子兄弟表示法
采用二叉链表;左指针指向第一个孩子,右指针指向兄弟。 - 树与二叉树的对应关系
树与二叉树都可以采用二叉链表作存储结构。任意给定一棵树,可以找到一个唯一的二叉树(没有右子树)。 - 森林与二叉树的对应关系
如果把森林中的第二棵树的根结点看作是第一棵树的根结点的兄弟,则可找到一个唯一的二叉树与之对应。 - 树的遍历
(1)先根(次序)遍历–二叉树的先序遍历
当树非空时,访问根结点,再依次先根遍历各棵子树。
遍历结果:ABEFCDG
(2)后根(次序)遍历–二叉树的中序遍历
当树非空时,依次后根遍历各棵子树,再访问根结点。
遍历结果:EFBCGDA
- 森林的遍历
(1)先序遍历–二叉树的先序遍历
若森林不空,则
1> 访问森林中第一棵树的根结点;
2> 先序遍历森林中第一棵树的子树森林;
3> 先序遍历森林中(除第一棵树之外)其余树构成的森林。
即:依次从左至右对森林中的每一棵树进行先根遍历.
遍历结果:ABCDEFGHIJK
(2)中序遍历–二叉树的中序遍历
若森林不空,则
1> 中序遍历森林中第一棵树的子树森林;
2> 访问森林中第一棵树的根结点;
3> 中序遍历森林中(除第一棵树之外)其余树构成的森林。
即:依次从左至右对森林中的每一棵树进行后根遍历。
遍历结果:BCEDAGFKIJH