数据结构基础(3) 二叉树操作总结

Content

  • 树的定义(root,F),基本术语,相关概念(有向树,有序树,无序树,森林)
  • 树的结构特点:只有一个根节点;有若干叶子节点;根节点无前驱,其余有若干个前驱;叶子节点无后继,其余有若干个后继
  • 二叉树的五个结构特性及证明:
    (1) 每层最多节点个数(归纳)
    (2)每层的累计节点数(利用(1))
    (3)n0=n2+1(边与节点数的关系)
    (完全二叉树与满二叉树的定义)
    (4)完全二叉树的层数公式(利用(2))
    (5)完全双亲节点与左右孩子节点的对应层次序号(归纳)
  • 二叉树的存储结构
    (1)顺序结构:树序号为i的节点存储在一维数组下标为i-1的分量中(适用于完全二叉树,不浪费空间)
    (2)链式结构
    二叉链表(左右孩子指针)
    三叉链表(左右孩子指针,双亲指针,方便直接找双亲节点)
    双亲链表(方便直接找双亲节点)
    线索链表(有效利用空指针域,若经常需要遍历链表查找前驱和后继,则比较适合使用线索链表)
  • 二叉树的遍历:先序,后序,中序,层次(相应的递归非递归算法)
  • 与中缀表达式的关系(以操作符为根节点和分支节点,操作数为叶子节点建树。先序遍历就是前缀表达式,中序遍历就是中缀表达式,后序遍历就是后缀表达式
  • 建立二叉树的存储结构
    1.字符串:AB C D
    2.表达式建树:先缀表达式建树,中缀表达式建树
    3.先序和中序序列建树
  • 线索二叉树操作
    (1)理解二叉树线索化的实质:建立结点与其在相应序列中的前驱或后继之间的直接联系
    (2)线索化的过程实际上就是修改根节点的空指针使其指向相应遍历序列里面的前驱和后继,相关的算法描述个人没办法直观看一眼就明白其操作含义,最好还是画图依照算法的步骤执行几步,看看能不能理解每每步究竟在干嘛。中序遍历的线索化过程的算法描述如下:
    在这里插入图片描述
    在这里插入图片描述
    这个是最后的完成图示:
    在这里插入图片描述
    先看inorderthreading函数,传进去的参数是thrt(头节点),T(树的根节点):首先为头节点分配空间,thrt的左标志域设为link,即thrt的左指针指向节点,thrt的右标志域为thread,即thrt的右指针指向遍历序列里面的节点,然后将其右指针指向自己(意义不明,不影响后面的理解),如果根节点不是空树,那么就将头节点左指针指向根节点,pre(刚刚访问过的节点,是一个全局变量,由两个函数共享),头节点的初始化完成后,接下来看重头戏–>inthreading(T),要记得这个线索化是给予中序遍历的线索化,因此要先对根节点的左子树进行操作,然后是根节点,最后是右子树。利用递归思想:首先对左子树进行线索化,(要记得我们是想将中序遍历所得的序列给予前驱后继关系,将整个序列链接起来。)然后看目前的节点p有没有前驱,如果他有左孩子的话,那么按照中序遍历的规则,他的左孩子就是他的前驱,如果他没有左孩子,那么就以刚刚访问的节点pre作为他的前驱。如果刚刚访问的节点pre没有后继,那么就将该点p作为pre的后继(实际上pre就是他的作为叶节点的左孩子。接下来将pre=p(因为接下来访问的是右子树,右子树根节点的前驱就是p),再将其右子树线索化。多说无益,看图(thrt的0和1画反了,最后一张才改过来):
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    thrt的指向实际上是为了方便正向和反向遍历,遍历算法考试好像没什么要求,理解掌握了了线索化即可。第一次看这个算法的时候也是理解了很久,希望以上能有些帮助。
  • 对于中序线索化,对于每个节点来说,要知道如何找前驱和后继:前驱,如果一个节点的Ltag为link,那么就找他左子树最后访问的节点,即左子树右下角,如果是thread,那么直接就是thread指向的内容;后继,如果一个节点的Rtag为link,那么就找他右子树第一个访问的节点,即右子树左下角,如果是thread,那么直接就是thread指向的内容;

以下是一些基础操作:

#include "stdio.h"
#include "stdlib.h"
#include<string.h>
#define N 30

//定义二叉树的节点结构
typedef struct node
{
	char data;
	struct node *left,*right,*pa;
}BT;

BT *createbt(char *pre,int *k);//根据先序创建二叉树
BT *createbt2(char *pre,char *in,int k);//根据先序,中序创建二叉树
void showbt(BT *T);//以括号的形式显示二叉树
void preorder(BT *T);//先序遍历二叉树(递归) 
void preorder2(BT *T);//先序遍历(非递归)
void inorder(BT *T);//中序遍历二叉树(递归)
void inorder2(BT *T);//中序遍历二叉树 (非递归) 
void postorder(BT *T);//后序遍历二叉树(递归)
void postorder2(BT *T);//后续遍历二叉树(非递归) 
void layer(BT *T);//层次遍历(非递归) 
BT *delbt(BT *T);//销毁二叉树 
void getpa(BT *T);//给父亲节点赋值
void getpa2(BT *T)//为二叉树的每个节点的pa指针赋值(非递归算法)
void showpa(BT *T);//遍历显示父节点 
void countleaf(BT*T,int &count);//统计叶子节点的个数 
int countlayer(BT*T);//统计树的层数 
BT *gettreenode(char,BT*,BT*,BT*);//得到一个树的节点 
BT *copytree(BT *);//复制树 

int main()
{
	char pre[]="ABD G   CE  F  ";//先序序列,空格对应的是NULL
	BT *T=NULL;//
	int k=0;
	//T=createbt(pre,&k);
	char pre2[]="ABDGCEF";
	char in[]="DGBAECF";
	T=createbt2(pre2,in,strlen(pre2));
	if(!T)
		printf("树为空\n");
	else
	    showbt(T);
	    	printf("\n\n");
	printf("\n先序遍历:\n");
	preorder2(T);
	printf("\n中序遍历:\n");
	inorder2(T);
	printf("\n后序遍历:\n");
	postorder2(T);
	printf("\n层次遍历:\n");
	layer(T);
	int count=0;
	countleaf(T,count);
	printf("\n叶子个数:%d\n",count);
	printf("\n树的层数: %d\n",countlayer(T));
	BT *newT=copytree(T);
	printf("\nNewtree!!!\n");
	if(!newT)
		printf("树为空\n");
	else
	    showbt(newT);
	    	printf("\n\n");
	    	printf("\n先序遍历:\n");
	preorder2(newT);
	printf("\n中序遍历:\n");
	inorder2(newT);
	printf("\n后序遍历:\n");
	postorder2(newT);
	printf("\n层次遍历:\n");
	layer(newT);
	count=0;
	countleaf(newT,count);
	printf("\n叶子个数:%d\n",count);
	printf("\n树的层数: %d\n",countlayer(newT));
	T=delbt(T);
	newT=delbt(newT);
	return 0; 
} 

 BT *createbt(char *pre,int *k)
 //这里用int*是为了保证进行递归时先序数组会一直向下读取,当左子节点完成初始化后读取的是右子节点 
 {	BT *A;
 	//注意:两种情况都要有返回值 (k对应的值所在节点的指针) 
 	if (pre[*k]!=' ')//根节点非空 ,表示该根节点存在 
 	{
 		A=(BT *)malloc(sizeof(BT));//分配空间 
 		A->data=pre[*k];//赋值 
 		(*k)++;//读取左子节点 
 		A->left=createbt(pre,k);//以左子节点作为新的根初始化 
		(*k)++;//读取右子节点 
		A->right=createbt(pre,k); //以右子节点作为新的根初始化 
		return A; 
	}
	else return NULL;
 }
  BT *createbt2(char *pre,char *in,int k)//根据先序,中序创建二叉树
 //pre指向根节点,in为中序遍历得到的列表,k为中序序列当前根节点左/右侧的点个数 
 {	
 	BT *T;
 	int i,j;
 	if (k<=0) //如果该点下一层左或右侧没有子节点,则将该节点的子节点赋为NULL 
		return NULL;
 	else
 	{	
	 	T=(BT *)malloc(sizeof(BT));
 		T->data=pre[0];
 		for(i=0;in[i]!=pre[0];i++);
 		//每次递归传入新的根节点值在先序表里面的指针,新的搜索起点,集合元素个数 
 		T->left=createbt2(pre+1,in,i);
		T->right=createbt2(pre+i+1,in+i+1,k-i-1);
 		return T;
	}
 }

 void showbt(BT *T)//以括号的形式显示二叉树
 {
 	if (T)//该节点非空 ,空的话执行空操作 
	 {	
	 	printf("%c",T->data);//输出该点的值 
	 	if(T->left||T->right)//若该点有子节点 
	 	{
			printf("("); 
	 		showbt(T->left);//以左子节点作为新的根节点进行输出,若左子节点为空,那么将不会有任何输出 
	 		printf(",");
	 		showbt(T->right);//以右子节点作为新的根节点进行输出。。。。。。 
	 		printf(")");
		}
	 } 
}
//--------------遍历递归算法:虽然简单,但要深刻理解访问顺序------------void preorder(BT *T)//先序遍历二叉树(递归)
{	
	if(T)
	{
		printf("%c ",T->data);
		preorder(T->left);
		preorder(T->right);
	}
}
void inorder(BT *T)//中序遍历二叉树(递归)
{
 if(T)
	{
		inorder(T->left);
		printf("%c ",T->data);
		inorder(T->right);
	}
} 
void postorder(BT *T)//后序遍历二叉树(递归)
 {
 if(T)
	{
		postorder(T->left);
		postorder(T->right);
		printf("%c ",T->data);
	}
} 

BT *delbt(BT *T)//销毁二叉树 
{   //后序遍历,先销毁左右子节点,再销毁根节点 
	if(T)
	{
		delbt(T->left);
		delbt(T->right);
		free(T);
		return NULL;
	}
	else return NULL;
}

void getpa(BT *T)//给父亲节点赋值--后序遍历 
{	
	 if(T)
	 {	
	 	getpa(T->left);
	 	getpa(T->right);
	 	if(T->left) T->left->pa=T;
	 	if(T->right) T->right->pa=T;
	 	T->pa=NULL;
	  } 
} 

void showpa(BT *T)//显示父节点
{
	if (T)
	{
		if(T->pa) printf("%c father is %c\n",T->data,T->pa->data);
		else printf("%c father is NULL\n",T->data);
		showpa(T->left);
		showpa(T->right);
	} 
} 

void preorder2(BT *T)//先序遍历非递归算法
{
	BT *t[N];//定义栈,指针数组,可以存放指向结点的指针
	BT *p;//指针p用来做遍历- 
	int top=0;//栈顶
	t[0]=T;//初始将根节点入栈
	while(top>=0)//只要栈非空
	{
		p=t[top];//
		top--;//弹栈
		printf("%c ",p->data);//输出被弹出的节点数值
		if(p->right)//当前结点如果有右孩子
		{
			top++;//
			t[top]=p->right;//右孩子压栈
		}
		if(p->left)//当前结点如果有左孩子
		{
			top++;//
			t[top]=p->left;//左孩子压栈
		}
	}
	printf("\n");
}

void inorder2(BT *T)//中序遍历二叉树 (非递归)
{
	BT *t[N],*p=T;//定义栈,指针数组,可以存放指向结点的指针,指针p用来做遍历,p初始指向根节点
	int top=-1;//栈顶,初始栈为空
	while(top>=0||p)//只要栈非空或者指针p为真
	{
		while(p)//只要p为真
		{
			top++;//
			t[top]=p;//将p指向的节点压栈
			p=p->left;//继续向左走
		}
		p=t[top];//
		top--;//弹栈
		printf("%c ",p->data);//输出弹出的节点数值
		p=p->right;//指针p指向当前弹出节点的右,如果有右,继续按照如上的步骤找到最左,然后弹栈
		          //输出....;如果没有右,则继续弹栈
	}
	printf("\n");
} 

void postorder2(BT *T)//后续遍历二叉树(非递归) 
{
	BT *t[N];//定义栈,指针数组,可以存放指向结点的指针
	BT *p=T,*b;//指针p用来做遍历,初始指向根节点;指针b用来存放刚刚访问过的节点
	int top=-1;//初始栈为空
	do//注意循环的样式
	{
		while(p)//只要指针p为真
		{
			top++;//
			t[top]=p;//将指针p指向的节点压栈
			p=p->left;//继续向左走
		}
		b=NULL;//给b赋起始值
		while(top>=0)//如果当前栈非空
		{
			p=t[top];//观察栈顶,判断栈顶元素的右孩子有没有被访问过
			if(p->right==b)//如果刚访问过的节点就是栈顶的右
			{
				p=t[top];//说明当前的栈顶元素可以弹出(弹出意味着可以访问)
				top--;//
				printf("%c ",p->data);//输出该结点数值
				b=p;//将刚刚访问的节点指向赋值给b
			}
			else//如果刚访问过的节点不是栈顶的右
			{
				p=p->right;//不能弹栈,要进入栈顶的右分支,
				break;//并从当前的循环中退出,回到第一层的循环体内,继续上述的操作
			}
		}

	}while(top>=0);//只要栈非空
	printf("\n");
}
void layer(BT *T)//层次遍历(非递归)
{
	BT *t[N],*p;
	int top=-1,rear=-1;//初始化空队列 
	if(T) t[++rear]=T;//根节点入队 
	while(top!=rear)//当队列非空时 
	{
		p=t[top+1];//根节点开始向下遍历 
		printf("%c ",p->data);//输出根节点 
		if(p->left) t[++rear]=p->left;//左孩子入队 
		if(p->right) t[++rear]=p->right;//右孩子入队 
		top++;//根节点出队,进行其右侧相邻兄弟节点或下一个层次的最左侧节点的遍历 
	}
	printf("\n");
 } 
 void countleaf(BT *T,int &count)//统计叶子节点的个数(先序遍历) 
 {
 	if(T)
 	{
 		if(!(T->left||T->right)) count++;
 		countleaf(T->left,count);
 		countleaf(T->right,count);
	 }
  } 
  //这里任意遍历方法都可以,本质就是简单地遍历所有节点并判断它是不是叶子
  int countlayer(BT *T)//统计树的层数 (先序遍历) 
  {
  	if (!T) return 0;
	else return 1+(countlayer(T->left)>countlayer(T->right)?countlayer(T->left):countlayer(T->right));//注意要加一
  }
 BT *gettreenode(char data,BT *left,BT *right,BT *pa)//得到一个树的节点
{
  	BT *node=(BT*)malloc(sizeof(BT));
  	node->data=data;
  	node->left=left;
  	node->right=right;
  	node->pa=pa;
  	return node;
} 
BT *copytree(BT *T)//复制树 
{	BT *L,*R;
	if(!T) return NULL;
	else
	{
		L=copytree(T->left);
		R=copytree(T->right);
	} 
	return gettreenode(T->data,L,R,T->pa);	//获得左右子树再与根节点链接 
}
void getpa2(BT *T)//为二叉树的每个节点的pa指针赋值(非递归算法)——————层次遍历 
{
	BT *t[N],*p;
	int front=-1,rear=-1;//初始化队列 
	t[++rear]=T;//根节点入队 
	T->pa=NULL;//根节点pa赋值为NULL 
	while(rear!=front)
	{
		p=t[front+1];
		if(p->left)
		{
			t[++rear]=p->left;
			p->left->pa=p;
		}
		if(p->right)
		{
			t[++rear]=p->right;
			p->right->pa=p;
		}
		front++;
	}
}

先缀表达式建树

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<iostream>
using namespace std;

typedef struct node
{
	char data;
	struct node *left,*right;
}BT;

BT *createtree(char*,int*);
int isoperator(char);
int isnum(char); 
void showbt(BT*T);
BT *delbt(BT *T);

int main()
{
	char temp[]="-*+abc/de";
	int k=0;
	int *p=&k;
	BT *T=createtree(temp,p);
	showbt(T);
	delbt(T);
	return 0;	
}
BT *createtree(char *expression,int *k)
{
	
	if(isoperator(expression[*k]))
	{
		BT *T=(BT*)malloc(sizeof(BT));
		T->data=expression[*k];
		(*k)++;
		T->left=createtree(expression,k);
		(*k)++;
		T->right=createtree(expression,k);
		return T;
	}
	else
	{	
		BT *T=(BT*)malloc(sizeof(BT));
		T->data=expression[*k];
		T->left=NULL;
		T->right=NULL;
		return T;
	}
}

int isoperator(char c)
{
	if(c==43||c==45||c==42||c==47) return 1;
	else return 0;
}
int isnum(char c)
{
	if((c>='a'&&c<='z')||(c>='A'&&c<='Z'))
	return 1;
	else return 0;
}
  void showbt(BT *T)//以括号的形式显示二叉树
 {
 	if (T)//该节点非空 
	 {	//输出该点的值
	 	printf("%c ",T->data);
	 	if(T->left||T->right)//若该点有子节点 
	 	{
			printf("("); 
	 		showbt(T->left);//以左子节点作为新的根节点进行输出,若左子节点为空,那么将不会有任何输出 
	 		printf(",");
	 		showbt(T->right);//以右子节点作为新的根节点进行输出。。。。。。 
	 		printf(")");
		}
	 } 
}
BT *delbt(BT *T)//销毁二叉树 
{   //后序遍历,先销毁左右子节点,再销毁根节点 
	if(T)
	{
		delbt(T->left);
		delbt(T->right);
		free(T);
		return NULL;
	}
	else return NULL;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值