要谈二叉树,先要谈树的定义,因为二叉树是树的的特例。
以下是树的定义:
树由根结点和一系列子树组成,而子树又由根结点和一系列子树构成。既没有根结点也没有子树的树称为空树。树有且仅有一个根结点(即没有直接前驱的结点),还存在叶子结点(没有直接后驱的结点),和分支结点(除掉上述两种结点外的结点)。
那么二叉树的定义就是将上述定义中的一系列子树改为两个子树。
而二叉树又可以分为满二叉树和完全二叉树。
由二叉树的定义可以知道前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;
}
}