数据结构中树的一些代码进行总结,想着为PAT打一下基础,树的代码敲有点太少了,不太熟,看了紫书后敲的,avl树的内容紫书不在树这章,所以后补算了
文章目录
(一)树的基本概念
(二)二叉树
1.二叉树的存储结构
- 二叉树的链式存储结构,结构体内为二叉树节点的值和左右子树根节点的指针,叶子节点的左右子树为NULL
typedef struct Tree
{
int v;
struct Tree *l,*r;
}BiTree,*Bnode;
-
二叉树的顺序存储结构,varr数组中存放的为各节点的值,n为节点总数
当根节点下标为0时,i节点的左子树根节点下标为
2*i+1
,右子树的根节点下标为2*i+2
,双亲节点下标为(i-1)/2
当根节点下标为1时,i节点的左子树根节点下标为
2*i
,右子树的根节点下标为2*i+1
,双亲节点下标为i/2
顺序存储结构适用于完全二叉树,或者节点个数不多的情况(因为是顺序存储所以需要减少空节点占用数组空间),顺序存储结构可以通过数组下标快速访问左右节点,并且不需要malloc来开辟空间,一般如果条件允许多会选择使用顺序存储来实现;
struct struct Tree
{
int n;
int varr[1000];
}BiTree;
3.二叉树的遍历
是一个递归的过程
前序遍历
先访问根节点,再访问左子树,最后右子树
void PreOrderTraverse(Bnode T)
{
if(T!=NULL)
{
printf("%d ",T->v);
PreOrderTraverse(T->l);
PreOrderTraverse(T->r);
}
}
中序遍历
先遍历左子树,再访问根节点,最后是右子树
void PreOrderTraverse(Bnode T)
{
if(T!=NULL)
{
printf("%d ",T->v);
PreOrderTraverse(T->l);
PreOrderTraverse(T->r);
}
}
后序遍历
先遍历左子树,在遍历右子树,最后访问根节点
void PreOrderTraverse(Bnode T)
{
if(T!=NULL)
{
printf("%d ",T->v);
PreOrderTraverse(T->l);
PreOrderTraverse(T->r);
}
}
构建二叉树
当输入的序列指明了结尾节点的时候可以据此构建二叉树,输入的序列中可以以一个负数或者‘#’,来表示没有后序的节点
构建的过程和遍历类似,这是对前序遍历的序列的构建二叉树过程
void CreateBiTree(Bnode &T,int &n,int num)
{
if(in[n]==-1||n>=num) //终端情况
{
T=NULL;
return;
}
T=(Bnode)malloc(sizeof(BiTree)); //创建根节点
T->v=in[n];
CreateBiTree(T->l,n+=1,num); //创建左子树
CreateBiTree(T->r,n+=1,num); //创建右子树
}
层次遍历
对于一棵链式结构的二叉树进行层次遍历,可以先进行一次先序遍历,在先序遍历的时候记录各节点在顺序存储结构时的下标,就是将链式结构转化为顺序结构,接着直接输出顺序存储结构的数组就行;
int level_arr[10000];
void func1(Bnode T,int k,int no,int &m)
{
if(T!=NULL)
{
m=m>no?m:no;
level_arr[no]=T->v;
func1(T->l,k+1,no*2+1,m);
func1(T->r,k+1,no*2+2,m);
}
}
void LevelOrderTraverse(Bnode T)
{
memset(level_arr,-1,sizeof(level_arr)); //-1代表这个下标对应没有节点,为空
int m=0;
func1(T,1,0,m);
int i;
for(i=0;i<=m;i++)
if(level_arr[i]!=-1)
printf("%d ",level_arr[i]);
}
完整代码
对如下的树A就行构建二叉树的链式存储结构和遍历;
![](https://i-blog.csdnimg.cn/blog_migrate/e9b27b847d7d4821a86695c8d191d07b.jpeg)
注意:函数里的变量为局部变量是存储在栈空间的变量,栈空间的内存较小;如果需要定义大数组,需要定义为全局变量,全局变量是存储在堆空间上的变量,可以存放较大规模的数组;
#include<bits/stdc++.h> //包含了所有头文件
using namespace std;
typedef struct Tree
{
int v;
struct Tree *l,*r;
}BiTree,*Bnode;
int in[10000];
void CreateBiTree(Bnode &T,int &n,int num)
{
if(in[n]==-1||n>=num)
{
T=NULL;
return;
}
T=(Bnode)malloc(sizeof(BiTree));
T->v=in[n];
CreateBiTree(T->l,n+=1,num);
CreateBiTree(T->r,n+=1,num);
}
void PreOrderTraverse(Bnode T)
{
if(T!=NULL)
{
printf("%d ",T->v);
PreOrderTraverse(T->l);
PreOrderTraverse(T->r);
}
}
void InOrderTraverse(Bnode T)
{
if(T!=NULL)
{
InOrderTraverse(T->l);
printf("%d ",T->v);
InOrderTraverse(T->r);
}
}
void PostOrderTraverse(Bnode T)
{
if(T!=NULL)
{
PostOrderTraverse(T->l);
PostOrderTraverse(T->r);
printf("%d ",T->v);
}
}
int level_arr[10000];
void func1(Bnode T,int k,int no,int &m)
{
if(T!=NULL)
{
m=m>no?m:no;
level_arr[no]=T->v;
func1(T->l,k+1,no*2+1,m);
func1(T->r,k+1,no*2+2,m);
}
}
void LevelOrderTraverse(Bnode T)
{
memset(level_arr,-1,sizeof(level_arr));
int m=0;
func1(T,1,0,m);
int i;
for(i=0;i<=m;i++)
if(level_arr[i]!=-1)
printf("%d ",level_arr[i]);
}
int main()
{
printf("输入的序列长度:");
int num;
while(scanf("%d",&num)!=EOF) //EOF(end of file)文件结束标志,windows中是ctrl+z
{
for(int i=0;i<num;i++)
scanf("%d",&in[i]);
Bnode t;
int n=0;
CreateBiTree(t,n,num);
printf("前序遍历结果:");
PreOrderTraverse(t);
puts("");
printf("中序遍历结果:");
InOrderTraverse(t);
puts("");
printf("后序遍历结果:");
PostOrderTraverse(t);
puts("");
printf("层序遍历结果:");
LevelOrderTraverse(t);
puts("");
printf("输入的序列长度:");
}
return 0;
}
运行结果
3.序列转换
看着柳婼的blog学的,一个很厉害的大神
当直接给出对二叉树的遍历后的序列,没有结束符号或者其他信息,我们无法只根据前序遍历结果,或者后续遍历结果,或者后序遍历结果来还原二叉树;
但是对于一个前序遍历结果我们可以很容易知道一棵二叉树的根节点的位置(就是序列的第一个树),但此时我们并不能知道它的后序序列哪部分属于左子树,那部分属于右子树;所以此时只需要一个中序遍历的序列就可以得到左右子树的节点数量n1、n2和节点信息;因为我们从前序序列中知道了根节点,所以在中序序列中对应根节点的左侧就是左子树,右侧就是右子树,而根据中序序列的信息我们可以知道前序序列的[1,n1](根节点下标为0)为左子树的前序遍历结果,[n1+1,n1+n2]为右子树的前序遍历结果,接着对左右子树递归这一过程,就可以还原一棵树,或者将其转化为后序序列;
后序遍历与其相同
但是这个方法只对节点信息唯一的树有效,如果有值相同的节点,则中序序列的拆分情况会不唯一!
所以知道前序中序可以推出后序,知道中序后序可以推出前序,知道前序后序并没有用
前序中序转后序
//root代表前序的子树根元素的下标;s,e代表中序的第一个元素的下标和最后一个元素后一个元素的下标
//pre为前序序列,in为中序序列
void func1(int root,int s,int e)
{
if(s<e)
{
int i=s;
while(i<e&&pre[root]!=in[i])i++;
func1(root+1,s,i);
func1(root+i-s+1,i+1,e);
printf("%d ",pre[root]);
}
}
中序后序转前序
//root代表后序的子树根元素的下标;s,e代表中序的第一个元素的下标和最后一个元素后一个元素的下标
//post为后序序列,in为中序序列
void func2(int root,int s,int e)
{
if(s<e)
{
int i=s;
while(i<e&&post[root]!=in[i])i++;
printf("%d ",post[root]);
func2(root-e+i,s,i);
func2(root-1,i+1,e);
}
}
中序后序转层序
只需要在中序后序转前序的时候记录根节点的下标即可,就是在遍历的过程中转化为顺序结构
int res[1000];
void m_func1(int root,int s,int e,int index,int &num)//index为对应下标
{
if(s<e)
{
int i=s;
while(i<e&&post[root]!=in[i])i++;
num=max(num,index);
res[index]=post[root];
m_func1(root-e+i,s,i,index*2+1,num);
m_func1(root-1,i+1,e,index*2+