树是一种很常见的分线性数据结构,公司的组织架构,行政区划结构等都是树形结构。树形结构里常见的有树和二叉树。node
树是n(n>=0)个结点的有限集。算法
在任意一棵非空树中:数组
(1)有且仅有一个特定的称为根(root)的结点数据结构
(2)当n>1时,其他结点可分为m(m>0)个互不相交的有限集,其中每个集合自己又是一棵树,称为根的子树(递归的过程)架构
如上图所示:ide
图3-1是n=0的树;优化
图3-2是n=1只有一个根节点的树;spa
图3-3是一棵普通的树,B为根节点的树T1 = {B,E,F,J} 是A的子树,B为T1的根节点,同时也有本身的子树。3d
如图所示的树有如下3中表示方法:指针
其中1是集合形式看起来很清晰;2是层级表示方式,相似书的目录;3是一种广义表的表示方法。
结点:包含一个数据元素 及 若干指向其子树的分支。
例如结点B,包含告终点数据B 和 指向子树E和F的分支。
度:结点拥有的子树数称为结点的度。
例如:结点B包含了两个子树,度为2;结点D包含了3个子树,度为3.
叶子(终端结点):度为0的结点(没有子树的结点)
例如:J、F、C、G、H、I都是树的叶子。
分支结点(非终端结点):度不为0的结点(有子树的结点)。除了根节点外,分支结点也成为内部结点。
例如:A、B、E、D为分支结点;B、E、D为内部结点。
树的度:树内各个结点的度的最大值。
例如:A的度为3;B的度为2;D的度为3,其他结点度为0,因此树的度为3。
孩子:结点的子树称为结点的孩子,反过来,该节点称为孩子的双亲。
例如:结点A有B、C、D 3个孩子,A是B、C、D的双亲结点。
兄弟:同一双亲的孩子互为兄弟。
祖先:从根节点到某个结点(N)经历的全部结点称为该节点(N)的祖先;反之,以某结点(N)为根的任一结点都是该节点(N)的子孙。
堂兄弟:双亲结点在同一层的结点互为堂兄弟。
例如:E 和 G、H、I为堂兄弟。
结点的层次:结点的层次是从根节点开始,根为第一层,依次递增,因此上面树的结点A在第1层,J在第4层。若是结点在n层,其子树(若是有子树)就在第n+1层。
树的深度(高度):树种结点的最大层次称为树的深度(Depth)或高度。上面树的深度为4。
有序树:若是树中结点的各子树从左到右是有次序的(即不能互换),则次树是有序树;反之,则为无序树。
二叉树是一种有限制的树,每一个结点最多只有两颗子树(即二叉树中不存在度大于2的结点),而且二叉树的子树有左右之分,次序不能任意颠倒。
能够简单理解为:二叉树是一棵任意结点度不大于2的有序树。
二叉树有如下5中结构:
1:空二叉树;
2:只有根节点的二叉树;
3:右子树为空的二叉树;
4:左右子树均非空的二叉树;
5:左子树为空的二叉树
满二叉树:一棵深度为k且有2k - 1个结点的二叉树称为满二叉树。满二叉树上每一层的结点数都是最大节点数。
彻底二叉树:深度为k的,有n个结点的二叉树,当且仅当其每个节点都与深度为k的满二叉树中自上而下,从左往右编号彻底相同的时候,就是彻底二叉树。
注:彻底二叉树是效率很高的数据结构,堆是一种彻底二叉树或者近似彻底二叉树,因此效率极高,像十分经常使用的排序算法、Dijkstra算法、Prim算法等都要用堆才能优化,二叉排序树的效率也要借助平衡性来提升,而平衡性基于彻底二叉树。
(1)二叉树的第i层上至多有2i-1个结点(i >= 1);
(2)深度为k的二叉树至多有2k - 1个结点(k >= 1);
(3)对于任意一棵二叉树T,若是其终端节点数为n0,度为2的结点数为n2,则= n2 + 1
有如下3中二叉树:
按顺序存储的时候,用一组连续的存储空间从上至下,从左至右,将二叉树编号 i 的元素存储在对应1维数组的下标为 i-1 的位置,对应的存储结构为:
数组里元素为0的表示没有此结点,能够看出:顺序存储结构适合于彻底二叉树,对于非彻底二叉树比较浪费空间,图(3)只有四个结点对于最坏状况下,须要的空间倒是最多的。
所以,在最坏状况下,一个深度为k且只有k个结点的单支树(树中不存在度为2的结点)须要的存储长度是2k - 1的一维数组。
由二叉树的定义得知:二叉树的结点由一个数据元素 和 分别指向左子树、右子树的两个分支构成。有时候为了方便,也能够加个双亲结点的指针域,以下图所示:
只含有左右子树的结点 和 含有左右子树和双亲指针的结点的数据结构:
只含有左右子树指针的链式结构称为二叉链表;含有左右子树指针和双亲结点指针的链式结构称为三叉链表。
以下2种结构的二叉树:
由存储结构能够得出:有n个结点的二叉链表中有n+1个空链域。
(1)二叉链表少存储了个parent指针,因此更节省内存。
(2)在二叉链表中查找某个元素的双亲结点须要从根节点遍历查询,而在三叉链表中能够直接经过parent指针拿到。
二叉链表和三叉链表各有优缺点,具体使用哪一种存储结构须要根据实际状况来决定。
二叉树不像线性表结构只须要从前向后遍历便可访问每一个元素,二叉树每一个结点均可能有两个分支,因此遍历方式确定不像线性表那么简单。二叉树是由若干个结点递归构成的,每一个结点又由根节点、左子树和右子树3个基本单元组成,所以遍历二叉树就是依次遍历这三个部分,每一个结点都按某种方法来遍历,遍历完全部结点即完成了对二叉树的遍历过程。假如限定先左后右,假如一棵二叉树不为空,则有如下3种方式:
先序遍历
(1)访问根节点;
(2)先序遍历左子树;
(3)先序遍历右子树。
中序遍历
(1)中序遍历左子树;
(2)访问根节点;
(3)中序遍历右子树。
后序遍历
(1)后序遍历左子树;
(2)后序遍历右子树;
(3)访问根节点。
按层次遍历
从上到下,从左往右,逐层遍历。
对于二叉树:
先序遍历(中左右):A->B->D->H->I->E->J->k->C->F->L->G
中序遍历(左中右):H->D->I->B->J->E->K->A->L->F->C->G
后续遍历(左右中):H->I->D->J->K->E->B->L->F->G->C->A按层遍历(上->下,左->右):A->B->C->D->E->F->G->H->I->J->K->L
用递归来实现前序、中序、后续遍历:
//前序 private void prePrint(Node node ) { if (node == null) return; System.out.print(node.getVal()); prePrint(node.getLeftChild()); prePrint(node.getRightChild()); } //中序 private void middlePrint(Node node) { if (node == null) return; middlePrint(node.getLeftChild()); System.out.print(node.getVal() + "->"); middlePrint(node.getRightChild()); } //后续 private void sufPrint(Node node) { if (node == null) return; sufPrint(node.getLeftChild()); sufPrint(node.getRightChild()); System.out.println(node.getVal()); }
遍历二叉树是以必定规则将二叉树中结点排列成一个线性序列,不一样方法会获得不一样序列方式,如先序序列、中序序列和后序序列。这其实是对一个非线性结构进行线性化的操做,使每一个节点(除了第一个和最后一个外)在这些线性序列中有且仅有一个直接前驱和直接后继。可是,当以二叉链表做为存储结构时候,只能找到左右孩子结点信息,不能直接获得结点在任一序列中的前驱和后继信息,这种信息只有在遍历的动态过程当中才能获得。如何保持这种线性关系呢?
(1)若是在每一个结点上增长两个指针域prefix 和 suffix,分别表示结点在任一次序遍历时候的前驱和后继,虽然可用实现,可是增长的两个指针域比较耗费空间;
(2)前面咱们知道,在有n个结点的二叉链表中一定有n+1个空链域,若是用空链域来存储结点的前驱和后继就能够充分利用内存空间,因此只须要新增两个标识位lTag和rTag,用来区分何时指向孩子节点,何时指向前驱(后继),标识位是布尔类型的,比(1)里的指针更省空间。
若是结点有左子树,则其lchild指向其左孩子结点,不然让lchild域指向其前驱;若结点有右子树,则其rchild指向其右孩子,不然让rchild指向其后继。新增两个标识位,结点结构为:
其中:
lTag:0 lchild域指示结点的左孩子
1 lchild域指示结点的前驱
rTag:0 rchild域指示结点的右孩子
1 rchild域指示结点的后继
以这种结点结构构成的二叉链表做为二叉树的存储结构叫作线索链表,其中指向结点前驱和后继的指针叫作线索,加上线索的二叉树叫作线索二叉树,对二叉树以某种次序遍历使其变为线索二叉树的过程叫作线索化。
由于线索化的过程是将二叉链表中的空指针改成指向前驱或后继的线索,并且前驱或后继信息是在遍历过程当中才有的,因此线索化即为改变二叉链表空指针的过程。
下面分别是前中后序对于的线索二叉树和线索二叉链表,若是给二叉链表增长一个head指针,那么在给定任意一个结点均可以遍历获得整棵树:
如上图所示的二叉树,有如下3中表示方法
假设以一组连续空间存储树的结点,存储结点的同时附加存储指示双亲的结点在链表里的位置,有图可知,方便找每一个结点的双亲,不太方便找孩子结点(须要遍历)。
图(b)由每一个结点分别指向本身的子树,构成多重链表结构;图(c)在图(b)基础上增长了双亲节点。
又称为二叉树表示法或二叉链表表示法。链表里每一个结点的两个指针分别指向该节点的第一个孩子结点和下一个兄弟结点。
前面知道二叉树能够用二叉链表表示,上小结提到树能够用二叉链表表示,因此就能够用二叉链表做为存储媒介将树与二叉树对应起来,也就是说对于一棵给定的树能够找到惟一的一棵二叉树与之对应,以下图所示:
由上节树的二叉链表表示法能够知道:任何一棵树对应的二叉树,其右子树确定为空(因为根节点确定没有兄弟结点)。
若是把第二棵树根节点看做第一棵树根节点的兄弟,那么第二颗树根节点就是第一棵树的右子树,以此类推能够将若干棵树构成一棵二叉树(即森林与二叉树对应关系),以下图所示:
由树的结构定义能够引出两种次序遍历方法:
先根(次序)遍历树:先访问树的根节点,而后依次先根遍历根的每颗子树
后根(次序)遍历树:先依次后根遍历每颗子树,而后访问根节点
对这棵树进行先根遍历:A B E F C D G
对这棵树进行后根遍历:E F B C G D A
按照森林和树的定义,能够推出森林的两种遍历方法
先序遍历森林
若是森林非空,能够按下面规则遍历:
(1)访问森林中第一棵树的根节点
(2)先序遍历第一棵树中根节点的子树森林
(3)先序遍历除去第一棵树以后的树构成的森林
中序遍历森林
若是森林非空,能够按下面规则遍历:
(1)中序遍历森林中第一棵树的根节点的子树森林
(2)访问第一棵树的根节点
(3)中序遍历除去第一棵树以后的树构成的森林
对上图森林进行遍历:
先序:A B C E D F G H I J K
中序:B E C D A G F I K J H
由森林转化成二叉树得知,对应的二叉树为
因此森林的先序和中序也就是转化成二叉树后,二叉树的先序和中序遍历。