当前位置:   article > 正文

数据结构之二叉树_数据结构二叉树

数据结构二叉树

一、树

1、基本概念:树就是一种类似现实生活中的树的数据结构(倒置的树)。任何一颗非空树只有一个根节点。

2、一棵树具有以下特点:

  1. 一棵树中的任意两个结点有且仅有唯一的一条路径连通。
  2. 一棵树如果有 n 个结点,那么它一定恰好有 n-1 条边。
  3. 一棵树不包含回路。

3、树的形状如下图所示:

 4、区别:

        层数:从根节点开始算,为第一层。

        高度:从最下面的叶子节点开始算,且从0开始计数。

        深度:从根节点开始算,从0开始计数。

树的基本名词解释:

  • 节点 :树中的每个元素都可以统称为节点。
  • 根节点 :顶层节点或者说没有父节点的节点。上图中 A 节点就是根节点。
  • 父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点。上图中的 B 节点是 D 节点、E 节点的父节点。
  • 子节点 :一个节点含有的子树的根节点称为该节点的子节点。上图中 D 节点、E 节点是 B 节点的子节点。
  • 兄弟节点 :具有相同父节点的节点互称为兄弟节点。上图中 D 节点、E 节点的共同父节点是 B 节点,故 D 和 E 为兄弟节点。
  • 叶子节点 :没有子节点的节点。上图中的 D、F、H、I 都是叶子节点。
  • 节点的高度 :该节点到叶子节点的最长路径所包含的边数。
  • 节点的深度 :根节点到该节点的路径所包含的边数
  • 节点的层数 :节点的深度+1。
  • 树的高度 :根节点的高度。

二、二叉树

1、基本概念:

二叉树(Binary tree)是每个节点最多只有两个分支(即不存在分支度大于 2 的节点)的树结构。

二叉树 的分支通常被称作“左子树”或“右子树”。并且,二叉树 的分支具有左右次序,不能随意颠倒。

二叉树 的第 i 层至多拥有 2^(i-1) 个节点,深度为 k 的二叉树至多总共有 2^(k+1)-1 个节点(满二叉树的情况),至少有 2^(k) 个节点

2、二叉树的分类

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是 满二叉树。也就是说,如果一个二叉树的层数为 K,且结点总数是(2^k) -1 ,则它就是 满二叉树。如下图所示:

2.1、完全二叉树

除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则这个二叉树就是 完全二叉树

大家可以想象为一棵树从根结点开始扩展,扩展完左子节点才能开始扩展右子节点,每扩展完一层,才能继续扩展下一层。如下图所示:

细心的小伙伴可能发现了,当根节点的值为 1 的情况下,若父结点的序号是 i,那么左子节点的序号就是 2i,右子节点的序号是 2i+1。这个性质使得完全二叉树利用数组存储时可以极大地节省空间,以及利用序号找到某个节点的父结点和子节点,后续二叉树的存储会详细介绍。

2.2、平衡二叉树

平衡二叉树 是一棵二叉排序树,且具有以下性质:

  1. 可以是一棵空树
  2. 如果不是空树,它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。

平衡二叉树的常用实现方法有 红黑树AVL 树替罪羊树加权平衡树伸展树 等。

在给大家展示平衡二叉树之前,先给大家看一棵树:

 

 这是一颗在极端情况下,退化为链表的树,叫斜树。

但是,如果二叉树退化为一个链表了,那么那么树所具有的优秀性质就难以表现出来,效率也会大打折,为了避免这样的情况,我们希望每个做 “家长”(父结点) 的,都 一碗水端平,分给左儿子和分给右儿子的尽可能一样多,相差最多不超过一层,如下图所示:

3、二叉树的存储

二叉树的存储主要分为 链式存储顺序存储 两种:

3.1、链式存储

和链表类似,二叉树的链式存储依靠指针将各个节点串联起来,不需要连续的存储空间。

每个节点包括三个属性:

  • 数据 data。data 不一定是单一的数据,根据不同情况,可以是多个具有不同类型的数据。
  • 左节点指针 left
  • 右节点指针 right
  1. class TreeNode {
  2. int val;
  3. TreeNode left;
  4. TreeNode right;
  5. TreeNode() {
  6. }
  7. TreeNode(int val) {
  8. this.val = val;
  9. }
  10. TreeNode(int val, TreeNode left, TreeNode right) {
  11. this.val = val;
  12. this.left = left;
  13. this.right = right;
  14. }
  15. }

 3.2、顺序存储

顺序存储就是利用数组进行存储,数组中的每一个位置仅存储节点的 data,不存储左右子节点的指针,子节点的索引通过数组下标完成。根结点的序号为 1,对于每个节点 Node,假设它存储在数组中下标为 i 的位置,那么它的左子节点就存储在 2i 的位置,它的右子节点存储在下标为 2i+1 的位置。

一棵完全二叉树的数组顺序存储如下图所示:

 完全二叉树和普通二叉树存储的区别

如果我们要存储的二叉树不是完全二叉树,在数组中就会出现空隙,导致内存利用率降低

4、二叉树的遍历

二叉树通常有两种访问方式,DFS和BFS。其中DFS又分为前序、中序、后序三种方式。

4.1、深度优先搜索(DFS)

        因为二叉树的特性,所有的子树都为二叉树。所以遍历二叉树,可以用递归的方式实现。把每次遍历都简化为求解子问题。遍历子树来解决。

1、前序(先序)遍历

口诀:根左右

