二叉树

要谈二叉树,先要谈树的定义,因为二叉树是树的的特例。

以下是树的定义:

树由根结点和一系列子树组成,而子树又由根结点和一系列子树构成。既没有根结点也没有子树的树称为空树。树有且仅有一个根结点(即没有直接前驱的结点),还存在叶子结点(没有直接后驱的结点),和分支结点(除掉上述两种结点外的结点)。

那么二叉树的定义就是将上述定义中的一系列子树改为两个子树。

而二叉树又可以分为满二叉树和完全二叉树。

由二叉树的定义可以知道前k层,最多有2^k-1个结点,第k层最多有2^(k-1)个结点,那么满二叉树就是刚好有k层,并且结点个数为2^k-1.。

完全二叉树的定义为按照从上到下,从左到右的顺序构造二叉树,更准确的说是:对于同一层,在没有构造左结点的情况下不允许构造右结点,对于不同层,在没有构造完上层结点时,不允许构造下层结点。按照这样的顺序构造的二叉树称为完全二叉树,很明显满二叉树是完全二叉树的特例。

完全二叉树有一个很明显的特征,即按照从根结点编号为1从左到右,从上到下依次给结点编号,就存在这样的结论,对于一个结点,其编号为n,如果它有子节点,则左结点编号为2n,如果他有右结点,则其右结点的编号为2n+1,相反,对于一个编号为n的结点,它的父亲结点为n/2.

完全二叉树的存储结构可以很简单,直接开数组,顺序存储,就可以了,但是对于非完全二叉树,就不能用这种存储结构了,要么是父亲结点与子节点无法对应,后者是太浪费空间了,所以就产生了一种链式二叉树存储方式。

typedef int type_data;

typedef struct Node{

type_data data;

struct Node Lson;

struct Node Rson;

}node,*pivot;

设计这样的结构体储存二叉树结点,其中data为该结点的数据,Lson指向该结点的左儿子,Rson指向该结点的右儿子,这样就可以解决上面的两个问题了,那么如何遍历二叉树呢?

总有6中方式遍历二叉树,

先序:根 左 右  或  根 右 左

中序:左 根 右  或  右 根 左

后序:左 右 根  或  右 左 根

由于默认为先左后右,所以就仅仅讨论前面的三种。

下面是对二叉树的操作:

#include <stdio.h>
#include <stdlib.h>
#define Max(a,b) (a)>(b)?(a):(b)
typedef char type_data;
typedef struct Node{
type_data data;
struct Node Lson;
struct Node Rson;
}node,*pnode;
pnode Creat_bitter_tree(){ //先序遍历创建二叉树
	char ch=getchar();
	if(ch=='.')
		return NULL;
	else{
		pnode p=(pnode)malloc(sizeof(node));
		p->data=ch;
		p->Lson=Creat_bitter_tree();
		p->Rson=Creat_bitter_tree();
		return p;
	}
}
//递归遍历算法
//================
//先序遍历二叉树
void Pre_vi_bitter_tree(pnode p){
	if(p){
	   putchar(p->data); //visit(p->data);
	   Pre_vi_bitter_tree(p->Lson);
	   Pre_vi_bitter_tree(p->Rson);
	}
}

//中序遍历二叉树
void In_vi_bitter_tree(pnode p){
	if(p){
		In_vi_bitter_tree(p->Lson);
		putchar(p->data);//visit([p->data);
		In_vi_bitter_tree(p->Rson);
	}
}

