数据结构之二叉树(c语言版)
之前的都是线性结构,而树结构在计算机应用中的应用更加广泛。linux中的目录结构,某些数据库的底层存储等,都是采用树结构进行构架的。
树的概念
线性表是一对一的关系,而树是一对多的关系。
树的结点:包含一个数据元素及若干指向子树的分支;
孩子结点:结点的子树的根称为该结点的孩子;
双亲结点:B 结点是A 结点的孩子,则A结点是B 结点的双亲;
兄弟结点:同一双亲的孩子结点; 堂兄结点:同一层上结点;
祖先结点: 从根到该结点的所经分支上的所有结点
子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙
结点层:根结点的层定义为1;根的孩子为第二层结点,依此类推;
树的深度:树中最大的结点层
结点的度:结点子树的个数
树的度: 树中最大的结点度。
叶子结点:也叫终端结点,是度为 0 的结点;
分枝结点:度不为0的结点;
有序树:子树有序的树,如:家族树;
无序树:不考虑子树的顺序;
二叉树
最常用的树结构是二叉树。在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”。
1. 二叉树的性质
二叉树有以下几个性质:TODO(上标和下标)
性质1:二叉树第i层上的结点数目最多为 2****{i-1} (i≥1)。
性质2:深度为k的二叉树至多有2{k}-1个结点(k≥1)。
性质3:包含n个结点的二叉树的高度至少为log2 (n+1)。
性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1。
2、二叉树的种类
完全二叉树
若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
满二叉树
除了叶子结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树
平衡二叉树
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
3、二叉树的遍历方式
先序遍历
先访问根节点,先序遍历左子树,先序遍历右子树
中序遍历
中序遍历左子树,访问根节点,中序遍历右子树
后序遍历
后序遍历左子树,后序遍历右子树,访问根节点
层次遍历
即按照层次访问,通常用队列来做。访问根,访问子女,再访问子女的子女
4、二叉树的存储方式
1、顺序存储
用一组连续的存储单元存放二叉树的数据元素。结点在数组中的相对位置蕴含着结点之间的关系。
比较浪费空间,基本不用。
2、链式存储
链式存储结构的每个结点由数据域、左指针域和右指针域组成。左指针和右指针分别指向下一层的二叉树。
二叉树的实现
使用链式结构来作为底层:
先初始化树,以先序遍历的方式创建树,输入的值为0时代表该节点为NULL,通过递归实现节点的建立:
#include<stdio.h>
#include<stdlib.h>
typedef struct tree{
int data;
struct tree *lchild,*rchild;
}Tree;
//初始化树,先序
Tree *init(Tree *root){
root=NULL;
int p=0;
printf("input data\n");
scanf("%d",&p);
if(0!=p){
root=(Tree *)malloc(sizeof(Tree));
root->data=p;
printf("The data is %d\n",p);
printf(" 请输入左字树data \n");
root->lchild=init(root->lchild);
printf(" 请输入右字树data \n");
root->rchild=init(root->rchild);
}
return root;
}
三种遍历方式:
//先序遍历
void preOrder(Tree *root){
if(root==NULL){
return ;
}
printf("%d ",root->data);
preOrder(root->lchild);
preOrder(root->rchild);
}
//后序遍历
void lastOrder(Tree *root){
if(root==NULL){
return ;
}
lastOrder(root->lchild);
lastOrder(root->rchild);
printf("%d ",root->data);
}
//中序遍历
void midOrder(Tree *root){
if(root==NULL){
return ;
}
midOrder(root->lchild);
printf("%d ",root->data);
midOrder(root->rchild);
}
为了测试,我们需要一个树,如下图所示。因为是先序遍历建立的树,所以输入时候应该是这个顺序:
123456
当然这样输入肯定不行,因为空节点也是需要0来占位的,所以输入方式应该是这样的:
1230004500600
在代码中测试一下:
分别输出三种遍历方式:
int main(){
Tree *p=NULL;
Tree *root=init(p);
printf("ready preOrder:\n");
preOrder(root);
printf("\n");
printf("ready lastOrder:\n");
lastOrder(root);
printf("\n");
printf("ready midOrder:\n");
midOrder(root);
}
按照规则输入,得到数据:
input data
1
The data is 1
请输入左字树data
input data
2
The data is 2
请输入左字树data
input data
3
The data is 3
请输入左字树data
input data
0
请输入右字树data
input data
0
请输入右字树data
input data
0
请输入右字树data
input data
4
The data is 4
请输入左字树data
input data
5
The data is 5
请输入左字树data
input data
0
请输入右字树data
input data
0
请输入右字树data
input data
6
The data is 6
请输入左字树data
input data
0
请输入右字树data
input data
0
ready preOrder:
1 2 3 4 5 6
ready lastOrder:
3 2 5 6 4 1
ready midOrder:
3 2 1 5 4 6
对于深度,节点数目可以这样:
/*计算树的深度*/
int deep(Tree* H){
int d1 = 0;
int d2 = 0;
if(NULL!=H){
d1 = deep(H->lchild) +1;
d2 = deep(H->rchild) +1;
}
return d1>=d2? d1:d2;
}
//计算叶子节点
int CountLeaf(Tree* H){
if(NULL==H) return 0;
if((NULL==H->lchild)&&(NULL==H->rchild)){
return 1;
}
return CountLeaf(H->lchild) + CountLeaf(H->rchild);
}
关于插入删除则有许多选择,插入的位置可以是任意的。
比如插入一个7,要求以先序遍历为1234567,那么可以插入x或者y的位置。
如果要求之后插入的元素都以先序遍历的方式在后面添加,那么甚至可以让元素只作为y后的右子树。
因为树结构是一对多的形式,所以即使同一种遍历方式也有不同的插入位置。