引言
一直没有系统的学习“树”这一数据结构,导致对一些概念,用法模棱两可,所以现在开始系统的学习一下;
知识点总结
树
树的定义
有若干条节点和若干条边组成的数据结构;
树是一种非线性的数据结构,我们可以类比想像生活中的树,有树枝,树枝分叉处,树叶等;
为什么要使用树
我们来考虑一个问题,有一段线路(长度为n)出现了故障,我们要判断问题出现在那部分,一般方法就是通过一节节的测试,但是这样的话平均需要测n/2次,当n非常大时,复杂度是不能接受的;因此,需要一个更为巧妙的办法==》二分查找;
这样的话,查找的时间复杂度就直接变成了log2n次,复杂度大大降低了,这种方法可以用”树“来实现;
树的特点
- 一棵树有n个节点,则可以确定有且仅有n-1条边;
常见术语
- 节点的度,指节点的子树数;
- 叶节点,度为0的点;
- 树的度,树中最大的节点的度;
二叉树
树的种类有很多种,其中最常用的就是二叉树;
二叉树的定义
树的度<=2的树称为二叉树;且二叉树的左右子树是严格区分的,不可随意改动;
特殊的二叉树
1.满二叉树
像这样的除了叶子节点,其余节点的度都为2的二叉树称为满二叉树;
2.完全二叉树
满二叉树是特殊的完全二叉树;
完全二叉树的性质
- 根节点的编号为1;
- 用数组表示完全二叉树的话,节点编号为i的左右节点存在的话,对应的编号为2i,2i+1;
- 二叉树的i层至多有2^(i-1)个节点;
二叉树的创建
struct node{
int data;
node* lc;
node* rc;//<1>
};
node* newNode(int x){
node* Node=new node;
Node->lc=Node->rc=NULL;//<2>
Node->data=x;
return Node;
}
void insert(node* &root,int x){//<5>
if(root==NULL){
root=newNode(x);
return;
}
if(root->lc==NULL)//<3>
insert(root->lc,x);
else
insert(root->rc,x);
}
int num[100];
node* create(int data[],int n){
node* root=NULL;//<4>
for(int i=0;i<n;i++)
insert(root,data[i]);
return root;
}
上述代码创建出来的二叉树:
对上面的代码一些我自己纠结了很久的地方做个解释
<1>
node* root与node *root是一样的,都是表示root为node类型的指针;千万不要过分纠结(很难受);
<2><4>
都是为了初始化节点,NULL是一个宏定义,表示不指向任何地址的空指针;
<5>
node* &root的写法是为了能够修改二叉树(添加节点),与之相对应的是在遍历操作者就不需要使用&root,因为遍历时不需要对二叉树进行修改;
<3>
目前只会这样创建,太菜了;
二叉树的遍历
**注意:以下先,中,后都是描述根节点的位置的,比如先序遍历的顺序是根节点->左节点->右节
;后序遍历的顺序是左->右->根节点
;
1.先序遍历
void preorder(node* root){
if(root==NULL)
return;
printf("%d ",root->data);
preorder(root->lc);
preorder(root->rc);
}
先序遍历的特点:
先序遍历序列的第一个一定是根节点;
2.中序遍历
void inorder(node* root){
if(root==NULL)
return;
inorder(root->lc);
printf("%d ",root->data);
inorder(root->rc);
}
中序遍历的特点:
只要知道根节点,就能区分左右子树;
1.后序遍历
void postorder(node* root){
if(root==NULL)
return;
postorder(root->lc);
postorder(root->rc);
printf("%d ",root->data);
}
后序遍历的特点:
序列的最后一个是根节点;
总结
只有知道中序遍历序列,才可能唯一确定一棵二叉树,因为先序/后序节点只能确定根节点的位置,不能把左右节点分开;
4.层序遍历
void layerorder(node* root){
queue<node*>q;
q.push(root);
while(!q.empty()){
node* curr=q.front();
q.pop();
cout<<curr->data<<" ";
if(curr->lc!=NULL){
q.push(curr->lc);
}
if(curr->rc!=NULL){
q.push(curr->rc);
}
}
}
重建二叉树
我们经常会见到这样的问题:给定一个二叉树的中序序列,和先序/后序/层次序列的一种,重建这个二叉树;其中,先序等三种序列的作用是确定树的根节点位置,中序遍历序列的作用是区分左右节点;
以下面的例题来解释:
问题描述
定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列。这里假设键值都是互不相等的正整数。
输入样例
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
输出样例
4 1 6 3 5 7 2
问题分析
代码展示
#include<bits/stdc++.h>
using namespace std;
struct node{
int data;
node* lc;
node* rc;//<1>
};
node* newNode(int x){
node* Node=new node;
Node->lc=Node->rc=NULL;//<2>
Node->data=x;
return Node;
}
int post[100],in[100];
vector<int>ans;
node* JudgeByPostAndIn(int PostL,int PostR,int inL,int inR){
//当前树的后序序列:[PostL,PostR],中序序列:[inL,inR]
if(PostL>PostR)//??什么情况会 >
return NULL;
node* root=newNode(post[PostR]);//创建节点!
int k=inL;
while(in[k]!=post[PostR])k+=1;
//找到中序序列中root的位置
int numLeft=k-inL;//左子树个数
root->lc=JudgeByPostAndIn(PostL,PostL+numLeft-1,inL,k-1);
root->rc=JudgeByPostAndIn(PostL+numLeft,PostR-1,k+1,inR);
return root;
}
void layorder(node* root){
queue<node*>q;
q.push(root);
while(!q.empty()){
node* curr=q.front();
q.pop();
ans.push_back(curr->data);
if(curr->lc!=NULL)q.push(curr->lc);
if(curr->rc!=NULL)q.push(curr->rc);
}
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&post[i]);
for(int i=1;i<=n;i++)scanf("%d",&in[i]);
node* root=JudgeByPostAndIn(1,n,1,n);
layorder(root);
for(int i=0;i<ans.size();i++){
if(i!=0)printf(" ");
printf("%d",ans[i]);
}
return 0;
}