什么是二叉树: 在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。
如图所示
关于树的术语
树的结点(node):包含一个数据元素及若干指向子树的分支;
孩子结点(child node):结点的子树的根称为该结点的孩子;
双亲结点:B 结点是A 结点的孩子,则A结点是B 结点的双亲;
兄弟结点:同一双亲的孩子结点;堂兄结点:同一层上结点;
祖先结点: 从根到该结点的所经分支上的所有结点
子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙
结点层:根结点的层定义为1;根的孩子为第二层结点,依此类推;
树的深度:树中最大的结点层
结点的度:结点子树的个数
树的度:树中最大的结点度。
叶子结点:也叫终端结点,是度为 0 的结点;
分枝结点:度不为0的结点;
有序树:子树有序的树,比如家族树;
无序树:不考虑子树的顺序;
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
每个节点的深度与它左右子树的深度有关,且等于其左右子树最大深度值加上 1。即:
maxDepth(root) = max(maxDepth(root.left),maxDepth(root.right)) + 1
以 [3,9,20,null,null,15,7] 为例:
由此我们可以得到根节点的最大深度为
maxDepth(root-3)
=max(maxDepth(sub-4),maxDepth(sub-20))+1
=max(1,max(maxDepth(sub-15),maxDepth(sub-7))+1)+1
=max(1,max(1,1)+1)+1
=max(1,2)+1
=3
因此求最大深度就可以用递归来实现,不断遍历该树的左右子树最后判断谁最大
在递归中,如果层级过深,我们很可能保存过多的临时变量,导致栈溢出。这也是为什么我们一般不在后台代码中使用递归的原因
事实上,函数调用的参数是通过栈空间来传递的,在调用过程中会占用线程的栈资源。而递归调用,只有走到最后的结束点后函数才能依次退出,而未到达最后的结束点之前,占用的栈空间一直没有释放,如果递归调用次数过多,就可能导致占用的栈资源超过线程的最大值,从而导致栈溢出,导致程序的异常退出。
二叉树也有很多性质
二叉树有以下几个性质:
性质1:二叉树第i层上的结点数目最多为 2^{i-1} (i≥1)。
性质2:深度为k的二叉树至多有2^{k}-1个结点(k≥1)。
性质3:包含n个结点的二叉树的高度至少为log2 (n+1)。
性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1。
顺序结构中找孩子容易 孩子的节点分别是2i 和 2i+1
二叉树中常用的三种遍历方式
第一种 前序遍历又称 先序遍历 根左右
void PreOrder(BTNode *b)
{ if (b!=NULL)
{ printf("%c ",b->data); //访问根结点
PreOrder(b->lchild);
PreOrder(b->rchild);
}
}
第二种中序遍历 左根右
void InOrder(BTNode *b)
{ if (b!=NULL)
{ InOrder(b->lchild);
printf("%c ",b->data); //访问根结点
InOrder(b->rchild);
}
}
第三种后序遍历 左右根
void PostOrder(BTNode *b)
{ if (b!=NULL)
{ PostOrder(b->lchild);
PostOrder(b->rchild);
printf("%c ",b->data); //访问根结点
}
}
对应的递归算法如下
int Nodes(BTNode *b)
{
if (b==NULL)
return 0;
else
return Nodes(b->lchild)+Nodes(b->rchild)+1
}
void DispLeaf(BTNode *b)
{ if (b!=NULL)
{ if (b->lchild==NULL && b->rchild==NULL)
printf("%c ",b->data); //访问叶子结点
DispLeaf(b->lchild); //输出左子树中的叶子结点
DispLeaf(b->rchild); //输出右子树中的叶子结点
}
}
A进栈 A出栈 C进栈 B进栈 B没有右孩子,B出栈 D进栈 D出栈 G进栈 G出栈
C出栈 F进栈 E进栈 E出栈 F出栈
算法如下
void PreOrder1(BTNode *b)
{ BTNode *p;
SqStack *st; //定义栈指针st
InitStack(st); //初始化栈st
if (b!=NULL)
{ Push(st,b); //根结点进栈
while (!StackEmpty(st)) //栈不为空时循环
{ Pop(st,p); //退栈结点p并访问它
printf("%c ",p->data);
if (p->rchild!=NULL) //有右孩子时将其进栈
Push(st,p->rchild);
if (p->lchild!=NULL) //有左孩子时将其进栈
Push(st,p->lchild);
}
printf("\n");
}
DestroyStack(st); //销毁栈
}
void InOrder1(BTNode *b)
{ BTNode *p; SqStack *st; //定义一个顺序栈指针st
InitStack(st); //初始化栈st
p=b;
while (!StackEmpty(st) || p!=NULL)
{ while (p!=NULL) //扫描结点p的所有左下结点并进栈
{ Push(st,p); //结点p进栈
p=p->lchild; //移动到左孩子
}
//以下考虑栈顶结点
if (!StackEmpty(st)) //若栈不空
{ Pop(st,p); //出栈结点p,访问结点p
printf("%c ",p->data);
p=p->rchild; //转向处理其右子树
}
}
printf("\n");
DestroyStack(st); //销毁栈
}
void PostOrder1(BTNode *b) //后序非递归遍历算法
{ BTNode *p,*r;
bool flag;
SqStack *st; //定义一个顺序栈指针st
InitStack(st); //初始化栈st
p=b;
do
{ while (p!=NULL) //扫描结点p的所有左下结点并进栈
{ Push(st,p); //结点p进栈
p=p->lchild; //移动到左孩子
}
r=NULL; //r指向刚刚访问的结点,初始时为空
flag=true;
while (!StackEmpty(st) && flag)
{ GetTop(st,p); //取出当前的栈顶结点p
if (p->rchild==r) //若结点p的右孩子为空或者为刚访问结点
{ printf("%c ",p->data); //访问结点p
Pop(st,p);
r=p; //r指向刚访问过的结点
}
else
{ p=p->rchild; //转向处理其右子树
flag=false; //表示当前不是处理栈顶结点
}
}
} while (!StackEmpty(st)); //栈不空循环
printf("\n");
DestroyStack(st); //销毁栈
}
以上的非递归思想都是建立于各种遍历的访问方法上
typedef struct
{ BTNode *data[MaxSize]; //存放队中元素
int 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);
}
}
二叉树的构造
先序和中序
BTNode *CreateBT2(char *post,char *in,int n)
{ BTNode *b; char r,*p; int k;
if (n<=0) return NULL;
r=*(post+n-1); //根结点值
b=(BTNode *)malloc(sizeof(BTNode)); //创建二叉树结点b
b->data=r;
for (p=in;p<in+n;p++) //在in中查找根结点
if (*p==r) break;
k=p-in; //k为根结点在in中的下标
b->lchild=CreateBT2(post,in ,k);
b->rchild=CreateBT2(post+k,p+1 ,n-k-1);
}
创建二叉树可以用 括号表示法创建,也可以用 前序中序 ,或者中序后续来创建二叉树
#include <iostream>
using namespace std;
typedef struct node
{
char data;
struct node *lchild;
struct node *rchild;
}BTNODE;
void CreateBTree(BTNODE * &b,char *str)
{
BTNODE *St[50],*p;
int top =-1,k,j=0;
char ch;
b=NULL;
ch=str[j];
while(ch!='\0')
{
switch (ch)
{
case'(': top++;St[top]=p;k=1;break;
case')':top--;break;
case',':k=2;break;
default :p=(BTNODE*)malloc(sizeof(BTNODE));
p->data=ch;
p->lchild =p->rchild =NULL;
if(b==NULL)
b=p;
else
{
switch (k)
{
case 1:St[top]->lchild =p;break;
case 2:St[top]->rchild =p;break;
}
}
}
j++;
ch=str[j];
}
}
void PreOrder(BTNODE * b)
{
if(b!=NULL)
{
printf("%c->",b->data );
PreOrder(b->lchild );
PreOrder(b->rchild );
}
}
void InOrder(BTNODE * b)
{
if(b!=NULL)
{
InOrder(b->lchild );
printf("%c->",b->data );
InOrder(b->rchild );
}
}
void PostOrder(BTNODE * b)
{
if(b!=NULL)
{
PostOrder(b->lchild );
PostOrder(b->rchild );
printf("%c->",b->data );
}
}
void DispLeaf(BTNODE *b)
{
if(b!=NULL)
{
if(b->lchild ==NULL&&b->rchild ==NULL)
printf("%c->",b->data );
DispLeaf(b->lchild );
DispLeaf(b->rchild );
}
}
int main()
{
BTNODE * b;
char str[]={'A','(','B','(','D',',','E','(','H','(','J',',','K','(','L',',','M','(',',','N',')',')',')',')',')',',','C','(','F',',','G','(',',','I',')',')',')'};
CreateBTree(b,str);
printf("先序遍历的结果如下");
PreOrder( b);
printf("\n");
printf("中序遍历的结果如下");
InOrder( b);
printf("\n");
printf("后序遍历的结果如下");
PostOrder( b);
printf("\n");
printf("所有的叶子结点的结果如下");
DispLeaf(b);
printf("\n");
return 0;
}
二叉树的销毁
查找二叉树中的某个节点
输出二叉树,也可以用 前序中序后续来输出
最后关于二叉树的实际应用 ,参考这篇文章,这篇文章说的很详细,例如二叉排序树,平衡二叉树,二叉搜索树等
https://mp.weixin.qq.com/s?__biz=Mzg2NzA4MTkxNQ==&mid=2247487396&idx=2&sn=f4da5c1b78713d24abe715466cb153d3&chksm=ce404470f937cd6674273c20755bf760294ad6cdabbc0b358a8c12297db9063c43a7f383ddac&mpshare=1&scene=23&srcid=0517fE7g8NAdS1e0QcjMggx4&sharer_sharetime=1589710839347&sharer_shareid=ac11f0b33b3f01f55650b6c5eeb96ac0#rd