程序员基本功11树和二叉树

你只管努力,其他的交给天意

1、树的概念和基本术语

树是一种非线性的数据结构,如果一组数组节点之间存在复杂的一对多关联时,程序可以考虑使用树来存储这组数据。Treemap本身就是一颗红黑树,红黑树又是一种特殊的排序二叉树。

树:指的是N个有父子关系的节点的有限集合

对于这个集合,它满足如下条件:

  1. 当N=0时,该节点集合为空,这棵树也被称为空树;
  2. 在任意的非空树中,有且仅有一个根节点;
  3. 当N>1时,除根节点外的其余节点可分为M个互为相交的有限集合T1、T2.。其中的每个集合本身又是一棵树,并称其为根的子树。
  • 节点:树的最基本组成单元,常包括一个数据元素及若干指针用于指向其他节点
  • 节点的度:节点拥有的子树的个数被称为节点的度
  • 树的度:树中所有节点的度的最大值
  • 叶子节点:度为0的节点被称为叶子节点或终端节点
  • 分支节点:度不为0的节点
  • 子节点、父节点和兄弟节点:节点的子树的根被称为子节点,该节点为父节点。具有相同父节点的子节点互称为兄弟节点
  • 节点的层次:节点的层次从根开始算起,跟的层次为1,其余节点的层次值为父节点的穿层次值加1。
  • 树的深度:树中节点的最大层次值称为树的深度
  • 有序树与无序树:如果树中节点的各个子树看成从左到右是有序的,则概述为有序树,否则为无序树
  • 祖先节点:从根到该节点所经过分支上的所有节点
  • 后代节点:以某节点为根的子树中任一节点
  • 深林:森林是2颗或2颗以上互不相交的树的集合

2、树的基本操作

  • 初始化:通常是一个构造器,用于创建一个空的树,或者以指定节点为根创建树
  • 为指定节点添加子节点
  • 判断树是否为空
  • 返回根节点
  • 返回指定节点(非根节点)的所有子节点
  • 返回指定节点的(非叶子节点)的所有子节点
  • 返回指定节点(非叶子节点)的第i个子节点
  • 返回该树的深度
  • 返回指定节点的位置

3、树的父节点存储实现

为了实现这种数据结构,程序必须记录节点与非节点的父子关系,有俩种选择:父节点表示法、子节点表示法

定义树节点时增加一个Parent域,该域用于保存该节点的父节点在数组中的位置索引,通过这种方式记录书中节点之间的父子关系,添加新节点时将新节点的parent域的值设为其父节点在数组中的索引。

每个节点都可以快速找到它的父节点,但如果要找某个节点的所有子节点就比较麻烦,程序要遍历整个节点数组。

4、树的子节点链表示法

让父节点记住它的所有子节点 

5、二叉树的定义和基本概念

二叉树指的是每个节点最多只能有俩个子树的有序树,左边的子树称为左子树,右边的子树称为右子树

树个二叉树的俩个重要区别:

  • 树中节点的最大度数没有限制,二叉树节点的最大度数为2
  • 无序树的节点无左右之分,而二叉树的节点有左右之分,也就是说,二叉树是有序树
  • 二叉树的性质:
  • 二叉树第i层的节点数目至多为2^{i-1}\left ( i> = 1 \right )
  • 深度为k的二叉树至多有2^{k}-1个节点
  • 任何二叉树中,如果其叶子节点的数量为n0,度为2的子节点数量为n2,则n0=n2+1。
  • 具有n个节点的完全二叉树的深度为\log _{2}n+1
  • 对一颗有n个节点的完全二叉树的节点按层自左向右编号,则对任一编号为i的节点有:

当i=1时,节点i是二叉树的根;若i>1,则节点的父节点是i/。

2i\leq n,则节点i有左孩子,左孩子的编号是2i,否则,节点无左孩子,并且是叶子节点。

2i+1\leq n,则节点i有右孩子,有孩子的编号是2i+1;否则节点无右孩子。

  • 对一颗有n个节点的完全二叉树的节点按层自左向右编号,1~n/2范围的节点都是有孩子节点的非叶子节点,其余的都是叶子节点。编号为n/2的节点可能只有左子节点,也可能左右子节点都有。

 

