目录
树
1.树的定义(树结构中的结点存在“一对多”的关系。)
树是n(n>=0)个结点的有限集。
若n=0,称为空树;
若n>0,则它满足如下两个条件:
(1)有且仅有一个特定的称为根(ROOT)的结点;
(2)其余结点可分为m(m>=0)个互不相交的有限集T1,T2,T3,....,
Tm其中每一个集合本身又是一棵树,并称为根的子树。
2.树的基本术语
根结点:非空树中无前驱结点的结点(无双亲)。
结点的度:结点拥有的子树数。
树的度:就是树内各结点的度的最大值。
终端结点:结点的度为0的结点,又称叶子(无孩子)。
非终端结点:度不为0的分支结点。
内部结点:根结点以外的分支结点。
结点的子树的根称为该结点的孩子,该结点称为孩子的双亲。
结点的祖先:从根到该结点所经分支上的所有结点。
结点的子孙:以某结点为根的子树中的任一结点。
堂兄弟:双亲在同一层的结点。
兄弟:拥有相同的双亲并且在同一层的结点。
树的深度:树中结点的最大层次。
有序树:树中结点的各子树从左至右有次序(最左边的为第一个孩子)。
无序树:树中结点的各子树无次序。
森林:是m(m>=0)棵互不相交的树的集合。
把根结点删除树就成为森林。
一棵树可以看成是一个特殊的森林。 树一定是森林,森林不一定是树。
给森林中的各子树加上一个双亲结点,森林就变成了树。
二叉树(二叉树不是树的特殊情况)
3.二叉树的定义
二叉树是n(n>=0)个结点的有限集,它或者是空集(n=0),或者由一个根结点及两棵互不相交的分别称作这个根的左子树和右子树的二叉树组成。
4.二叉树的特点
1.每个结点最多有俩孩子(二叉树中不存在度大于2的结点)。
2.子树有左右之分,其次序不能颠倒。
3.二叉树可以是空集合,根可以有空的左子树或空的右子树。
二叉树和树是两个概念,他们最大的区别是二叉树即使只有一棵子树也是要说明左子树或者是右子树,而树就不用区别。
5.二叉树的抽象数据类型定义
ADT Binary Tree{
数据对象D:D是具有相同特性的数据元素的集合。
数据关系R:若D=空集,则R=空集;
若D!=空集,则R={H};H是如下二元关系:
1.root唯一//关于根的说明
2.Dj并Dk=空集 //关于子树不相交的说明
3.......//关于数据元素的说明
4......//关于左子树和右子树的说明
基本操作P://至少有20个
}ADT Binary Tree
6.二叉树的性质
性质1:在二叉树的第i层上至多有2^i-1个结点(i>=1)。
性质2:深度为k的二叉树至多有2^k -1个结点(k>=1)。
性质3:对任何一棵二叉树T,如果其叶子数为n0,度为2的结点数为n2,则n0=n2+1。
证明:分支数B=n1+2*n2,而B=n-1,联立n=n0+n1+n2,得:
n0+n1+n2-1=n1+2*n2,故证明结果为n0=n2+1
性质4:具有n个结点的完全二叉树的深度为[log2 n]+1。
性质5:如果有对一棵有n个结点的完全二叉树(深度为[log2 n]+1)的结点按层序编号(从第1层到第[log2 n]+1层,每层从左到右),则对任一结点i(1<=i<=n),有:
满二叉树
满二叉树就是一棵深度为k且有2^k -1个结点的二叉树。
特点:1.每一层上的结点数都是最大结点数(即每层都满)。
2.叶子结点全部在最底层。
3.满二叉树在同样深度的二叉树中结点个数最多。
4.满二叉树在同样深度的二叉树中叶子结点个数最多。
对满二叉树结点位置进行编号
编号规则:从根结点开始,自上而下,自左而右。
每一结点位置都有元素。
完全二叉树
完全二叉树就是深度为k的具有n个结点的二叉树,当且仅当其每一个结点都与深度我k的满二叉树中编号为1-n的结点一一对应的时侯。
注意:在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即变成一棵完全二叉树。满二叉树一定是完全二叉树,而完全二叉树不一定为满二叉树。
特点:1.叶子只可能分布在层次最大的两层上。
2.对任一结点,如果其右子树的最大层次为i,则其左子树的最大层次必为i或i+1.
7.二叉树的顺序存储结构
实现:按满二叉树的结点层次编号,依次存放二叉树在的数据元素。
//二叉树顺序存储表示
#define MAXTSIZE 100
Typedef TElem Type SqBiTree[MAXTSIZE]
SqBiTree(Binary Tree) bt;
特点:结点间关系蕴含在其存储位置中浪费空间,适用存满二叉树和完全二叉树。
缺点:当深度为k的且只有k个结点的单只树需要长度为2^k -1的一维数组。
8.二叉树的链式存储结构
注意:在n个结点的二叉链表中,有n+1个空指针域。
定义格式:
typedef struct BiNode{
TElem Type data;
struct BiNode *lchild,*rchild;//左右孩子指针
}BiNode,*BiTree;
8.1二叉链表的结点的插入
(1)先定义一个二叉树节点:
template<typename T>
class treenode{
public:
T ele;
treenode<T>*left;
treenode<T>*right;
treenode(){left=NULL;
right=NULL;}
treenode(T ele){
this->ele=ele;
left=NULL;
right=NULL;}
};
(2)再进行二叉树的插入以创建一个二叉树:
若二叉树为空,直接创建根节点保存数据即可;若不为空,当新元素小于父节点时,根据二叉搜索树特点,需要作为父节点左子树,反之作为右子树:
bool insert(T ele){
if(root==NULL) root=new treenode<T>(ele);
else{
treenode<T>*parent=NULL;
treenode<T>*p=root;
while(p!=NULL){
if(ele<p->ele){parent=p;
p=p->left;}//新元素小于父节点,则做父节点左孩纸
else if(ele>p->ele){parent=p;p=p->right;}//新元素大于父节点,则做父节点右边孩纸
else return false;}
if(ele<parent->ele)parent->left=new treenode<T>(ele);
else parent->right=new treenode<T>(ele);
}
size++;
return true;}
8.2二叉链表的结点的删除
总体思想:分多种情况讨论
1.被删除节点没有子树的情况,直接删除,并修改对应父节点的指针为空。
2.对于只有一个子树的情况,考虑将其子树作为其父节点的子树,关于是左还是右,根据被删除的节点确定。
3.最复杂的是有两个子数的情况,可以考虑两种方法,都是同样的思想:用被删除节点A的左子树的最右节点或者A的右子树的最左节点作为替代A的节点,并修改相应的最左或最右节点的父节点的指针,修改方法类似2 ,不做细致讨论。
9.遍历二叉树(时间效率:O(n),空间效率:O(n))
遍历就是顺着某一条搜索路径巡防二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次(又称周游)。
遍历的目的:得到树中所有结点的一个线性排序。
遍历用途:它是树结构的插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。
若规定先左后右,则只有前三种情况:
DLR--先(根)序遍历,
LDR--中(根)序遍历,
LRD--后(根)序遍历。
9.1先序遍历二叉树
操作定义:若二叉树为空,则空操作;否则
(1)访问根结点;(D)
(2)先序遍历左子树;(L)
(3)先序遍历右子树。(R)
代码实现:
Status PreOrderTraverse(BiTree T){
if(T==NULL) return OK;//空二叉树
else{
vist(T);//访问根结点
PreOrderTraverse(T-->lchild);//递归遍历左子树
PreOrderTraverse(T-->rchi;d);//递归遍历右子树
}
}
void Pre(BiTree *T){
if(T!=NULL){
printf("%d\t",T->data);
pre(T->lchild);
pre(T->rchild);
}
}
9.2中序遍历二叉树
操作定义:若二叉树为空,则空操作;否则
(1)中序遍历左子树;(L)
(2)访问根结点;(D)
(3)中序遍历右子树。(R)
代码实现:
Status InOrderTraverse(BiTree T){
if(T==NULL) return OK;//空二叉树
else{
InOrderTraverse(T->lchild);//递归遍历左子树
visit(T);//访问根结点
InOrderTraverse(T->rchild);//递归遍历右子树
}
}
9.3后序遍历二叉树
操作定义:若二叉树为空,则空操作;否则
(1)后序遍历左子树;(L)
(2)后序遍历右子树。(R)
(3)访问根结点;(D)
代码实现:
Status PostOrder Traverse(BiTree T){
if(T==NULL) return OK;//空二叉树
else{
PostOrderTraverse(T->lchild);//递归遍历左子树
PostOrderTraverse(T->rchild);//递归遍历右子树
visit(T);//访问根结点
}
}
例题1:请写出下图二叉树的先序、中序和后序遍历顺序。
遍历结果:
先序:-+a*b-cd/ef(表达式的前缀表示--波兰式)
中序:a+b*c-d-e/f(表达式的中缀表示)
后序:abcd-*+ef/-(表达式的后缀表示--逆波兰式)
注意:若二叉树中各结点的值均不相同,则二叉树结点的先序序列、中序序列和后序序列都是唯一的。由二叉树的先序序列和中序序列,或由二叉树的后序序列和中序序列可以唯一一棵二叉树。
例题2:已知二叉树的先序序列和中序序列,构造出相应的二叉树
先序:ABCDEFGHI
中序:CDBFEAIHGJ
分析:由先序序列确定根;由中序序列确定左右子树。
9.4中序遍历非递归算法
二叉树中序遍历的非递归算法的关键:在中序不来了过某个结点的整个左子树,如何找到该结点的根以及右子树。
基本思想:(1)建立一个栈;
(2)根结点进栈,遍历左子树;
(3)根结点出栈,输出根结点,遍历右子树。
代码实现:
Status InOrderTraverse(BiTree T){
BiTree p; InitStack(S); p=T;
while(p||!StackEmpty(S)) {
if(p) { Push(S,p); p=p->lchild;}
else { Pop(S,q); printf("%c",q->data);
p=q->rchild;}
}//while
return OK;
}
9.5二叉树层次遍历
算法设计思路:使用一个队列
1.将根结点进队
2.队不空时循环,从队列中出列一个结点*p,访问它;
(1)若它有左孩子结点,将左孩子结点进队;
(2)若它有右孩子结点,将右孩子结点进队;
队列类型定义如下:
typedef struct{
BTNode data[MaxSize];//存放队中元素
in front,rear;//队头和队尾指针
}SqQueue;//顺序循环队列类型
代码实现:
void LevelOrder(BTNode *b){
BTNode *p,SqQueue *qu;
InitQueue(qu);//初始化队列
enQueue(qu,b);//根结点指针进入队列
while(!QueueEmpty(qu)){//队不为空,则循环
deQueue(qu,p);//出队结点p
printf("%c",p->data);//访问结点p
if(p->lchild!=NULL)enQueue(qu,p->lchild);
//有左孩子时将其进队
if(p->rchild!=NULL)enQueue(qu,p->rchild);//有右孩子时将其进队
}
}
9.6二叉树的建立
按先序遍历排序建立二叉树的二叉链表
例:已知先序序列为:
ABCDEGF
(1)从键盘输入二叉树的结点信息,建立二叉树的存储结构;
(2)在建立二叉树的过程中按照二叉树先序方式建立;
对下图所示二叉树,按下了顺序读入字符:ABC##DE#G##F###
代码实现:
Status CreateBiTree(BiTree &T){
scanf(&ch);//cin>>ch
if(ch=="#")T=NULL;
else{
if(!(T=(BiTNode*)malloc(sizeof(BiTNoce))))
exit(OVERFLOW);//T=new BiTNode;
T->data=ch;//生成根结点
CreateBiTree(T->lchild);//构造左子树
CreateBiTree(T->rchild);//构造右子树
}
return OK;
}//CreateBiTree
9.7复制二叉树
操作流程:如果是空树,递归结束;否则,申请新结点空间,复制根结点。
递归复制左子树,递归复制右子树。
代码实现:
int Copy(BiTree T,BiTree &New T){
if(T==NULL){//如果是空树返回0
New T=NULL; return 0;
}
else{
New T=new BiTNode;New T->data=T->data;
Cope(T->lChild,New T->lchild);
Cope(T->rChild,New T->rchild);
}
}
9.8计算二叉树的深度
操作流程:如果是空树,则深度为0;否则,递归计算左子树的深度记为m,递归计算右子树的深度记为n,二叉树的深度则为m与n的较大者加1。
代码实现:
int Depth(BiTree T){
if(T==NULL) return 0;//如果是空树返回0
else{
m=Depth(T->lChild);
n=Depth(T->rChild);
if(m>n) return (m+1);
else return (n+1);
}
}
9.9计算二叉树结点总数
操作流程:如果是空树,则结点个数为0;否则,结点个数为左子树的结点个数+右子树结点个数再+1。
代码实现:
int NodeCount(BiTree T){
if(T==NULL)
return 0;
else
return NodeCount(T->lchild)+
NodeCount(T->rchild)+1;
}
9.10计算二叉树叶子结点数
操作流程:如果是空树,则叶子结点个数为0;否则,为左子树的叶子结点个数+右子树叶子结点个数。
代码实现:
int LeadCount(BiTree T){
if(T==NULL)//如果是空树返回0
return 0;
if(T->lchild==NULL&&T->rchild==NULL)
return 1;//如果是叶子结点返回1
else
return LeafCount(T->lchild)+LeafCount(T->rchild);
}
线索二叉树
可以利用二叉链表中的空指针域(如果某个结点的左孩子为空,则将空的左孩子指针域指向其前驱,如果某结点的右孩子为空,则将空的右孩子指针域指向其后继)来寻找特定遍历序列中的二叉树结点的前驱和后继。这种通过改变指向的指针称为“线索”,而加上线索的二叉树称为线索二叉树。
为区别lrchild和child指针到底是指向孩子的指针还是指向前驱或者后继的指针,对二叉链表中每个结点增设两个标志域了ltag和rtag,并约定:
ltag=0 lchild指向该结点的左孩子;
ltag=1 lchild指向该结点的前驱
rtag=0 rchild指向该结点的右孩子
rtag=1 rchild指向该结点的后继
typedef struct BiThrNode{
int data;
int ltag,rtag;
struct BiThrNode *lchild,rchild;
}BiThrNode,*BiThrTree;
树的存储结构
10.1双亲表示法
实现:定义结构数组存放树的结点,每个结点含两个域:数据域:存放结点本身信息。双亲域:指示本结点的双亲结点在数组中的位置。
特点:找双亲容易,找孩子难。
10.2孩子链表
把每个结点的孩子结点排列起来,看成一个线性表,用单链表存储,则n个结点有n个孩子链表(叶子的孩子链表为空表)。而n个头指针又组成一个线性表,用顺序表(含n个元素的结构数组)存储。
特点:找孩子容易,找双亲难。
10.3孩子兄弟表示法
实现:用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟结点。
10.4树转换为二叉树(二叉树转树则与之相反)
步骤:1.加线:在兄弟之间加一连线
2.抹线:每个结点,除了其左孩子外,去除其与其余孩子之间的关系
3.遵循孩子在左边,兄弟在右边的原则。
口诀:树变二叉树:兄弟相连留长子。二叉树变树:左孩右右连双亲。去掉原来右孩线。
10.5森林转换为二叉树(二叉树转换为森林则与之相反)
步骤:1.将各棵树分别转换成二叉树。
2.将每棵树的根结点用线相连。
3.以第一棵树根结点为二叉树的根,再以根结点为轴心,遵循孩子在左边,兄弟在右边的原则构造二叉树型结构。
口诀:森林变二叉树:树变二叉根相连。二叉树变森林:去掉全部右孩线,孤立二叉再还原。