二叉树的特点:1、每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点);2、二叉树的子树有左右之分,其次序不能任意颠倒。
二叉树的递归定义
二叉树(BinaryTree)是n(n≥0)个结点的有限集。它或者是空集(n=0),或者同时满足以下两个条件:
(1) 有且仅有一个根结点;
(2) 其余的结点分成两棵互不相交的左子树和右子树。
二叉树(Binary Tree)是另一种树型结构。
二叉树的特点:1、每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点);2、二叉树的子树有左右之分,其次序不能任意颠倒。
二叉树的递归定义
二叉树(BinaryTree)是n(n≥0)个结点的有限集。它或者是空集(n=0),或者同时满足以下两个条件:
(1) 有且仅有一个根结点;
(2) 其余的结点分成两棵互不相交的左子树和右子树。
二叉树的五种基本形态:
(a) 空二叉树 (b) 只有根节点的二叉树 (c)右子树为空的二叉树 (d) 左子树为空的二叉树 (e) 满二叉树
满二叉树:第i层节点的个数位2^(i-1) 节点的总个数位:2^k-1(k位深度)
完全二叉树:每个节点i的左子树深度减去右子树深度结果为零或1
二叉树的性质:
性质1 若二叉树的层次从1开始, 则在二叉树的第
i 层最多有 2^(i-1) 个结点。(i >=1)
性质2:深度为k的二叉树最多有 2k-1个结点。(k ³ 1)
性质3:对任何一颗二叉树,如果其叶子节点的个数为n0,度为2的非叶子节点个数为n2,则有n0=n2+1
性质4:具有n个节点的完全二叉树的高度为log2(n)+1(log以2为底n的对数+1)
性质5:如果对一颗有n个节点的完全二叉树(深度log2n+1)的节点按连续层序编号,则对任一节点i有
(1)如果i=1,则节点i为二叉树的根节点,无双亲,如果i>1,则其双亲是编号为i/2的节点
(2)如果2i>n.则编号为i的节点无左孩子,为叶子节点;否则其左孩子为编号是2i的节点
(3)如果2i+1>n,则编号为i的节点无右孩子,否则其右孩子是编号为2i+1的节点。
(前面的都是基本概念)
现在来讲一下二叉树的存储结构,他的存储结构也有两种,顺序和链表
1.顺序存储结构:用这种存储结构是我们不仅要知道二叉树要存储的数据信息,还要存储他的结构信息,比如双亲和孩子的关系
关键是如何反应二叉树中的非线性关系(完全 二叉树中节点i的左孩子为2i,右孩子为2i+1,双亲为i/2),在此约定把一颗具有n个节点的完全二叉树存储在具有N+1个存储单元的一维数组中,并且,为了使节点的位置与其在数组中的下标一致,下标为0的存储单元不存放二叉树的节点信息,可以用于存放二叉树节点的总数等。这种存储方式适合完全二叉树,既不浪费存储空间,又能很快确定节点的存放位置,节点的双亲和左右孩子的存放位置,但对一般二叉树,可能造成存储空间的大量浪费。
例如:深度为K,且只有k个节点de左单支,需2^k-1.个单元,既有2^k-1-K个单元被浪费
2.链式存储结构
设计不同的节点结构,可以构成不同的链式存储结构,常用的有:
(1)二叉链表 用于二叉树每个节点之多只有连个孩子,分别是左孩子和右孩子,因此可以把每个节点分成三个域,一个域存放节点本身信息,另外另个域是指针,分别指向左右孩子的地址,每个节点的结构表示为:
lchile data rchild
二叉链表的类型定义如下:
typedef int ElemType
typedef struct BiTreeNode
{
ElemType data;
struct BitreeNode *lchild
struct BitTreeNode *rchild
}BitTreeNode,*BiTree;
一个二叉链表由根指针root唯一确定,若二叉树为空,则root=Null,若节点的某个孩子不存在,则相应的指针为空
具有n个节点的二叉链表中,共有2n个指针域,其中有n-1个用来指示节点的左右孩子,其余的n+1个指针域为空
遍历二叉树
DLR------------先序(先遍历根节点,再遍历左子树,再遍历右子树)
LDR------------中序 (先遍历左子树,在遍历根节点,再遍历右子树)
LRD------------后序 (先遍历左子树,再遍历右子树,再遍历根节点)
二叉树的先序遍历递归算法:
void PreOrder(BiTree bt) /* bt为指向根结点的指针*/
{
if (bt) /*如果bt为空,结束*/
{
visit (bt ); /*访问根结点*/
PreOrder (bt -> lchild); /*先序遍历左子树*/
PreOrder (bt -> rchild); /*先序遍历右子树*/
}
}
代码实现:
typedef char ElemType;
typedef struct BtNode // binarytreenode
{
BtNode *leftchild;
//BtNode *parent; // BST val rb b+-
BtNode *rightchild;
ElemType data;
}BtNode,*BinaryTree;
//先序
void PreOrder(BtNode *p)
{
if(p != NULL)
{
cout<
PreOrder(p->leftchild);
PreOrder(p->rightchild);
}
}
//中序
void InOrder(BtNode *p)
{
if(p != NULL)
{
InOrder(p->leftchild);
cout<
InOrder(p->rightchild);
}
}
//后序
void PastOrder(BtNode *p)
{
if(p != NULL)
{
PastOrder(p->leftchild);
PastOrder(p->rightchild);
cout<
}
}
写出图示二叉树的前序,中序,后序序列
层次遍历:二叉树的层次遍历是指从二叉树的第一层(根节点)开始从上至下逐层遍历,在同一层中,按从左至右的顺序对节点逐个进行访问
树的层次遍历利用队列来实现
算法思想:遍历从二叉树的根节点开始,首先将根节点入队列,然后执行下面的操作
(1)取出队头的元素;
(2)访问该元素所指节点
(3)若该元素所指节点的左右孩子节点非空,则将该元素所指节点的左孩子指针和右孩子指针入队
(4)若队列非空,重覅1,3,当队列为空时,二叉树的层次遍历结束
void LevelOrder( BiTree bt) /*层次遍历二叉树bt算法*/
{ 初始化队列Q;
if ( bt == NULL ) return;
bt入队列Q;
while( 队列Q不空){
pß出队元素;
Visit( p); /*访问出队结点*/
if ( p->lchild) /*队首结点左孩子不空,入队*/
{ p->lchild入队Q }
if (p->rchild) /*队首结点右孩子不空,入队*/
{ p->rchild入队Q }
}
}
代码实现:
层次遍历:
void NiceLevelOrder(BtNode *ptr)
{
if(NULL == ptr) return ;
queue
qu.push(ptr);
while(!qu.empty())
{
BtNode *p = qu.front(); qu.pop();
cout<
if(p->leftchild != NULL)
{
qu.push(p->leftchild);
}
if(p->rightchild != NULL)
{
qu.push(p->rightchild);
}
}
cout<
}
构造二叉树:
(根据序列构造二叉树)
已知二叉树的先序序列和中序序列构造二叉树:
int FindPos(ElemType *is,ElemType x,int n)
//这个函数的作用是在中序遍历中找到先序遍历根的位置
{
int pos = -1;
for(int i = 0;i
if(x == is[i])
{
pos = i;
break;
}
}
return pos;
}
BtNode * CreatePI(ElemType *ps,ElemType *is,int n)//ps是先序序列,is是中序序列,数组大小
{
BtNode *s = NULL;
if(n > 0)
{
s = Buynode();
s->data = ps[0];// s->data = *ps;
int pos = FindPos(is,ps[0],n);//找到跟节点在中序中的位置
if(pos == -1) exit(1);
s->leftchild = CreatePI(ps+1,is,pos);
s->rightchild = CreatePI(ps+1+pos,is+1+pos,n - pos - 1);
}
return s;
}
BtNode * CreateTreePI(ElemType *ps,ElemType *is,int n)
{
if(NULL == ps || NULL == is || n < 1)
return NULL;
else
return CreatePI(ps,is,n);
}
已知二叉树的中序遍历和后序遍历序列创建一颗二叉树
BtNode * CreateTreePI(ElemType *ps,ElemType *is,int n)
{
if(NULL == ps || NULL == is || n < 1)
return NULL;
else
return CreatePI(ps,is,n);
}
BtNode * CreateIL(ElemType *is,ElemType *ls,int n)
{
BtNode *s = NULL;
if(n > 0)
{
s = Buynode();
s->data = ls[n-1];
int pos = FindPos(is,ls[n-1],n);//同上面的findpos 相同,是在中序遍历中找后序遍历序列得到的根节点
if(pos == -1) exit(1);
s->leftchild = CreateIL(is,ls,pos);
s->rightchild = CreateIL(is+pos+1,ls+pos,n - pos - 1);
}
return s;
}
******这样有一个缺点,如果二叉树的节点中有重复的,那末这样构造就不准确
二叉树的非递归调用:
非递归的遍历
以二叉树的中序遍历为例说明采用这种深入个返回策咯的遍历方法
沿左子树深入时,深入一个节点入栈一个节点,沿左分治无法继续深入时,返回,即出栈,出此栈的同是访问节点,然后从此节点的右子树继续深入,这样一直下去,直到从根节点的右子树返回(即空栈)时结束。
中序遍历的非递归算法思想:
Void Inorder(BiTree bt)
{ p=bt; /*根结点为当前结点*/
S=Initial( ); /*初始化栈*/
While(p||!Empty(S))
{
While(p) /*当前结点不空*/
{
Push(S,p); /*当前结点入栈*/
p=p->Lchild; /*左孩子作为当前点;
}
If(!Empty(S)) /*栈不空*/
{
p=pop(s); /*出栈*/
Visit(p); /*访问结点*/
p=p-Rchild; /*右孩子作为当前结点*/
}
}
}
先序遍历的非递归算法思想:
Void Firstorder(BiTree bt)
{ p=bt; /*根结点为当前结点*/
S=Initial( ); /*初始化栈*/
While(p||!Empty(S))
{
While(p) /*当前结点不空*/
{ visit(p); /*访问结点*/
Push(S,p); /*当前结点入栈*/
p=p->Lchild; /*左孩子作为当前结点*/
}
If(!Empty(S)) /*栈不空*/
{
p=pop(s); /*出栈*/
p=p->Rchild; /*右孩子作为当前结点*/
}
}
}
后序遍历的非递归算法思想:
typedef enum{L,R} tagtype; /*定义枚举类型*/
typedef struct
{
Bitree ptr;
tagtype tag;
}stacknode; /*定义栈结点类型*/
typedef struct
{
stacknode Elem[maxsize];
int top;
}SqStack; /*定义顺序栈*/
SqStack s;
stacknode x;
void PostOrderUnrec(Bitree t) /*后序遍历算法*/
{ p=t;
If(!p) return;
do
{ while (p) /*遍历左子树*/
{
x.ptr = p;
x.tag = L; /*标记为左子树*/
push(s,x); /*入栈*/
p=p->lchild; /*左孩子作为当前结点*/
}
while (!StackEmpty(s) && s.Elem[s.top].tag==R)
{ x = pop(s);
p = x.ptr;
visite(p);//tag为R,表示右子树访问完毕,故访问根结点
}
if (!StackEmpty(s))
{
s.Elem[s.top].tag =R; /*遍历右子树*/
p=s.Elem[s.top].ptr->rchild; /*右孩子作为当前结点*/
}
}while (!StackEmpty(s));
}
结合“扩展先序遍历序列”,创建二叉树
扩展先序遍历序列:就是先对原有二叉树用空子树进行扩展,使每个结点的左右子树(包括空子树)都存在,然后再对扩展后的二叉树进行先序遍历。遍历序列中用特定的符号表示空子树扩展先序遍历序列:就是先对原有二叉树用空子树进行扩展,使每个结点的左右子树(包括空子树)都存在,然后再对扩展后的二叉树进行先序遍历。遍历序列中用特定的符号表示空子树
其扩展先序遍历序列为:
5 8 9 0 0 7 0 0 6 0 3 4 0 0 0
其中“0”表示空子树。
代码如下:
BtNode *Buynode()
{
BtNode *s = (BtNode*)malloc(sizeof(BtNode));
if(NULL == s) exit(1);
memset(s,0,sizeof(BtNode));
return s;
}
void Freenode(BtNode *p)
{
free(p);
}
BtNode * CreateTree1()
{
BtNode *s = NULL;
ElemType item;
cin>>item;
if(item != '#')
{
s = Buynode();
s->data = item;
s->leftchild = CreateTree1();
s->rightchild = CreateTree1();
}
return s;
}
BtNode * CreateTree2(ElemType *&str)
{
BtNode *s = NULL;
if(*str != '#')
{
s = Buynode();
s->data = *str;
s->leftchild = CreateTree2(++str);
s->rightchild = CreateTree2(++str);
}
return s;
}
设计算法输出二叉树的所有叶子结点的个数。
设计算法求二叉树的深度。
设计算法先序输出二叉树中所有结点值及其对应层次/序号。
设计算法输出从根结点到每个叶子结点的路径(利用栈)。
1.设计算法输出二叉树所有叶子节点的个数
思想:若二叉树为空,则叶子数目为0,
对于一颗非空二叉树,如果他的左子树和右子树都为空,那末此二叉树只有一个节点,就是叶子,此时叶子数目为1,否则,二叉树的叶子数目为左子树叶子数目和右子树叶子数目总和
算法如下:
int BitreeLeaf ( BiTree bt ) //bt是根节点
{
if ( bt == NULL ) return 0 ; /* 空树,叶子数为0 */
if ( bt->lchild ==NULL&& bt->rchild == NULL)
return 1 ; /*只有一个根结点,叶子数为1*/
return ( BitreeLeaf ( bt -> lchild ) + BitreeLeaf ( bt -> rchild )) ;
}
代码如下:
int GetSize(BtNode *ptr) //计算二叉树节点的个数
{
if(ptr == NULL) return 0;
else return GetSize(ptr->leftchild) + GetSize(ptr->rightchild) + 1;
}
2.设计算法求二叉树的深度
基本思想:若二叉树为空,约定二叉树的深度为0;对于一颗二叉树,如果他的左子树和右子树都为空,那末此二叉树只有一个根节点,此时二叉树的深度为1,否则先求其左右子树的深度为depthl和depthr,那末整颗二叉树的深度为1+max(depthl,delptr).
算法如下:
int BitreeDepth ( BiTree bt )
{ int d = 0,depthL, depthR; /*depthL和depthR分别为左、右子树的深度*/
if ( bt == NULL ) return 0 ; /*空树,深度为0 */
if ( bt -> lchild ==NULL && bt -> rchild == NULL) return 1; /*叶子结点,深度为1 */
depthL = BitreeDepth ( bt -> lchild ) ; /*左子树深度 */
depthR = BitreeDepth ( bt -> rchild ) ; /*右子树深度 */
d = max (depthL , depthR ) /*d为左右子树中较深者的深度*/
return d+1 ; /* 整棵二叉树的深度为左、右子树中较深者的深度+1 */
}
代码如下:
int Depth(BtNode *ptr)
{
if(ptr == NULL) return 0;
else return max(Depth(ptr->leftchid),Depth(ptr->rightchild)) + 1;
}