6、二叉树的基本操作

  • 初始化:通常是一个构造器,用于创建一个空的树,或者以指定节点为根创建二叉树
  • 为指定节点添加子节点
  • 判断二叉树是否为空
  • 返回根节点
  • 返回根节点的父节点
  • 返回指定节点的左子节点
  • 返回指定节点的右子节点
  • 返回该二叉树的深度
  • 返回指定节点的位置

实现二叉树这种数据结构,右三种选择:

  • 顺序存储:采用数组来记录二叉树的所有节点
  • 二叉链表存储:每个节点保留一个left、right域,分别指向其左右子节点
  • 三叉链表存储:每个节点保留left、right和parent域,分别指向左、右子节点和父节点。

7、二叉树的顺序存储的实现

二叉树每层的节点数最多为2^{i-1},这个二叉树最多能包含节点为2^{i}-1,因此只要定义一个2^{i}-1的数组即可储存这棵树。二叉树采用顺序存储可能会造成一定的空间浪费。

8、二叉树和二叉链表存储的实现

对于二叉链表的二叉树,因为程序采用链表来记录树中所有节点,所以添加节点没有限制,也不会像顺序存储那样产生大量的空间浪费。这种二叉链表的存储方式在遍历树节点时效率不高,指定节点访问父类节点也比较困难,程序必须采用遍历二叉树的方式搜索其父节点。

为了克服二叉链表存储方式中访问父类节点不方便的问题,可以将二叉链表扩展成三叉链表。三叉链表存储方式是二叉链表的一种改进,通过为树节点增加一个parent引用,可以让每个节点都能方便的访问父节点。

遍历二叉树指的是按照某种规律依次访问二叉树的每个节点,对二叉树的遍历过程就是将非线性结构的二叉树中的节点排列在一个线性序列上升的过程。如果采用顺序结构保存二叉树,程序遍历二叉树将非常容易,直接遍历底层数组即可,如果采用链表保存二叉树的节点,则有俩中遍历方式:

深度优先遍历:这种遍历将先访问到树中最深层析的节点。它右可分为3中(前序、中序、后序)

广度优先遍历:逐层访问每层的节点,先访问根节点,然后访问第二层节点,广度遍历法又被称为按层遍历

9、先序遍历二叉树

DLR(L:左子树、D:根、R:右子树)

10、中序遍历二叉树

LDR

11、后续遍历二叉树

LRD

12、广度优先遍历二叉树

先遍历二叉树的第一层,再遍历根节点的俩个子节点,逐层遍历二叉树所有节点。

为了实现广度优先遍历,可以借助FIFO特征队列来实现:

  1. 建立一个队列,把树的节点压入队列
  2. 从队列中弹出一个节点,然后把该节点的左右节点压入队列,如果没有子节点,则声明到达叶子节点
  3. 重复执行第二步,直到队列为空,声明所有叶子节点都已经经过了队列,也就完成了遍历

13、森林、树和二叉树之间的转换

三者之间一一映射,可以相互转换

多叉树向二叉树转换:

  • 加虚线:同一个父节点的相邻兄弟节点之间加虚线
  • 抹实线:每个节点只保留它与最左子节点的连线,与其他子节点的连线都被抹掉
  • 虚改实:虚线改实线

二叉树向多叉树、森林恢复:

  • 加虚线:若某节点I是父节点的左子节点,则将该节点I的右孩子链的所有节点分别与I的父节点添加连线
  • 抹线:把有虚线的节点与原父节点的连线抹去
  • 整理:虚改实并按层排列

14、树的链表存储

因为多叉树和二叉树可以自由转换,因此,普通树可以以二叉树的形式保存,实际需要的时候,再将二叉树转换为普通树

通常使用三叉链表来保存二叉树

15、哈夫曼树的概念和用途

哈夫曼树又被称为最优二叉树,是一类带权路径最短的二叉树,在信息检索时常用。