前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。

 步骤:

        若二叉树为空则结束返回,否则:

                        (1)访问根结点。

                        (2)前序遍历左子树。

                        (3)前序遍历右子树 。

代码:

  1. public void preOrder(TreeNode root){
  2. if(root == null){
  3. return;
  4. }
  5. system.out.println(root.data);
  6. preOrder(root.left);
  7. preOrder(root.right);
  8. }

2、中序遍历

口诀:左右根

 二叉树的中序遍历,就是先递归中序遍历左子树,再输出根结点的值,再递归中序遍历右子树

 步骤:

        若二叉树为空则结束返回,否则:     

                        (1)中序遍历左子树。

                        (2)访问根结点。

                        (3)中序遍历右子树 。

代码:

  1. public void inOrder(TreeNode root){
  2. if(root == null){
  3. return;
  4. }
  5. inOrder(root.left);
  6. system.out.println(root.data);
  7. inOrder(root.right);
  8. }

3、后序遍历

口诀:左右根

二叉树的后序遍历,就是先递归后序遍历左子树,再递归后序遍历右子树,最后输出根结点的值

 步骤:

        若二叉树为空则结束返回,否则:     

                        (1)后序遍历左子树。  

                        (2)后序遍历右子树 。

                        (3)访问根结点。

代码:

  1. public void postOrder(TreeNode root){
  2. if(root == null){
  3. return;
  4. }
  5. postOrder(root.left);
  6. postOrder(root.right);
  7. system.out.println(root.data);
  8. }

4.2、广度优先搜索(BFS)

层次遍历:二叉树的层次遍历 ,顾名思义就是指从二叉树的第一层(根节点)开始,从上至下逐层遍历,在同一层中,则按照从左到右的顺序对节点逐个访问。在逐层遍历过程中,按从顶层到底层的次序访问树中元素,在同一层中,从左到右进行访问。

算法思想:(BFS)广度优先搜索

用一个队列保存被访问的当前节点的左右孩子以实现层序遍历。

在进行层次遍历的时候,设置一个队列结构,遍历从二叉树的根节点开始,首先将根节点指针入队列,然后从队头取出一个元素,每取一个元素,执行下面两个操作:

1、访问该元素所指向的节点

2、若该元素所指节点的左右孩子节点非空,则将该元素所指节点的左孩子指针和右孩子指针顺序入队。此过程不断进行,当队列为空时,二叉树的层次遍历结束。

知识点:队列

队列是一种仅支持在表尾进行插入操作、在表头进行删除操作的线性表,插入端称为队尾,删除端称为队首,因整体类似排队的队伍而得名。它满足先进先出的性质,元素入队即将新元素加在队列的尾,元素出队即将队首元素取出,它后一个作为新的队首。

思路:

二叉树的层次遍历就是按照从上到下每行,然后每行中从左到右依次遍历,得到的二叉树的元素值。对于层次遍历,我们通常会使用队列来辅助:

因为队列是一种先进先出的数据结构,我们依照它的性质,如果从左到右访问完一行节点,并在访问的时候依次把它们的子节点加入队列,那么它们的子节点也是从左到右的次序,且排在本行节点的后面,因此队列中出现的顺序正好也是从左到右,正好符合层次遍历的特点。

具体做法:

  • step 1:首先判断二叉树是否为空,空树没有遍历结果。
  • step 2:建立辅助队列,根节点首先进入队列。不管层次怎么访问,根节点一定是第一个,那它肯定排在队伍的最前面。
  • step 3:每次进入一层,统计队列中元素的个数。因为每当访问完一层,下一层作为这一层的子节点,一定都加入队列,而再下一层还没有加入,因此此时队列中的元素个数就是这一层的元素个数。
  • step 4:每次遍历这一层这么多的节点数,将其依次从队列中弹出,然后加入这一行的一维数组中,如果它们有子节点,依次加入队列排队等待访问。
  • step 5:访问完这一层的元素后,将这个一维数组加入二维数组中,再访问下一层。

代码:

  1. import java.util.*;
  2. public class Solution {
  3. public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
  4. ArrayList<ArrayList<Integer> > res = new ArrayList();
  5. if(root == null)
  6. //如果是空,则直接返回空数组 fast-template
  7. return res;
  8. //队列存储,进行层次遍历
  9. Queue<TreeNode> q = new ArrayDeque<TreeNode>();
  10. q.add(root);
  11. while(!q.isEmpty()){
  12. //记录二叉树的某一行
  13. ArrayList<Integer> row = new ArrayList();
  14. int n = q.size();
  15. //因先进入的是根节点,故每层结点多少,队列大小就是多少
  16. for(int i = 0; i < n; i++){
  17. TreeNode cur = q.poll();
  18. row.add(cur.val);
  19. //若是左右孩子存在,则存入左右孩子作为下一个层次
  20. if(cur.left != null)
  21. q.add(cur.left);
  22. if(cur.right != null)
  23. q.add(cur.right);
  24. }
  25. //每一层加入输出
  26. res.add(row);
  27. }
  28. return res;}
  29. }

5、总结

        二叉树是十分重要的数据结构,我们需要掌握二叉树的四种遍历方式。本文中,针对深度优先搜索的遍历:1、前序遍历。2、中序遍历。3、后序遍历。均是采用递归的方式进行操作。然而,这三种方式同样还可以通过栈的特点,已非递归的方式进行遍历。这也是我们需要重点掌握的。层次遍历则是利用队列先进先出的特点,每遍历一层后,进入队列的元素个数就是该层元素个数。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/522329
推荐阅读
相关标签
  

闽ICP备14008679号