什么是二叉树
[二叉树] 顾名思义就是有两个分支节点的树,不仅如此,除了叶子外的所有节点都具有两个分支节点;
由于结构像一棵倒立的树,顾名思义为二叉树;
如下图所示,该图即为一棵野生的二叉树;
既然二叉树为树,固然有着和树一样的部分(叶子、根、分支…)
这些也成为了树相关的概念;
树相关的概念
- 叶子节点或者终端节点
叶子节点或终端节点,顾名思义就是最底端的节点,该节点不存在分支,故被称为叶子;
- 节点的度
节点的度即为一个节点含有的子树成为该节点的度,如图所示,节点C的度为3;
- 双亲结点(父节点)
若是一个节点存在子节点,则该节点成为该子节点的父节点;如上图所示,A节点既是B的父节点,也是C的父节点;
- 兄弟节点
具有相同父节点的节点成称为兄弟节点,“双亲结点”中所提到的“A结点既是B的父节点,也是C的父结点”处中的BC节点即为兄弟节点。
- 树的度
树的度即为一棵树中,最大的节点的度,如“结点的度”中的C节点,该节点为整棵树中度最大的结点为3,故该树的度为3;
- 树的高度或深度
树中结点的最大层次即为树的高度,如上图所示,树的高度为3;
- 堂兄弟节点
双亲在同一层的节点互为堂兄弟,如上图所示,E节点和F节点护卫堂兄弟节点;
- 节点的祖先
从根到节点所经分支上的所有节点,如上图所示,A节点为所有节点的祖先;
- 子孙
以某节点为根的子树中任意一个节点都称为该节点的子孙;
如上图所示,除A以外的所有其他节点都为A节点的子孙;
- 森林
由m(m>0)棵互不相交的树的集合称为森林;
树的表示形式
树的结构与线性表相比就会更加复杂,既要保存值域,同时也要保存树中节点与节点之间的关系;在树中有许多的表示方法(双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法等等),在此就不做过多赘述;
既然树的结构如此复杂,那对于真正实际中,树有什么应用呢?大家可以将计算机中的文件夹进行比较,从一个文件夹可以分支出很多个文件夹,文件夹内还可以继续存放文件夹,如此;
在Linux操作系统就应用了文件目录树,目录树的起点是根目录,Linux文件系统中的每一文件在此目录树中的文件名都是独一无二的,因为其包含从根目录开始的完整路径;
同时在很多算法中,都运用到了树;
特殊的二叉树
二叉树在树中也算是一个比较特殊的树种,但在二叉树中也存在着特殊的二叉树,即为完全二叉树与满二叉树;
- 满二叉树
一个二叉树,如果每一层的节点树都达到最大值,则这个二叉树就是满二叉树;也就是说,如果一个二叉树的层数为k,且节点总数是2^k-1,则它就是满二叉树; - 完全二叉树
完全二叉树是一种效率很高的树,是由满二叉树引申出来的。对于深度为k的,有n个节点的二叉树,当且仅当其每个节点都与深度k的满二叉树中编号从1至n的节点一一对应时称为完全二叉树,同时满二叉树也是一种特殊的完全二叉树。
如何创造出一棵二叉树
在不使用任何算法的前提下若是想得到一棵二叉树可以使用比较简单粗暴的方式:
将各个节点创建并将每个节点连接在一起(该方法适用于任何一种树);
根据树的结构我们可以得知,树既要保存值域,同时也要保存节点之间的关系,又因为为二叉树(每个节点必有两个分支)
假设需要创建一个如图所示的二叉树
在此处可以定义一个结构体,并在结构体内存放结构体指针用来存放左右两个子树,同时创建一个成员变量用来存放所需要存储的值域;
typedef char BTDataType; typedef struct BinaryNode { struct BinaryNode*left; struct BinaryNode*right; BTDataType a; }BTNode;
根据图所示可知,A节点的子节点为B、C;
B节点的子节点为D、E;
C节点的子节点为F、G;
int main() { BTNode q1; BTNode q2; BTNode q3; BTNode q4; BTNode q5; BTNode q6; BTNode q7; q1.a = 'A'; q2.a = 'B'; q3.a = 'C'; q4.a = 'D'; q5.a = 'E'; q6.a = 'F'; q7.a = 'G'; q1.left = &q2; q1.right = &q3; q2.left = &q4; q2.right = &q5; q3.left = &q6; q3.right = &q7; q4.left = q4.right = q5.left = q5.right =q6.left = q6.right = q7.left = q7.right =NULL; return 0; }
二叉树的遍历
二叉树的遍历分为先序遍历,中序遍历,后序遍历以及层序遍历;
为什么会叫先中后,顾名思义,这里的先中后为遍历过程中根节点的访问顺序,一棵树的任意子树都可以看成根节点和子树;
至于层序遍历,即为一层一层的进行访问;
首先设存在一棵二叉树:
先序遍历(前序遍历)
先序遍历的遍历方式为:根、左子树、右子树;
按照该树可以得出,该树的前序遍历为:A,B,D,E,C,F,G
根据根、左子树、右子树的顺序可知,最先访问的必定是A节点,当A节点访问完即访问左子树,而左子树的根节点为B,B访问结束访问B的左子树,为D,D没有左右子树(为空)故返回访问B的右子树,E与D同理,而后B访问结束放回根节点A并访问A的右树,同理得出该树的前序遍历为 A,B,D,E,C,F,G;
既然看图可以得出树的前序遍历,那在代码中如何表示出二叉树的前序遍历,该处只需要使用递归的方式,按照根、左、右的方式即可;
void BinaryTreePreOrder(BTNode*root) { if(root==NULL){//当root为空时则表示该处无节点,即无存储有效数据,若是访问则会造成对空指针的非法解引用 printf("NULL"); return ; } printf("%c ",root->a);//若是不为空则打印出该节点内所存储的有效数据 BinaryTreePreOrder(root->left);//访问该节点的左子树 BinaryTreePreOrder(root->right);//访问该节点的右子树 }
如图所示
中序遍历
中序遍历的遍历顺序为:左子树、根、右子树;
按照该树可得出中序遍历的结果为:D,B,E,A,F,C,G;
若是用代码方式表示即为:
void BinaryTreeInOrder(BTNode*root) { if(root==NULL){//当root为空时则表示该处无节点,即无存储有效数据,若是访问则会造成对空指针的非法解引用 printf("NULL"); return; } printf("%c ",root->a);//若是不为空则打印出该节点内所存储的有效数据 BinaryTreeInOrder(root->left);//访问该节点的左子树 BinaryTreeInOrder(root->right);//访问该节点的右子树 }
后序遍历
中序遍历的遍历顺序为:左子树、右子树、根;
按照该树可得出中序遍历的结果为:D,E,B,F,G,C,A;
若是用代码方式表示即为:
void BinaryTreePostOrder(BTNode*root) { if(root==NULL){//当root为空时则表示该处无节点,即无存储有效数据,若是访问则会造成对空指针的非法解引用 printf("NULL"); return; } BinaryTreePostOrder(root->left);//访问该节点的左子树 BinaryTreePostOrder(root->right);//访问该节点的右子树 printf("%c ",root->a);//若是不为空则打印出该节点内所存储的有效数据 }
总结
二叉树除了前中后序遍历以外还有一种遍历方式叫作层序遍历,可以使用队列的FIFO特性从而完成该遍历的实现;
在利用递归实现解决二叉树相关问题的过程中,可以根据实际情况选择相应的遍历方式从而以效率较高的方式解决问题;
所有的二叉树问题都可以将其分为两个子问题进行解决;