树的定义:树是n个结点的有限集。
若n = 0,称为空树
若n > 0, 则它满足如下两个条件
(1)有且仅有一个特定的称为根的结点
(2)其余结点可分为m个互不相交的有限集,其中每一个集合本身又是一颗树,并称为 树的子树
树的基本术语:
根结点:非空树中无前驱结点的结点
结点的度:结点拥有的子树数
树的度:树内各结点的度的最大值
分支结点:非终端结点
内部结点:艮结点以外的分支结点
叶子:终端结点
结点的字数的根成为该结点的孩子,该结点称为孩子的双亲,有共同双亲的结点为兄弟
双亲在同一层的结点为堂兄弟。
有序树:树中结点的各子树从左到右有次序(最左边的为第一个孩子)
森林:m(m>=0)棵互不相交的树的集合
二叉树是n(n>=0)个结点的有限集,它或者是空集,或者由一个根结点及两颗互不相交的分别称为这个根的左子树和右子树的二叉树组成
特点:每个结点最多有俩孩子,子树有左右之分,其次序不能颠倒,二叉树可以是空集合,根可以有空的左子树或空的右子树。
注:二叉树的子树要区分左子树和右子树,即使只有一颗子树也要区分。二叉树不是树的特殊情况,它们是两个概念
//二叉树的抽象数据类型定义
ADT BinaryTree{
数据对象D:D是具有相同特性的数据元素的集合
数据关系R:若D是空集则R是空集
若D不是空集,则R ={H};H是如下二元关系
1、root唯一
2、子树不相交
...
基本操作P:
CreatBiTree(&T,definition)//按definition建立二叉树
PreOrderTraver(T)//前序遍历
InOrderTraver(T)//中序遍历
PostOrderTraver(T)//后序遍历
}
二叉树的性质:
性质1、在二叉树的第i层上至多有个结点(i>=1)第i层最少有1个结点
性质2、深度为k的二叉树至多有个结点,至少有k个结点
性质3、任何一棵二叉树T,如果其叶子数有n0,度为2的结点数位n2,则n0 = n2+1
性质4、具有n个结点的完全二叉树的深度为[log]+1; [x]表示不大于x的最大整数
性质4证明:假设此二叉树的深度为k,则根据性质2及完全二叉树的定义得到或,取对数得 k-1<=log
性质5、如果对一棵有n个结点的完全二叉树(深度为 log +1)的结点按层序编号(从第一层到第 log +1层,每层从左到右),则对任一结点i,有:
(1)如果i=1,则结点i是二叉树的根,无双亲,如果i>1,则其双亲是结点[i/2].
(2)如果2i>n,则结点i为叶子结点,无左孩子;否则,其左孩子是结点2i
(3)如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1
两种特殊形式的二叉树:
满二叉树:一棵深度为k且有个结点的二叉树为满二叉树
特点:每层都满,叶子节点全部在最底层
对满二叉树从上到下从左到右编号。
完全二叉树:深度为k的具有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉 树中编号为1~n的结点一一对应时,称为完全二叉树
注:满二叉树中,从最后一个结点开始,连续去掉任意个结点,即使一棵完全二叉树。
特点:叶子只可能分布在层次最大的两层上,对任一结点,如果其右子树的最大层次为i,则其左子树最大层次必为i或i+1
按满二叉树的结点层次编号,依次存放二叉树中的数据元素
#define MAXTSIZE 100
Typedef TElemType SqBiTree[MAXTSIZE]
SqBiTree bt;
最坏的情况:深度为k的且只有k个结点的单支树需要长度为的一维数组
顺序存储适于存满二叉树和完全二叉树
一个结点存放三个内容,数据,左孩子指针,右孩子指针
typedef struct BiNode{
TElemType data
struct BiNode *lchild,*rchild;//左右孩子指针
}BiNode,*BiTree
typedef struct TriTNode{
TelemType data;
struct TriTNode *lchild,*parent,*rchild;
}TriTNode,*TriTree;
顺着某一条搜索路径巡访二叉树的结点,使得每个结点均被访问一次,而且仅被访问一次(又称周游)
遍历目的:得到树中所有结点的一个线性排列
L:左 R:右 D:根
若规定先左后右:则只有三种情况:DLR先(根)序遍历,LDR中(根)序遍历,LRD后(根)序遍历
先序遍历:访问根结点、先序遍历左子树、先序遍历右子树
中序遍历:中序遍历左子树,访问根结点,中序遍历右子树
后序遍历:后序遍历左子树,后序遍历右子树,访问根结点
由二叉树的先序和中序或后序和中序能构造出相应的二叉树,但知道前序和后序不能构造
前序后序定根,中序定左右子树
递归算法实现:
//先序遍历算法
Status ProOrderTraberse(BiTree T)
{
if(T==NULL)return OK;//空二叉树
else{
visit(T);//访问根结点
PreOrderTraverse(T->lchild);//递归遍历左子树
PreOrderTraverse(T->rchild);//递归遍历右子树
}
}
//C实现
void Pre(BiTree *T){
if(T!=NULL){
printf("%d\t",T->data);
pre(T->lchild);
pre(T->rchild);
}
}
//中序遍历算法
Status InOrderTraberse(BiTree T)
{
if(T==NULL)return OK;//空二叉树
else{
InOrderTraverse(T->lchild);//递归遍历左子树
visit(T);//访问根结点
InOrderTraverse(T->rchild);//递归遍历右子树
}
}
//后序遍历算法
Status PostOrderTraberse(BiTree T)
{
if(T==NULL)return OK;//空二叉树
else{
PostOrderTraverse(T->lchild);//递归遍历左子树
PostOrderTraverse(T->rchild);//递归遍历右子树
visit(T);//访问根结点
}
}
非递归算法实现:
//中序遍历非递归算法
二叉树中序遍历的非递归算法的关键:在中序遍历过某结点的整个左子树后,如何找到该结点的根以及右子树
基本思想:
(1)建立一个栈
(2)根结点进栈,遍历左子树
(3)根结点出栈,输出根结点,遍历右子树
Status InOrderTraverse(BiTree T)
{
BiTree p;
InitStack(S);
p = T;
q = new BiTree;
while(p||!StackEmpty(S)){
if(p){
Push(S,p);
p = p->lchild;
}
else{
Pop(S,q);
printf("%c",q->data);
p = q->rchild;
}
}
return OK;
}
二叉树的层次遍历:
对一颗二叉树,从根结点开始,按从上到下、从左到右的顺序访问每一个结点
算法思路:使用一个队列
1、将根结点进队;
2、队不空时循环:从队列中列出一个结点*p,访问它;若它有左孩子结点,将左孩子结点进队,若它有右孩子结点,将右孩子结点进队。
void LevelOrder(BTNode *b){
BTNode *p;
SqQueue *qu;
InitQueue(qu);//初始化队列
enQueue(qu,b);//根结点指针进入队列
while(!QueueEmpty(qu)){//队不为空,则循环
deQueue(qu,p);//出队结点p
printf("%c",p->data);
if(p->lchild!=NULL)enQueue(qu,p->lchild);//有左孩子时将其进队
if(p->rchild!=NULL)enQueue(qu,p->rchild);//有右孩子时将其进队
}
}
二叉树遍历算法的应用:
//按先序遍历序列建立二叉树的二叉链表,需要用#补上空结点,不然得到的解不唯一
Status CreateBiTree(BiTree &T)
{
scanf(&ch);
if(ch=="#")T = NULL;//用#表示补充的结点
else{
if(!(T=(BiTNode*)malloc(sizeof(BiTNode))))
exit(OVERFLOW);
T->data;//生成根结点
CreateBiTree(T->lchild);//构造左子树
CreateBiTree(T->rchild);//构造右子树
}
return OK;
}
//复制二叉树
int Copy(BiTree T,BiTree &NewT)
{
if(T==NULL){//如果是空树返回0
NewT = NULL;
return 0;
}
else{
NewT = new BiTNode;
NewT->data = T ->data;
Copy(T->lChild,NewT->lchild);
Copy(T->rChild,NewT->rchild);
}
}
//计算二叉树的深度
//计算左右子树的深度,选取较大者加一
int Depth(BiTree T)
{
if(T==NULL)return 0;
else{
m = Depth(T->lChild);
n = Depth(T->rChild);
if(m>n)return m+1;
lese return n+1;
}
}
//计算二叉树结点总数
//结点个数为左子树结点个数+右子树的结点个数+1
int NodeCount(BiTree T){
if(T == NULL)
return 0;
else
return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}
//计算二叉树叶子结点数
//左子树叶子个数+右子树叶子结点个数
int LeadCount(BiTree T)
{
if(T==NULL)
return 0;
if(T->lchild==NULL&&T->rchild==NULL)
return 1;
else
return LeafCount(T->lchild)+LeafCount(T->rchild);
}
如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱,如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继----这种改变指向的指针称为线索,加上了线索的二叉树称为线索二叉树。对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化
ltag = 0 lchild 指向该结点的左孩子
ltag = 1 lchild 指向该结点的前驱
rtag = 0 rchild 指向该结点的右孩子
rtag = 1 rchild 指向该结点的后继
typedef struct BiThirNode{
int data;
int ltag,rtag;
struct BiThrNode *lchild,rchild;
}BiThrNode,*BiThrTree;
线索二叉树增设了一个i头结点
ltag = 0,lchild指向根结点
rtag = 1,rchild指向遍历序列中的最后一个结点
遍历序列中第一个结点的lc域和最后一个结点的rc域都指向头结点