ToB企服应用市场:ToB评测及商务社交产业平台
标题:
二叉树讲解升级版
[打印本页]
作者:
民工心事
时间:
2024-6-22 13:01
标题:
二叉树讲解升级版
目录
二叉树的存储结构
二叉树结点的查找和修改
二叉树结点的插入
二叉树的创建
二叉树的遍历
先序遍历
中序遍历
后序遍历
层序遍历
重建二叉树
二叉树的静态实现
二叉树的存储结构
一样平常来说,二叉树使用链表来定义。和平凡链表的区别是,由于二叉树每个结点有两条出边,因此指针域酿成了两个---分别指向左子树的根结点地址和右子树根节点地址。如果某个子树不存在,则指向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);
}
}
}
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4