二叉树讲解升级版

目录

二叉树的存储结构

二叉树结点的查找和修改

二叉树结点的插入

二叉树的创建

二叉树的遍历

先序遍历

中序遍历

后序遍历

层序遍历

重建二叉树

二叉树的静态实现

二叉树的存储结构

一般来说,二叉树使用链表来定义。和普通链表的区别是,由于二叉树每个结点有两条出边,因此指针域变成了两个---分别指向左子树的根结点地址和右子树根节点地址。如果某个子树不存在,则指向NULL,其他地方和普通链表完全相同,因此又把这种链表叫做二叉链表,其定义如下:

struct node{
	typename data;
	node* lchild;
	node* rchild;
};

由于在二叉树建树前根节点不存在,因此其地址一般设为NULL。

node* root=NULL;

而如果需要新建结点(例如往二叉树中插入结点的时候),就可以使用下面的函数:

//生成一个新结点,v为结点权值
node* newNode(int v){
	node* Node=new node;
	Node->data=v;
	Node->lchild=Node->right=NULL;
	return Node;
} 

二叉树的常用操作有以下几个:二叉树的建立,二叉树结点的查找、修改、插入与删除,其中删除操作对不同性质的二叉树区别比较大,这里不做介绍。

二叉树结点的查找和修改

查找操作是指在给定数据域的条件下,在二叉树中找到所以数据域为给定数据域的结点,并将它们的数据域修改为给定的数据域。

需要使用递归来完成查找修改操作。在这里,递归式是指对当前结点的左子树和右子树分别进行递归,递归边界是当前结点为空时到达死胡同。

void search(node* root,int x,int newdata){
	if(root==NULL){
		return;
	}
	if(root->data==x){
		root->data=newdata;
	}
	search(root->lchild,x,newdata);
	search(root->rchild,x,newdata);
}

二叉树结点的插入

二叉树结点的插入位置就是数据域在二叉树中查找失败的位置。而由于这个位置是确定的,因此在递归查找的过程中一定是只根据二叉树的性质来选择左子树或右子树中的一棵子树进行递归,且最后到达空树的地方就是查找失败的地方。

void insert(node* &root,int x){
	if(root==NULL){
		root=newNode(x);
		return;
	}
	if(由二叉树的性质,x应该插在左子树){
		insert(root->lchild,x);
	}
	else{
		insert(root->rchild);
	}
}

二叉树的创建

二叉树的创建其实就是二叉树结点的插入过程,而插入所需要结点的数据域一般都会由题目给出,因此比较常用的写法是把需要插入的数据存储在数组中,然后再将它们使用insert函数一个个插入二叉树中,并最终返回根节点的指针root。

node* Create(int data[],int n){
	node* root=NULL;
	for(int i=0;i<n;i++){
		insert(root,data[i]);
	}
	return root;
}

二叉树的遍历

先序遍历

void preorder(node* root){
	if(root==NULL){
		return;
	}
	printf("%d\n",root->data);
	preorder(root->lchild);
	preorder(root->rchild);
}

中序遍历

void inorder(node* root){
	if(root==NULL){
		return;
	}
	inorder(root->lchild);
	printf("%d\n",root->data);
	inorder(root->rchild);
}

后序遍历

void postorder(node* root){
	if(root==NULL){
		return;
	}
	postorder(root->lchild);
	postorder(root->rchild);
	printf("%d\n",root->data);
}

层序遍历

void layerorder(node* root){
	queue<node*> q;//注意队列里存地址 
	q.push(root);
	while(!q.empty()){
		node* now=q.front;
		q.pop();
		printf("%d",now->data);
		if(now->lchild!=NULL){
			q.push(now->lchild);
		}
		if(now->rchild!=NULL){
			q.push(now->rchild);
		}
	}
}

在这里使用node*型变量,这样就可以通过访问地址去修改原元素,如果使用node型,队列中保存的只是元素的一个副本,因此如果队列中直接存放node型,当需要修改队首元素时,就无法修改

另外还需要指出,如果题目中要求计算出每个结点所处的层次,这时就需要在二叉树结点的定义中添加一个记录层次layer的变量:

struct node{
	int data;
	int layer;
	node* lchild;
	node* rchild;
};

需要在根节点入队前就先令根节点的layer为1来表示根节点在第一层,之后在now->lchild和now->rchild入队前,把它们的层号都记为当前结点now的层号加1

