您的位置:首页 > 其它

二叉树的性质极其二叉树的构建、各种遍历以及深度宽度的求解

2015-06-10 11:28 471 查看
学了忘记,忘记了又来翻书。还是mark一下吧。

性质1:二叉树的第i层上至多有2的i-1次方个节点。

性质2:深度为n的二叉树至多有2的n-1次方个节点。

性质3:对于任何一个二叉树,终端节点数为n0,度为2的节点为n2,那么n0=n2+1。

性质4:具有n个节点的完全二叉树的深度为[log n]+1,这里的[ ]表示取整,不大于log n的最大整数。

证明的地址:proof

构建二叉树之前,需要定义相关使用的数据结构:

#include<iostream>
#include<vector>
#include<stack>
#include<queue>
#include<cassert>

using namespace std;

extern "C"
{
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<math.h>
}
typedef char ELement;

typedef struct Tree{
ELement data;
struct Tree *lchild,*rchild;
}iTree,*bTree;
二叉树的建立,这里利用前序递归的方法进行建立:

void creat_tree(bTree *T)
{
ELement d;

fflush(stdin);
printf("please input data:\n");
scanf("%c",&d);
(*T)=(bTree)malloc(sizeof(iTree));
if((*T)!=NULL)
{
(*T)->data=d;
(*T)->lchild=(*T)->rchild=NULL;
printf("%d\n",(*T)->data);
}

if((*T)->data == '#')
{
*T=NULL;
return;
}
else
{
creat_tree(&(*T)->lchild);
creat_tree(&(*T)->rchild);
}
}
我需要构建的树形如下:



由于采用的是递归的方式产生二叉树,这里输入元素的顺序:ABC##D##DF##G##。

然后我们进行遍历,分为前序、中序、后序、层序。前中后序的遍历利用递归的方式可以简单地实现:

void prereadtree(bTree T)
{
if(T==NULL)
return;
printf("%c\t",T->data);
prereadtree(T->lchild);
prereadtree(T->rchild);
}
void midreadtree(bTree T)
{

if(T==NULL)
return;
midreadtree(T->lchild);
printf("%c\t",T->data);
midreadtree(T->rchild);
}
void backreadtree(bTree T)
{
if(T==NULL)
return;
backreadtree(T->lchild);
backreadtree(T->rchild);
printf("%c\t",T->data);
}
其思想很简单,这里不再讲解。

这里主要说下层序遍历的方式,想到层序遍历的时候,首先想到地是使用队列或者栈的方式进行遍历。这里采用C++里面的队列方式实现,对于C++队列的操作主要需要包含类queue。队列的操作主要有以下几种:

入队:q.push(x); 将x 接到队列的末端。

出队:q.pop(); 弹出队列的第一个元素,注意,并不会返回被弹出元素的值。

访问队首元素:q.front(),即最早被压入队列的元素。

访问队尾元素:q.back(),即最后被压入队列的元素。

判断队列空:q.empty(),当队列空时,返回true。

访问队列中的元素个数:q.size()


这里利用C++队列实现层序遍历的思想如下:

首先将根节点压入队列;定义一个数据节点指针指向队列的首元素;队列的首元素出队列;然后查看队列的首元素是否有左右子树,如果有则入队列,其中左子树先入队列;然后判断队列是否为空。第二次循环判断的时候,上一个节点的左子树在队列首位,然后就这样一直循环,直到所有节点均被遍历完。借用上面的思想实现的代码如下:

void layerreadtree(bTree T)
{
if(T==NULL)
return;
queue<bTree> quetree;

quetree.push(T);
while(!quetree.empty())
{
bTree node=quetree.front();
quetree.pop();

printf("%c\t",node->data);

if(node->lchild)
quetree.push(node->lchild);
if(node->rchild)
quetree.push(node->rchild);
}

}
然后再进行树的深度的计算,思想很简单,找到最左节点,每次循环加一,找到最右节点,每次循环加一。比较两次累加的结果,取其中最大的就是树的深度。代码的实现如下:

int depthtree(bTree T)
{
int leftdepth=0,rightdepth=0;
bTree p;

p=T;
do{
leftdepth++;
p=p->lchild;
}while(p!=NULL);

p=T;//回到树根
do{
rightdepth++;
p=p->rchild;
}while(p!=NULL);

if(leftdepth>rightdepth)
return leftdepth;
else
return rightdepth;
}
下面来说有点难度的树的宽度的计算,什么叫做树的宽度呢?树的宽度就是寻找树中元素的节点最多的一层。怎么计算呢?有了上面层序遍历的经验,这次继续采用层序遍历的方式进行计算。考虑到几个因素,计算每一层元素的个数的时候,我们只需要统计当前队列里面有多少元素即可,但是在统计之前,需要等到上一层的元素完全出了队列,统计出来的结果才是当前层的结果。下面是代码的实现:

int getwidth(bTree T)
{
int maxwidth=1;
int currentwidth=0;
int lastwidth;

if(T==NULL)
return 0;

queue<bTree> quetree;

quetree.push(T);
lastwidth=1
while(!quetree.empty())
{
while(lastwidth!=0)
{
bTree node=quetree.front();
quetree.pop();

if(node->lchild)
quetree.push(node->lchild);
if(node->rchild)
quetree.push(node->rchild);
lastwidth--;
}
currentwidth=quetree.size();
maxwidth=currentwidth>maxwidth?currentwidth:maxwidth;
lastwidth=currentwidth;
}
return maxwidth;

}


下面结合代码,讲解下这个算法:

首先树根入队列,初始化上层元素个数为1。

然后进入内层循环,node节点指向队列首位,队列首位元素出队列,树根左右子树入队列,lastwidth减一,结束此次循环。

currenwidth获取当前队列内元素个数,然后判断前元素个数currentwidth是不是最大的,是则赋值给maxwidth,不是则maxwidth保持不变。

然后将本层统计的元素个数值赋值给lastwith,继续进入下一次循环,然后在这次循环中所有的元素出队列,其左右子树均入队列。然后一直循环,按照这样的规则,直到队列中不再有元素停留,则结束。

下面讲树的另一种操作,对树本身进行反转。

未对树进行镜像反转之前,先序遍历的结果是:ABCDDFG,如果对树进行镜像操作,得到的结果应该是ADGFBDC。

怎么实现树的镜像呢?

思路是这样的,先查看树节点存不存在左右子树,如果没有左右子树,镜像反转blabla。。。都是无用的~存在左右子树就交换左右子树,这样就完成了反转。

具体怎么实施呢?

我们先到树根节点,反转与树根连接的左右节点,然后进入左子树反转,然后进入右子树反转。这么一分析,so easy。。。下面是代码实现:

void turnaround(bTree *T)
{
if((*T)==NULL || (*T)->lchild==NULL || (*T)->rchild ==NULL)
return ;
bTree tempnode;

tempnode=(*T)->lchild;
(*T)->lchild=(*T)->rchild;
(*T)->rchild=tempnode;

turnaround(&(*T)->lchild);
turnaround(&(*T)->rchild);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: