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

学了忘记,忘记了又来翻书。还是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);
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值