二叉树的定义:
二叉树是每个结点最多有两个子树的树结构。
二叉树的重要性质:
对于任何非空二叉树,n0表示叶子结点数,n1表示度为1的结点数,n2表示度为2的结点数,满足关系n0=n2+1。
二叉树的实现、主要操作
typedef struct node *tree;
struct node{
ElementType data;
*tree left;
*tree right;
};
(1)新建结点:
tree Create(ElementType v)
{
tree T=new node;
T->data=v;
T->left=T->right=NULL;
return T;
}
(2)查询并修改结点:
tree FindAndChange(tree Tree,ElementType V,ElementType newV)
{
if(Tree==NULL) return;
if(Tree->data==V){
Tree->data=newV;
return;
}
FindAndChange(Tree->left,V,newV);
FindAndChange(Tree->right,V,newV);
}
(3)插入结点:
void Insert(tree & Tree,ElementType V)
{
tree newT=Create(V);
if(Tree==NULL){
Tree=newT;
return;
}
if(插入到左子树的条件)
Insert(Tree->left,V);
else
Insert(Tree->right,V);
}
- 关键点在于传入的树结构是引用,因为插入的地方原来必然是没有结点的,如果想通过函数中的赋值改变传入函数的变量值就必须要用引用。这也体现了引用的改变可以相应改变其引用对象。
(4)建立二叉树:
其实就是建立一个空树,然后把一个容器内的东西插入到空树中。
tree BuildTree(ElementType data[],int N)
{
tree Tree=NULL;
for(int i=0;i<N;i++)
Insert(Tree,data[i]);
return Tree;
}
(5)判断树空:
bool Empty(tree Tree)
{
if(Tree==NULL)
return true;
else
return false;
}
二叉树的遍历:
(1)先序遍历:根左右
对于每个父节点和其两个子节点(如果有的话)按照父节点、左节点、右节点的顺序遍历:
void PreTraveral(tree Tree)
{
if(Tree!=NULL){
cout<<Tree->data;//或者其他操作。。。
PreTraveral(Tree->left);
PreTraveral(Tree->right);
}
}
(2)中序遍历:左根右
对于每个父节点和其两个子节点(如果有的话)按照左节点、父节点、右节点的顺序遍历:
void InTraveral(tree Tree)
{
if(Tree!=NULL){
InTraveral(Tree->left);
cout<<Tree->data;//或者其他操作。。。
InTraveral(Tree->right);
}
}
(3)后序遍历:左右根
对于每个父节点和其两个子节点(如果有的话)按照左节点、右节点、父节点的顺序遍历:
void PostTraveral(tree Tree)
{
if(Tree!=NULL){
PostTraveral(Tree->left);
PostTraveral(Tree->right);
cout<<Tree->data;//或者其他操作。。。
}
}
(4)层序遍历:
从根节点开始从上到下、从左到右遍历:
void levelTraveral(tree Tree)
{
if(Tree==NULL) return;
queue<node> T;
tree tmp;
T.push(tmp);
while(!T.empty(){
tmp=T.front();
cout<<tmp->data;
if(tmp->left!=NULL) T.push(Tree->left);
if(tmp->right!=NULL) T.push(Tree->right);
T.pop();
}
(5)能否三种遍历中的任意两种求另一种?
首先我们分析每种遍历的特点:
先序遍历:根左右——第一个被发现的是根节点
中序遍历:左根右——根节点一定在中间被发现
后序遍历:左右根——最后一个被发现的是根节点
显然如果只知道根节点的位置是无法确定其他节点的具体情况的,所有我们不能根据先序遍历和后序遍历求中序遍历,但可以根据中序遍历加任意一种求另外一种。
举个例子:(自己画体会!!!)
对于层序遍历是1、2、3、4、5、6、7的树,它的三种遍历分别为:
先序遍历:根左右 1 2 4 5 3 6 7
中序遍历:左根右 4 2 5 1 6 3 7
后序遍历:左右根 4 5 2 6 7 3 1
如果我们根据先序遍历和中序遍历求后序遍历:
首先确定根节点为1,把中序遍历分成根节点左子树一侧的4 2 5,右子树一侧的6 3 7,左子树一侧的根节点为2,得到左子树的左子树为4和右子树为5,右子树一侧的根节点为3,得到左子树的左子树为6和右子树为7,按后序遍历的顺序左右根就是4 5 2 6 7 3 1
方法就是一直根据先序或后序确定每个父节点,再放入到中序遍历中,左侧的左子树,右侧的为右子树。
???有没有代码不是像下面这样构造出原来的树再后序遍历而是能直接得到后序遍历的方法呢???
//给定一棵二叉树的前序遍历和中序遍历,求其后序遍历
#include <iostream>
#include <string.h>
using namespace std;
typedef struct BinTree{
char data;
BinTree* lchild;
BinTree* rchild;
} BinTree;
void RebuildTree(BinTree* &Tree,char *pre,char *in,int len)
{
Tree = new BinTree;
if(Tree!=NULL){
if(len<=0){//递归截止条件
Tree = NULL;
return ;
}
int index = 0;
while(index<len&&*(pre)!=*(in+index)) //寻找当前的root结点(包含子树)
index++;
Tree->data = *(pre);
RebuildTree(Tree->lchild,pre+1,in,index);//去掉root结点
RebuildTree(Tree->rchild,pre+1+index,in+1+index,len-(index+1));//去掉左边和根节点
}
return ;
}
void PostOrderTravese(BinTree* Tree){
//后序遍历输出
if(Tree==NULL)
return;
PostOrderTravese(Tree->lchild);
PostOrderTravese(Tree->rchild);
cout<<Tree->data<<" ";
}
int main()
{
char pre[101]; //string pre;
char in[101]; //string in;
cout<<"Inuput DLR and LDR:"<<endl;
while(cin>>pre>>in)
{
BinTree* tree;
int length = strlen(pre);
RebuildTree(tree,pre,in,length);
PostOrderTravese(tree);
cout<<endl;
}
return 0;
}
摘自https://blog.csdn.net/zqxdsy/article/details/102576570
(6)由中序和后序还原二叉树:
#include <iostream>
#include <conio.h>
using namespace std;
struct node
{
int data;
struct node *lt, *rt;
};
struct node *create_by_post_and_mid(int n, int *post, int *mid)
{
struct node *root;
int i;
if(n==0)
return NULL;
root=(struct node *)malloc(sizeof(struct node));
root->data=post[n-1];
for(i=0;i<n;i++) // 寻找左右子树的元素
if(post[n-1]==mid[i])
break;
root->lt = create_by_post_and_mid(i, post, mid); // 建立左子树
root->rt = create_by_post_and_mid(n-i-1, post+i, mid+i+1); // 建立右子树
return root;
}
// 前序遍历
void preorder(struct node *root)
{
if(root)
{
printf("%d ", root->data);
preorder(root->lt);
preorder(root->rt);
}
}
int main()
{
int N;
cin>>N;
struct node *root=NULL;
int post[30],in[30];
for(int i=0;i<N;i++)
cin>>in[i];
for(int i=0;i<N;i++)
cin>>post[i];
root=create_by_post_and_mid(N,post,in);
preorder(root);
getch();
return 0;
}
(7)由先序和后序求可能的中序:
/*
用unique标记是否唯一,如果为1就表示中序是唯一的~
已知二叉树的前序和后序是无法唯一确定一颗二叉树的,
因为可能会存在多种情况,这种情况就是一个结点可能是根的左孩子也有可能是根的右孩子,
如果发现了一个无法确定的状态,置unique = 0,又因为题目只需要输出一个方案,
可以假定这个不可确定的孩子的状态是右孩子,接下来的问题是如何求根结点和左右孩子划分的问题了,
首先我们需要知道树的表示范围,需要四个变量,
分别是前序的开始的地方prel,前序结束的地方prer,后序开始的地方postl,后序结束的地方postr,
前序的开始的第一个应该是后序的最后一个是相等的,这个结点就是根结点,以后序的根结点的前面一个结点作为参考,
寻找这个结点在前序的位置,就可以根据这个位置来划分左右孩子,递归处理~
*/
#include <iostream>
#include <vector>
using namespace std;
vector<int> in, pre, post;
bool unique = true;
void getIn(int preLeft, int preRight, int postLeft, int postRight) {
if(preLeft == preRight) {
in.push_back(pre[preLeft]);
return;
}
if (pre[preLeft] == post[postRight]) {
int i = preLeft + 1;
while (i <= preRight && pre[i] != post[postRight-1]) i++;
if (i - preLeft > 1)
getIn(preLeft + 1, i - 1, postLeft, postLeft + (i - preLeft - 1) - 1);
else
unique = false;
in.push_back(post[postRight]);
getIn(i, preRight, postLeft + (i - preLeft - 1), postRight - 1);
}
}
int main() {
int n;
scanf("%d", &n);
pre.resize(n), post.resize(n);
for (int i = 0; i < n; i++)
scanf("%d", &pre[i]);
for (int i = 0; i < n; i++)
scanf("%d", &post[i]);
getIn(0, n-1, 0, n-1);
printf("%s\n%d", unique == true ? "Yes" : "No", in[0]);
for (int i = 1; i < in.size(); i++)
printf(" %d", in[i]);
printf("\n");
return 0;
}
(8)遍历的应用:
- 输出二叉树的叶子结点:
void Printleave(tree Tree)
{
if(Tree!=NULL){
if(!Tree->left&&!Tree->right)
cout<<Tree->data;
}else{
return;
}
Putleave(Tree->left);
Putleave(Tree->right);
}
- 求二叉树的高度:
只要知道左右子树的高度就能知道树的高度,我们可以想象斐波那契数列,递归到最初的0和1来求第n项,而这里就是递归到叶子结点。
故根据后序遍历来递归得到树的高度!!!!
int Heigh(tree Tree)
{
int maxh,hl,hr;//左子树高度hl,右子树高度hr
if(Tree){
hl=Heigh(Tree->left);
hr=Heigh(Tree->right);
maxh=max(hl,hr)+1;
return maxh;
}else{
return 0;
}
}