节点之间的路径长度:从一个节点到另一个节点之间的分支数量称为俩个节点之间的路径长度

树的路径长度:从根节点到树中每一个节点的路径长度之和

节点的带权路径长度:从该节点到根节点之间的路径长度与节点上权的乘机

树的带权路径长度:树中所有叶子节点的带权路径长度之和

带权路径最小的二叉树被称为哈夫曼树或最有二叉树。

对于具有n个叶子节点的哈夫曼树,一共需要2*n-1个节点。

【对于二叉树来说,有三种类型节点:度数为0、1、2。而哈夫曼树的非叶子节点都是由俩个节点合并产生】

16、创建哈夫曼树

  1. 根据给定的n个权值{w1,w2,...wn}构建n颗二叉树的集合F={T1,T2,.....,Tn},F集合中每棵树都只有一个节点。
  2. 选取F集合中两颗根节点最小的树作为左右子树以构建一颗新的二叉树,且将新的二叉树的根节点的权值设为左右子树上根节点的权值之和。
  3. 将新的二叉树加入到F集合中,并删除(2)步中被选中的两棵树
  4. 重复2和3步直到F集合中只剩一棵树

17、哈夫曼编码

从哈夫曼根节点开始,对左子树分配代码“0”,对右子树分配代码“1”,一直到达叶子节点,然后,将从树根沿每条路径到达子节点的代码排列起来,便得到了每个叶子节点的哈夫曼编码。

采用哈夫曼树原理构造的二进制编码,使电文总最短。

哈夫曼编码有一条规矩:假设有N个叶子节点需要编码,最终得到的哈夫曼一定有N层,哈夫曼编码得到的二进制码的最大长度为N-1.

18、排序二叉树的概念和实现

排序二叉树是一种特殊结构的二叉树,通过它可以非常方便地对树中所有节点进行排序和索引。排序二叉树要么是空树,要么有以下性质:

  • 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值
  • 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值
  • 它的左右子树也分别为排序二叉树

创建排序二叉树的步骤:

 当程序从排序二叉树中删除一个检点后,为了保持为排序二叉树,必须对该排序二叉树进行维护。

被删除的节点是叶子节点,则只需将它从父节点中删除

被删除节点p只有左子树,将p的左子树pL添加成p的父节点的左子树即可;被删除节点p只有右子树,将p的右子树pR添加成p的父节点的右子树即可

若被删除节点p的左右树均为空:

将pL设为p的父节点q的左或右子节点(取决于p是其父节点q的左右子节点),将pR设为p节点的中序前趋节点s的右子节点(s是pL最右下的节点,也就是pL子树中最大的节点)

以p节点的中序前趋或后继替代p所指节点,然后再从原排序二叉树中删除中序前趋或后继节点 

  1. 以根节点为当前节点开始搜索
  2. 拿新节点的值和当前节点的值进行比较
  3. 如果新节点的值更大,则以当前节点的右子节点作为新的当前节点;如果新节点的值更小,则以当前节点的左子节点作为新的当前节点。
  4. 重复2、3两个步骤,直到搜索到合适的叶子节点
  5. 将新节点添加为第4步找到的叶子节点的子节点,如果新节点更大,,则添加为右子节点;否则为左子节点

19、红黑树的概念和实现

排序二叉树虽然可以快速检索,但如果插入的节点本身就是有序的,那么最后排序二叉树将变成链表:只有左节点或右节点。这时排序二叉树就是普通链表,检索效率就会很差。

红黑树的提出为了改善排序二叉树的不足。

红黑树并不是真正的平衡二叉树,实际应用中,红黑树的统计性能要高于平衡二叉树,但极端性能略差

红黑树在原有二叉树上增加如下几个要求:

  1. 节点要么是红色,要么是黑色
  2. 根节点永远是黑色
  3. 所有的叶子节点都是空节点,并且是黑色
  4. 每个红色节点的俩个子节点都是黑色
  5. 从任意一节点到其子树中每个子节点的路径都包含数量相同的黑色节点

你可能感兴趣的:(JAVA,突破程序员基本功)