2.二叉树
二叉树是n个结点的有限集合,该集合或者为空集(称为空二叉树),或者有一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成
特点:
①每个结点
最多有两棵子树,所以二叉树中不存在度 大于2的结点
②左子树和右子树是有顺序的,次序不能任意颠倒
③即使树中某结点只有一棵子树,也要区分它是左子树还是右子树
5种基本形态:
①空二叉树
②只有一个根节点
③根结点只有左子树
④根结点只有右子树
⑤根结点既有左子树又有右子树
特殊二叉树
①斜树:所有的结点都只有左子树的二叉树叫左斜树。所有及诶单都是只有右子树的二叉树叫右斜树。两者统称为斜树。线性表就可以理解为是树的一种极其特殊的表现形式
②
满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树
特点:叶子只能出现在最下面一层;非叶子结点的度一定是2;在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多
③
完全二叉树:对一棵具有n个结点的二叉树按层序编号,如果编号为i 个结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这课二叉树称为完全二叉树9个人理解就是满二叉树的(完全二叉树的最后一排不完全,缺少右面的结点0)
完全二叉树的特点:
①叶子结点只能出现在最下两层
②最下层的叶子一定集中在左部全部连续位置
③倒数第二,若有叶子结点,一定都在右部连续位置
④如果结点度为1,则该及诶单只有左孩子,即不存在只有右子树的情况
⑤同样结点树的二叉树,完全二叉树的深度最小
3.二叉树的性质
①在二叉树的第i层上至多有2^(i-1)个结点
②深度为k的二叉树至多有2^k - 1 个结点
③对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2 + 1
④具有n个结点的完全二叉树的深度为【log
2n】+ 1(【x】表示不大于x的最大整数)
⑤如果对 一棵有n个结点的完全二叉树(其深度为【log
2n】+1 也就是结点为n )的结点按层序编号,对任一结点i (1<=i <=n)有
如果 i= 1,则结点i是二叉树的根,无双亲;如果 i > 1,则其双亲是结点【i/2】
如果 2i > n ,则结点i无左孩子;否则其左孩子是结点 2i
如果 2i + 1 > n,则结点i无右孩子;否则其右孩子是结点 2i + 1
4.二叉树的存储结构
顺序存储结构一般只用于完全二叉树,因为如果是一颗深度为k的右斜树,则要分配2^k -1 个存储单元空间
二叉链表
二叉树每个结点最多有两个孩子,所以为它涉及一个数据域和两个指针域
lchild | data | rchild |
5.二叉树的遍历
二叉树的遍历是指从根结点出发,按照某种
次序依次
访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次
二叉树遍历方法
①前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,然后前序遍历右子树。ABDGHCEIF ——
根结点 - 左子树 - 右子树
②中序遍历:若树为空,则空操作返回,否则从根结点开始(并不是先访问根结点),中序遍历根结点的左子树,然后访问根结点,最后中序遍历右子树GDHBAEICF ——
左子树 - 根结点 - 右子树
③后序遍历:
从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点,GHDBIEFCA ——
左右子叶-根结点
④层序遍历:从树的第一层,也就是从根结点开始访问,从上而下逐层比那里,在同一层,按从左到右的顺序对节点逐个访问ABCDEFGHI
把树中的结点变成某种意义的线性序列
二叉树的定义是用递归的方式,所以实现遍历算法也可以采用递归
①前序遍历算法
void preOderTraverse(bigtree T){
if(T == NULL)
return;
printf();
preOderTrverse(T->lchild);
preOderTrverse(T->rchild);
}
②
中序遍历算法
void preOderTraverse(bigtree T){
if(T == NULL)
return;
preOderTrverse(T->lchild);
printf();显示结点数据,可以更改为其他对结点操作
preOderTrverse(T->rchild);
}
③后序遍历算法
void preOderTraverse(bigtree T){
if(T == NULL)
return;
preOderTrverse(T->lchild);
preOderTrverse(T->rchild);
printf();0
}
已知前序比那里序列和中序遍历序列,可以唯一确定一棵二叉树;已知后序遍历序列和中序遍历序列,可以唯一确定一棵二叉树;已知前序和后序遍历,是不能确定一棵二叉树的
关于二叉树顺序存储结构,其实就是用数组来存储数据,根据满二叉树的特点用下标来处理其关系
处理二叉树链表就是使用递归函数进行处理
#include<stdio.h>
#include<malloc.h>
#define ERROR 0
#define OK 1
#define FALSE 0
#define TRUE 1
typedef int type;//结构体中数据域中的类型
typedef int Status;
typedef struct treeNode//结构体
{
type data ; //数据
treeNode *lchild;//左指针
treeNode *rchild;//右指针
}treeNode,*tree;
Status initTree(treeNode *node){//初始化树
node = NULL;
return OK;
}
/*
错误代码:一开始我用一重指针来创建树,可是发现创建不了,会出错,后来发现原因——创建二叉树的时候采用递归的方式,如果每次传的是一重指针,当函数回调的时候,参数指针是局部变量,其生命周期结束,
所以指向的那块内存将不能与上一级结点联系起来
为什么动态链表的时候插入时,传入一级指针可以??
——因为传入的是指针,实际传入的是指针指向那块内存的地址,实际就操作的是指针指向的那块地址。而二叉树的最后指针并没有指向一块内存空间,所以不能够使用一重指针来创建
void create(treeNode *tr){
type data ;
printf("请输入结点的值,类型为数字\n");
scanf("%d",&data);
if(data == 0 ){
tr = NULL;
}
else{
tr = (treeNode*)malloc(sizeof(treeNode));
if(tr){
tr->data = data;
create(tr->lchild);
create(tr->rchild);
}
}
}
*/
void create(tree *tr){
type data ;
printf("请输入结点的值,类型为数字\n");
scanf("%d",&data);
if(data == 0 ){
*tr = NULL;
}
else{
*tr = (treeNode*)malloc(sizeof(treeNode));
if(*tr){
(*tr)->data = data;
create(&(*tr)->lchild);
create(&(*tr)->rchild);
}
}
}
/*
当销毁树时,与上面的道理相似,也需要二重指针,如果是一重指针最后销毁的是局部参数指针,因为最后一级指针没有指向一块内存空间,所以不能被销毁
*/
Status destroy(tree *tre){
if(*tre ){//如果不为空
if((*tre)->lchild){
destroy(&(*tre)->lchild);//销毁左子树
}
if((*tre) ->rchild){
destroy(&(*tre)->rchild);//销毁右子树
}
free(*tre);
*tre = NULL;
}
return OK;
}
int treeDepth(treeNode* tree){ //计算树的深度
int i = 0;
int j = 0;
if(tree){
if(tree->lchild){
i = treeDepth(tree->lchild);
}
if(tree->rchild){
j = treeDepth(tree->rchild);
}
return i>j ? i+1:j+1;
}
else
return 0;
}
void preOrderTraverse(treeNode* tree){//前序遍历二叉树
if(tree){
printf("%d\t",tree->data);
preOrderTraverse(tree->lchild);
preOrderTraverse(tree->rchild);
}
}
void main(){
tree tre;
create(&tre);
printf("该树的深度为:%d\n",treeDepth(tre));
preOrderTraverse(tre);
destroy(&tre);
printf("该树的深度为:%d\n",treeDepth(tre));
preOrderTraverse(tre);
}