//后序遍历二叉树
void Behind_vi_bitter_tree(pnode p){
    if(p){
	    Behind_vi_bitter_tree(p->Lson);
	    Behind_vi_bitter_tree(p->Rson);
	    putchar(p->data);
     }
}
//统计二叉树中的结点个数
int count=0;
//先序
void Calcu_number(pnode p){
	if(p){
		count++;
		Calcu_number(p->Lson);
		Calcu_number(p->Rson);
	}
}
//中序
void Calcu_number(pnode p){
	if(p){
		Calcu_number(p->Lson);
	    count++;
		Calcu_number(p->Rson);
	}
}
//后序
void Calcu_number_node(pnode p){
	if(p){
		Calcu_number(p->Lson);
		Calcu_number(p->Rson);
		count++;
	}
}
//统计二叉树中叶子结点
//先序,其他的和上述相同,交换一下次序即可
int count=0;
void Calcu_num_leaf(pnode p){
	if(p){
		if(p->Lson==NULL && p->Rson==NULL){
			count++;
			return ;
		}
		Calcu_num_leaf(p->Lson);
		Calcu_num_leaf(p->Rson);
	}
}
//后序的另一种方法
int Calcu_num_leaf(pnode p){
	if(p==NULL)
		return 0;
	else if(p->Lson==NULL && p->Rson==NULL)
		return 1;
	else
		return Calcu_num_leaf(p->Lson)+
		       Calcu_num_leaf(p->Rson);
}
//后序求二叉树的深度
int Calcu_deep(pnode p){
	if(p==NULL)
		return 0;
	int num1=Calcu_deep(p->Lson);
	int num2=Calcu_deep(p->Rson);
	return Max(num1,num2)+1;
}
//先序遍历求二叉树的深度,相应的中序与后序算法改变一下比较语句的位置即可
int deep=0;//h初始值为1
void Calcu_deep(pnode p,int h){
	if(p){
		if(h>deep)
			deep=h;
		Calcu_deep(p->Lson,h+1);
		Calcu_deep(p->Rson,h+1);
	}
}
//非递归算法遍历二叉树
//栈实现先序遍历
void Pre_vi_Bitree(pnode p){
	if(p==NULL)
		return ;
	stack<pnode> s;
	pnode q=p;
	while(!s.empty() || q){
		if(q){
		putchar(q->data);
		if(q->Rson)
		s.push(q->Rson);
		q=q->Lson;
		}
		else{
			q=s.top();
			s.pop();
		}
	}
}
//用队列实现层次遍历
void Vi_Bitree_level(pnode p){
	if(p==NULL)
		return ;
	else{
		queue<pnode> pivot;
		pnode q=p;
		pivot.push(q);
		while(!empty(pivot)){
			q=pivot.front();
			pivot.pop();
			putchar(q->data);
			if(q->Lson)
				pivot.push(q->Lson);
			if(q->Rson)
				pivot.push(q->Rson);
		}
	}
}
//中序
void In_vi_Bitree(pnode p){
	stack<pnode> pivot;
	pnode q=p;
	while(q || !pivot.empty()){
		while(q){
			pivot.push(q);
			q=q->Lson;
		}
		putchar(pivot.top()->data);
		q=pivot.top()->Rson;
		pivot.pop();
	}
}
//后序
void After_vi_Bitree(pnode p){
	stack<pnode> pivot;
	pnode q=p;
	pnode pre=NULL;
	while(q || !pivot.empty()){
		while(q){
			pivot.push(q);
			q=q->Lson;
		}
		q=pivot.top();
		if(q->Rson==NULL || q->Rson==pre){
			putchar(q->data);
			pivot.pop();
			pre=q;
			q=NULL;
		}
		else
			q=q->Rson;
	}
}

下面谈一下线索二叉树:我们知道一般二叉树的结点中会有空的指针,主要是因为没有左孩纸或右孩纸了,那么这样的存储结构看上去就显得不够充实,有点浪费空间,那么线索二叉树就是要解决这个问题,充分利用空指针而设计出来的,那么什么是线索二叉树呢,其实就是利用原二叉树的空指针,当左儿子为空时,左儿子指针指向该结点的直接前驱,当右儿子为空时,右儿子指针指向该结点的直接后继,那么二叉树中的所有空间都被充分应用了,要实现这个操作,那么还必须设置二个记录变量,一个用来记录左儿子是否为空,即不存在,这是其值为1,此时,左儿子指针指向其直接前驱,否则为0;同样的道理,设置一个用来记录右儿子是否存在的标记,依次设置其值,由于是前驱和后继,所以对二叉树的线索花要与遍历的顺序相联系,不同的遍历次序得到的二叉树的线索设置就会不同,这是显然的,另外,需要注意的是并不是二叉树中的每个结点都可以设置索引。需要理解的是:二叉树的线索化是为了使二叉树的操作变得相应的简单,并实现普通二叉树所不能实现的功能,或不好实现的功能。下面是线索二叉树的一部分操作代码:

