二叉树
(题外话:)在数据结构和算法中,数据结构也就是ADT是组织数据的方式,而算法就是解决问题的流程问题;通常我们说数据结构和算法的优化就是两个方面,即空间和时间,尽可能的使空间和时间最小化。基于以上两个原则,才有了不断在原来的经典的算法之上继续改进和创新。
本主今天讲解一下二叉树,二叉树的应用很广泛,尤其是在后面讲解到查找的算法,二叉树是一个效率很高的数据结构,并且在此基础上延伸到B数,B+树,B*树,红黑树等。
二叉树定义
首先说一下什么是树? 树作为一个常用的数据结构,我们可以暂时理解为数据之间的一对多的关系如图所示。
二叉树在图论中是这样定义的:二叉树是一个连通的无环图,并且每一个顶点的度不大于3。有根二叉树还要满足根结点的度不大于2。有了根结点之后,每个顶点定义了唯一的父结点,和最多2个子结点。
可以把二叉树的定义简单得理解为:如存在根,每个节点的度不大于2,每个结点的孩子结点次序不能任意颠倒(递归定义)。
特殊二叉树
1.斜树
所有结点都只有左子树的二叉树叫左斜树,所有结点都只有右子树的二叉树叫右斜树。斜树的每一层都只有一个结点,结点的个数与斜树的深度相同。
2 满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层上,这样的二叉树称为满二叉树。(上图中所示的二叉树,就是一棵满二叉树)
3 完全二叉树
对一棵具有n个结点的二叉树按层序编号,如果编号为i(1≤i≤n)的结点与同样深度的满二叉树中的编号为i的结点在二叉树中的位置完全相同,则这棵二叉树称为完全二叉树。
二叉树性质
性质1:在二叉树中至多有2^(n-1)个结点(n为树的深度)。可用数学归纳法证明。
性质2:深度为K的二叉树,至多有(2^n ) -1 个结点。
以上两个性质常会在查找树等问题中用到,如求B树的最小深度。
性质3:对任意一个二叉树,如终端结点n0,而度数为2的结点为n2,则有n0=n2+1。
证明:
设二叉树的结点总数为n,度为0的结点即终端结点为n0,度为1的结点为n1,度为2的结点为n2,则有 n = n0 + n1 + n2,从另一方面讲,度为1的结点有一个子结点,度为2的结点有有两个子结点,度为0的结点有0个子结点,则n = 0 * n0 + 1 * n1 + 2* n2 +1 ,这里为什么加1,加1 是由于加上根结点,由上面两个公式可得 n0 = n2+1。
性质4:具有n个结点的完全二叉树的深度为floor(log2n) + 1 。
性质5:如果对一棵有n个结点的完全二叉树(其深度为floor(log2n) + 1)的结点按层序编号,则对任一结点i(1≤i≤n)有:
(1) 如果i = 1,则结点i是二叉树的根,无双亲;如果i > 1,则其双亲PARENT(i)是结点 floor((i)/2)。
(2)如果2i > n,则结点i无左孩子;否则其左孩子LCHILD(i)是结点2i。
(3)如果2i + 1 > n,则结点i无右孩子;否则其右孩子RCHILD(i)是结点2i + 1。
由此性质可知,如果结点的编号为i(假设其左子树、右子树和父结点都存在),则其左子树的编号为2i,右子树的编号为2i +1, 其父节点为i/2。
二叉树逻辑存储结构
二叉树的逻辑结构如图所示,是客观世界的真实反映,反映了在客观世界中数据之间组织的方式。如图所示。
二叉树的物理存储结构
二叉树的物理存储结构即为在二叉树在计算机世界的反映,一般是顺序存储和链式存储。
顺序存储:如图所示
由此可以看出,当二叉树为左斜树,右斜树,会有很多的空间浪费。
链式存储:如图所示
由此可以看出,链式存储虽然能够节约空间,但是在查找效率上回损失时间。因此,有二叉链表拓展到三叉链表,加个指向父节点的指针。提高查找效率,由此可以看出,数据结构和算法总是在空间和时间上寻求平衡,根据具体情况来设计数据结构和算法。
二叉树的操作(c语言实现)
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
typedef char ElemType;
typedef int status; //状态码
#define ERROR 0
#define TRUE 1
/*定义二叉树结构*/
typedef struct Node{
ElemType data;
struct Node *lchild, *rchild; /*左右孩子*/
}BTNode, *BTree;
BTree pre;
/*前序插入*/
void PreOrderInsert(BTree *BT){
char temp;
scanf("%c", &temp);
if(temp =='.')(*BT) = NULL;
else {
(*BT) = (BTNode *) malloc(sizeof(BTNode));
(*BT)->data = temp;
PreOrderInsert(&(*BT)->lchild);
PreOrderInsert(&(*BT)->rchild);
}
}
/*前序遍历*/
void preOrder(BTree T){
if(T!=NULL){
printf("%c",T->data);
preOrder(T->lchild);
preOrder(T->rchild);
}
}
/*中序遍历*/
void InOrder(BTree T){
if(T!=NULL){
preOrder(T->lchild);
printf("%c",T->data);
preOrder(T->rchild);
}
}
/*后序遍历*/
void LastOrder(BTree T){
if(T!=NULL){
preOrder(T->lchild);
preOrder(T->rchild);
printf("%c",T->data);
}
}
/*打印树形结构*/
void PrintTree(BTree BT, int nLayer){
int i = 0;
if(BT == NULL)return ;
PrintTree(BT->rchild, nLayer +1);
for(i = 0;i<nLayer;i++){
printf(" ");
}
printf("%c\n",BT->data);
PrintTree(BT->lchild,nLayer +1);
}
/*树的深度*/
int Depth(BTree BT){
int left = 0, right = 0;
if(BT != NULL){ //自顶向下方法,也可以用从下向顶
left = Depth(BT->lchild) +1;
right = Depth(BT->rchild) +1;
return left > right ? left : right;
}
else return 0;
}
void main(){
BTree T = NULL;
printf("请输入前序顺序空结点以.形式代表:\n");
PreOrderInsert(&T);
printf("根节点的结构\n");
PrintTree(T,1);
printf("树的深度为:%d\n",Depth(T));
}