作为数据结构的基础,树分很多种,像AVL树、红黑树、二叉搜索树....今天我想分享的 Chat 是关于二叉树,一种基础的数据结构类型,能大家一起植树,我超开心的!
通过本场 Chat,你将收获:
- 二叉树的原理推敲与性质探索;
- 一起动手种下一棵漂亮的二叉树;
作为后续分享的《基于 K- 近邻算法的 KD-tree》所需的一个基础知识,二叉树可是很重要的!
什么是树
在《数据结构》[注1] 中树有如下定义:
$$树(Tree) 是 n(n≥0) 个结点的有限集$$
在此我对上述定义做出如下解释:
当 $n=0$ 时,为空树,树的深度与高度均为 $0$,是树的一种特例;当 $n>0$ 时,为非空树,树的第一个结点,即深度为 $1$ 的结点,我们称其为根结点,由根结点可以引出若干子树分支,同时子树分支可依此向下延伸,此时树的深度与高度也在变化,即树状图。
这里我们需要厘清树的深度与树的高度与其他树的术语:
- 树的深度:树中结点的最大层次
- 树的高度:从叶子结点开始定义,叶子结点为第一层,往上依次递增,直至根结点。
- 结点:树的结点包含一个数据元素以及若干指向其子树的分支
- 度:结点所拥有的子树数量
- 终端节点:度为0的结点称为叶子结点或终端结点
- 树的度:树中各结点度的最大值
- 层次:从根开始定义,根为第一层,依次递增
- 有序树:树中结点的各子树从左往右是有次序的,不可相互交换;反之则是无序树
- 森林:一棵非空树删掉根结点,即是森林
二叉树的概念引入
二叉树是由树演化而来的一种数据结构,上面所有术语均适用于二叉树。二叉树与树不同之处在于,树的每一个结点(除终端结点外)允许有若干子树分支;而二叉树只允许有左右两个子树分支,即不存在度大于 $2$ 结点。
C语言示例:
struct Tree{ int data; struct Tree *left; struct Tree *right;};
上面的示例清晰地阐明了二叉树的结点是由一个数据元素和两个子树分支构成,需要明确的是,虽然终端结点没有指向任何子树,但它仍旧有往下繁衍的能力。
除此之外,二叉树还是一棵有序树,它的各个结点从左到右是依次有序可循的,且不可交换;它具有以下五种形态:
- 空树
- 仅有根结点
- 左子树为空
- 右子树为空
- 左右子树均非空
当二叉树处于第五种状态,且设树的深度为 $n$,总结点数为 $2^n-1$ 时,我们称其为满二叉树。
事实上还有另外一种也处于第五状态的树——完全二叉树。由于完全二叉树的定义在每个版本的教科书中均不相同,而笔者只接触过《数据结构 · 严蔚敏版》,因此摘录此书中对完全二叉树的定义:
深度为 k 的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应时,称之为完全二叉树。
这段描述我读了两遍,方才理解其中的深刻含义,我们把深度为 $3$ 的满二叉树的每个结点从上往下,从左往右进行编号:
然后我们再定义一棵深度也为 $3$ 的二叉树,该二叉树的 $n$ 个结点($n≤7$),当从 1 到 n 的每个结点都与上图中的编号结点一一对应时,这二叉树就称为完全二叉树。
举个例子,当 $n=5$ 时:
这便是完全二叉树。
因此我们还可以得到一个推论:满二叉树是完全二叉树,但完全二叉树不一定是满二叉树。
当二叉树处于第三种状态时,称其为右斜树。
同理,处于第四状态为左斜树。
二叉树的性质总结
二叉树的第 $i$ 层上最多有 $2^{i-1}$ 个结点。此性质可通过上面满二叉树的图示推得。
深度为 $n$ 的二叉树,最多拥有 $2^n-1$ 个结点。此性质可以通过数列求和得出:$$S_n=\sum_{i=1}^{n}2^{i-1}=2^n-1$$
设满二叉树深度为 $n$,叶子结点数必为 $2^{n-1}$
设任意一棵二叉树的叶子结点数为 $n_0$,度为 1 的结点数为 $n_1$,度为 2 的结点数为 $n_2$;总结点数为 $n$。则有:$$n=n_0+n_1+n_2$$设分支的总边数为 $x$,则有:$$x=n_1+2n_2$$$$n=x+1$$ 联立上述三式可得:$$n_0=n_2+1$$ 即任意二叉树的叶子结点数为该树中度为 $2$ 的结点数的总和加一。
设一完全二叉树具有 $n$ 个结点,则其深度必为 $[log_2^n]+1$,$[x]$ 表示不大于 x 的最大整数,即向下取整。
手把手建立二叉树
C 语言示例:
#include<stdio.h>#include<malloc.h>//构建结点域struct Bin_tree{ int data; struct Bin_tree *left; struct Bin_tree *right;};typedef struct Bin_tree node;/*定义插入函数模块当结点不存在时,生成新的结点并比较左右顺序插入树中*/void write(node **tree,int value){ node *temp=NULL; if(!(*tree)){ temp=(node *)malloc(sizeof(node)); temp->left=temp->right=NULL; temp->data=value; *tree=temp; return; } if(value<(*tree)->data){ write(&(*tree)->left,value); }else if(value>(*tree)->data){ write(&(*tree)->right,value); }}//先序遍历void fir_read(node *tree){ if(tree){ printf("%d\n",tree->data); fir_read(tree->left); fir_read(tree->right); }}//中序遍历void mid_read(node *tree){ if(tree){ mid_read(tree->left); printf("%d\n",tree->data); mid_read(tree->right); }}//后序遍历void las_read(node *tree){ if(tree){ las_read(tree->left); las_read(tree->right); printf("%d\n",tree->data); }}//测试模块int main(){ node *tree; node *temp; tree=NULL; write(&tree,9); write(&tree,5); write(&tree,13); write(&tree,2); write(&tree,6); write(&tree,11); write(&tree,17); printf("先序遍历!\n"); fir_read(tree); printf("中序遍历!\n"); mid_read(tree); printf("后序遍历!\n"); las_read(tree); return 0;}
其中需要指明的是二叉树的三种遍历方法:先序遍历、中序遍历、后序遍历。
先序遍历
即遍历顺序为“根—>左->右”。
中序遍历
即遍历顺序为“左—>根—>右”,由于二叉树为有序树,因此中序遍历输出的值由小到大的。
后序遍历
即遍历顺序为“左—>右—>根”。
还有一种遍历法,称为层序遍历,有兴趣的读者可以尝试着写一下。
参考资料
注1:《数据结构 C 语言版》严蔚敏
本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。
阅读全文: http://gitbook.cn/gitchat/activity/5b18ed5002fa96300bc92d92
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。