void Layerorder(node* root){
	queue<node*> q;
	root->layer=1;
	q.push(root);
	while(!q.empty()){
		node* now=q.front();
		q.pop();
		printf("%d ",now->data);
		if(now->lchild!=NULL){
			now->lchild->layer=now->layer+1;
			q.push(now->lchild);
		}
		if(now->rchild!=NULL){
			now->rchild->layer=now->layer+1;
			q.push(now->rchild);
		}
	}
}

重建二叉树

给定一棵二叉树的先序遍历序列和中序遍历序列,重建这棵二叉树。

根据二叉树遍历的性质,先序遍历的第一个结点为二叉树的根节点,中序遍历的序列可以根据二叉树的根节点将中序遍历序列划分为左子树和右子树。因此只需要在中序遍历序列中找到根节点的位置,然后划分为两个子树,然后再两个子树中分别递归执行上面的操作。

node* create(int prel,int prer,int inl,int inr){
	if(prel>prer){
		return NULL;
	}
	node* root=new node;
	root->data=pre[prel];
	int k;
	for(k=inl;k<=inr;k++){
		if(in[k]==pre[prel]){
			break;
		}
	}
	int numleft=k-inl;
	root->lchild=create(prel+1,prel+numleft,inl,k-1);
	root->rchild=create(prel+numleft+1,prer,k+1,inr);
	return root;
}

另外如果给定了后序序列和中序序列也可以构建一棵二叉树,做法是一样的。

例题:给出一棵树的后序遍历序列和中序遍历序列,求这棵二叉树的层次遍历序列。

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 50;
struct node{
	int data;
	node* lchild;
	node* rchild;
};
int pre[maxn],in[maxn],post[maxn];
int n;
node* create(int postl,int postr,int inl,int inr){
	if(postl>postr){
		return NULL;
	}
	node* root=new node;
	root->data=post[postr];
	int k;
	for(k=inl;k<=inr;k++){
		if(in[k]==post[postr]){
			break;
		}
	}
	int numleft=k-inl;
	root->lchild=create(postl,postl+numleft-1,inl,k-1);
	root->rchild=create(postl+numleft,postr-1,k+1,inr);
	return root;
}
int num=0;
void bfs(node* root){
	queue<node*> q;
	q.push(root);
	while(!q.empty()){
		node* now=q.front();
		q.pop();
		printf("%d",now->data);
		num++;
		if(num<n){
			printf(" ");
		}
		if(now->lchild!=NULL){
			q.push(now->lchild);
		}
		if(now->rchild!=NULL){
			q.push(now->rchild);
		}
	}
}
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&post[i]);
	}
	for(int i=0;i<n;i++){
		scanf("%d",&in[i]);
	}
	node* root=create(0,n-1,0,n-1);
	bfs(root);
	return 0;
}

二叉树的静态实现

可以使用数组来实现上面的操作,采用静态二叉链表,结点的左右指针域使用int型代替,用来表示左右子树的根节点在数组中的下标。为此需要建立一个大小为结点上限个数的node型数组,所有动态生成的结点都直接使用数组中的结点,所有对指针的操作都改为对数组下标的访问。于是,结点node的定义变为如下:

struct node{
	typename data;
	int lchild;
	int rchild;
}Node[maxn];

在这样的定义下,结点的动态生成就可以转变为如下的静态指定

int index=0;
int newNode(int v){
	Node[index].data=v;
	Node[index].lchild=-1;
	Node[index].rchild=-1;
	return index++;
}

下面给出二叉树的查找、插入、建立的代码。

void search(int root,int x,int newdata){
	if(root==-1){
		return;
	}
	if(Node[root].data==x){
		Node[root].data=newdata;
	}
	search(Node[root].lchild,x,newdata);
	search(Node[root].rchild,x,newdata);
}
void insert(int &root,int x){
	if(root==-1){
		root=newNode(x);
		return;
	}
	if(由二叉树的性质x应该插在左子树){
		insert(Node[root].lchild,x);
	}
	else{
		insert(Node[root].rchild,x);
	}
}
int Create(int data[],int n){
	int root=-1;
	for(int i=0;i<n;i++){
		insert(root,data[i]);
	}
	return root;
}

关于二叉树的先序遍历、中序遍历、后序遍历、层次遍历也做相应的转换:

void Layerorder(int root){
	queue<int> q;
	q.push(root);
	while(!q.empty()){
		int now=q.front();
		q.pop();
		printf("%d ",Node[now].data);
		if(Node[now].lchild!=-1){
			q.push(Node[now].lchild);
		}
		if(Node[now].rchild!=-1){
			q.push(Node[now].rchild);
		}
	}
}
  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值