一、树
1.树的存储结构-三种表示法
(1)双亲表示
(2)孩子表示法
(3)孩子兄弟表示法
二、二叉树
1.存储结构
(1)顺序存储
(2)链式存储
2.二叉树的建立
//算法5.3 先序遍历的顺序建立二叉链表
void CreateBitTree(BitTree &T){
char ch;//接收树存储的数据
cin>>ch;
if(ch=='#') T=NULL;//若输入为字符为#,递归结束,建立空树
else
{
T=new BitNode;//申请空间,创建结点
T->data=ch;//存储数据,生成根节点
CreateBitTree(T->lchild);//创建左子树(递归)
CreateBitTree(T->rchild);//创建右子树(递归)
};
//一定注意递归调用的函数不要写错了!
}
3.二叉树的遍历
(1)前序遍历(PreOrderTraverse)
//先序遍历输出结点
void PreOrderTraverse(BitTree T){
if(T)
{
Visit(T);//访问根节点
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
(2)中序遍历(InOrderTraverse)
//中序遍历输出结点
void InOrderTraverse(BitTree T){
if(T==0) return;
InOrderTraverse(T->lchild);
Visit(T);//访问根节点
InOrderTraverse(T->rchild);
}
(3)后序遍历(PostOrderTraverse)
//后序遍历输出结点
void PostOrderTraverse(BitTree T){
if(T==0) return;
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
Visit(T);//访问根节点
}
先序、中序、后序遍历的完整代码及运行结果:
//二叉树的链式存储
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <typeinfo>
using namespace std;
//定义存储结构
typedef struct BitNode{
char data;
struct BitNode *lchild,*rchild;
}BitNode,*BitTree;
void Visit(BitTree T){
cout<<T->data;
}
//先序遍历输出结点
void PreOrderTraverse(BitTree T){
if(T)
{
Visit(T);//访问根节点
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
//中序遍历输出结点
void InOrderTraverse(BitTree T){
if(T==0) return;
InOrderTraverse(T->lchild);
Visit(T);//访问根节点
InOrderTraverse(T->rchild);
}
//后序遍历输出结点
void PostOrderTraverse(BitTree T){
if(T==0) return;
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
Visit(T);//访问根节点
}
//算法5.3 先序遍的顺序建立二叉链表
void CreateBitTree(BitTree &T){
char ch;//接收树存储的数据
cin>>ch;
if(ch=='#') T=NULL;//若输入为字符为#,递归结束,建立空树
else
{
T=new BitNode;//申请空间,创建结点
T->data=ch;//存储数据,生成根节点
CreateBitTree(T->lchild);//创建左子树(递归)
CreateBitTree(T->rchild);//创建右子树(递归)
};
//一定注意递归调用的函数不要写错了!
}
//测试用例1:ABDH#K###E##CFI###G#J##
//测试用例2:12#46###3#5##
int main()
{
BitTree T;//T为指向当前根节点的指针
cout<<"请输入构造结点的数值"<<endl;
CreateBitTree(T);
cout<<"树已构造完毕"<<endl;
cout<<"前序遍历结果:"<<endl;
PreOrderTraverse(T);
cout<<endl<<"中序遍历结果:"<<endl;
InOrderTraverse(T);
cout<<endl<<"后序遍历结果:"<<endl;
PostOrderTraverse(T);
}
//总结:
//1、创建、先序、中序、后序都是使用递归的思想;
//2、递归调用的是自己的函数,自己的函数里面调用自己,写代码时注意同步修改!
//测试用例1:ABDH#K###E##CFI###G#J##(树如图1)
//测试用例2:12#46###3#5##(树如图2)
需要注意的是,我们在创建一棵树前,就需要现在草稿纸上画好树的模样,而且注意:
1)“#”不要遗漏,尤其右子树的叶子节点的#容易遗漏,一旦遗漏,无法成功遍历;
2)数字和字母都属于字符,代码中定义data的数据类型为char型,所以输入数字和字母,代码都能完成遍历,无需将data类型改为int,反而会出错。
(图1:《大话数据结构》P178)
(图2:《王道》P132-图5.4)
前序遍历结果:124635 中序遍历结果:264135 后序遍历结果:642531
运行结果:
(4)层序遍历
(5)访问根节点
void Visit(BitTree T){
cout<<T->data;
}
(6)遍历的应用1:用后序遍历思想求:二叉树的深度
先放代码,如下:
这段代码,真是耐人寻味,虽然短短几句,但是理解其本质,还是要下点功夫!本质是后序遍历:左右根,而深度统计的实现,也是基于递归的过程,此处访问根的操作即为:
1.每次对左右子树的深度进行比较,
2.如果左子树的深度l>右子树的深度r,那么就将l+1作为当前根节点构成树的深度作为返回值返回
3.否则(注意这个else语句,否则包括:l<r 和 l=r 两种情况,比如某个结点为叶子结点,那么它的l=r=0,此时递归函数是有返回值的为:r+1,这点很容易被忽略,这也是递归能够持续下去的出发点)就返回r+1;
4.在代码中专门加入输出语句,以观测每次递归的返回值,也即当前结点的深度。
5.对T的理解:一定要明确T是时刻移动更新的,时刻指向新的根节点。假设B是A的孩子,T初始时指向A,比如T=T->Lchild执行后,此时T的指向已经发生改变,指向B,是一个动态递归的过程。
为了更清晰的展示【二叉树深度算法】的执行过程,列出如下表格,读者可跟着演算:
后序遍历结点 访问顺序(当前根结点) | 左子树深度 L | 右子树深度 R | 当前跟结点的深度 Depth(x)=L>R?L+1:R+1 |
K | 0 | 0 | Depth(K)=R+1=1 |
H | 0 | 1 | Depth(H)=R+1=2 |
D | 2 | 0 | Depth(D)=L+1=3 |
E | 0 | 0 | Depth(E)=R+1=1 |
B | 3 | 1 | Depth(B)=L+1=4 |
I | 0 | 0 | Depth(I)=R+1=1 |
F | 1 | 0 | Depth(F)=L+1=2 |
J | 0 | 0 | Depth(J)=R+1=1 |
G | 0 | 1 | Depth(G)=R+1=2 |
C | 2 | 2 | Depth(C)=R+1=3 |
A | 4 | 3 | Depth(A)=L+1=5 |
运行结果如下:
始运行...
请输入树的结点值:
ABDH#K###E##CFI###G#J##
前序遍历的结果是:ABDHKECFIGJ
中序遍历的结果是:HKDBEAIFCGJ
后序遍历的结果是:KHDEBIFJGCA
当前访问结点:K,当前树深度:1
当前访问结点:H,当前树深度:2
当前访问结点:D,当前树深度:3
当前访问结点:E,当前树深度:1
当前访问结点:B,当前树深度:4
当前访问结点:I,当前树深度:1
当前访问结点:F,当前树深度:2
当前访问结点:J,当前树深度:1
当前访问结点:G,当前树深度:2
当前访问结点:C,当前树深度:3
当前访问结点:A,当前树深度:5运行结束。