//线索二叉树
//创建线索二叉树
#include <stdio.h>
#include <stdlib.h>
#include <queue>
#include <stack>
typedef char type_data;
typedef struct Node{
	type_data data;
	bool Ltrag;
	bool Rtrag;
	struct Node Lson;
	struct Node Rson;
}node,*pnode;
pnode Creat_bitter_tree(){ //先序遍历创建二叉树
	char ch=getchar();
	if(ch=='.')
		return NULL;
	else{
		pnode p=(pnode)malloc(sizeof(node));
		p->data=ch;
		p->Lson=Creat_bitter_tree();
		p->Rson=Creat_bitter_tree();
		return p;
	}
}
//先序初始化线索二叉树
void Pre_init(pnode p){
	if(p){
		p->Ltrag=0;
		p->Rtrag=0;
		Pre_init(p->Lson);
		Pre_init(p->Rson);
	}
}
void Init_bitter_tree(pnode p){
	if(p==NULL)
		return ;
	/*for(pnode q=p;q->Lson;q=q->Lson)
		;
	q->Ltrag=1;*/
	pnode q=p;
	for(;q->Rson;q=q->Rson)
		;
	q->Rtrag=1;
}
//中序创建线索二叉树
pnode pre=NULL;
void Construct_bitter_tree(pnode p){
	if(p){
		Construct_bitter_tree(p->Lson);
		if(p->Lson==NULL){
			p->Ltrag=1;
			p->Lson=pre;
		}
		if(pre && pre->Rson==NULL){
			pre->Rtrag=1;
			pre->Rson=p;
		}
		pre=p;
		Construct_bitter_tree(p->Rson);
	}
}
//在中序线索二叉树中查找某个结点的直接前驱,并返回其指针
pnode Search_bitter_tree_front(pnode p){
	if(p==NULL)
		return NULL;
	if(p->Ltrag)
		return p->Lson;
	else{
		pnode q=p->Lson;
		for( ;q->Rtrag==0;q=q->Rson)
			;
		return q;
	}
}
//在中序线索二叉树中查找某个结点的直接后继,并返回其指针
pnode Search_bitter_tree_near(pnode p){
	if(p==NULL)
		return NULL;
	if(p->Rtrag)
		return p->Rson;
	else{
		pnode q=p->Rson;
		for( ;q->Ltrag==0;q=q->Lson)
			;
		return q;
	}
}	

//输出中序线索二叉树的第一个结点的值,并返回其位置
pnode Search_bitter_tree_first_number(pnode){
	if(pnode==NULL){
		printf("the bitter tree is empty\n");
		return NULL;
	}
	pnode q;
	for(q=p;q->Ltrag==0;q=q->Lson)
		;
	printf("the first nmber is %c\n",q->data);
	return q;
}
//遍历中序线索二叉树
void Vi_bitter_tree(pnode p){
	if(p==NULL)
		return ;
	pnode q=Search_bitter_tree_first_number(p);
	while(q){
		putchar(q->data);
		q=Search_bitter_tree_near(q);
	}
}
//在二叉树中插入一个结点作为某个二叉树结点的右儿子
void Insert_bitter_tree_rightson(pnode p){
	if(p==NULL)
		return NULL;
	if(p->Rtrag){
		pnode q=p->Rson;
		pnode pivot=(pnode)malloc(sizeof(node));
		pivot->Ltrag=1;
		pivot->Lson=p;
		p->Rtrag=0;
		p->Rson=pivot;
		pivot->Rtrag=1;
		pivot->Rson=q;
	}
	else{
		pnode s=Search_bitter_tree_near(p);
		pnode q=p->Rson;
		pnode pivot=pnode(malloc)(sizeof(node));
		pivot->Ltrag=1;
		pivot->Lson=p;
		pivot->Rtrag=0;
		pivot->Rson=q;
		p->Rson=pivot;
		s->Lson=pivot;
	}
}

		




 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值