• 树和二叉树


    1.在数据结构中,树的定义如下:

      树(tree)是n(n>0)个节点的有限集。当n=0时,称为空树。在任意一个非空树中,有如下特点:

        ① 有且仅有一个特定的称为根的节点。

        ② 当n>1时,其余节点可分为m(m>0)个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树。

    2.什么是二叉树?

      二叉树是树的一种特殊形式。二叉,顾名思义,这种树的每个节点最多有2个孩子节点。

      注意,这里是最多有2个,也可能只有一个,或者没有孩子节点。

      二叉树节点的两个孩子节点,一个被称为左孩子,一个被称为右孩子。这两个孩子节点的顺序是固定的,就像人的左手和右手,不能颠倒或混淆。

      此外,二叉树还有两种特殊形式,一个叫满二叉树,另一个叫完全二叉树。

      2.1 什么是满二叉树呢?

        一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上,那么这个树就是满二叉树。简单点说,满二叉树的每一个分支都是满的。

      2.2 什么是完全二叉树呢?

        对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同,则这个二叉树为完全二叉树。

        完全二叉树的特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。需要注意的是,满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树。

    3.二叉树可以用哪些物理存储结构来表达呢?

      3.1 链式存储结构

        链式存储时二叉树最直观的存储方式,之前学过链表,它是一对一的存储方式,每一个链表节点拥有data变量和一个指向下一节点的next指针。

        而二叉树稍微复杂一些,一个节点最多可以指向左右两个孩子节点,所以二叉树的每一个节点包含3部分:

        • 存储数据的data变量
        • 指向左孩子的left指针
        • 指向右孩子的right指针          

      3.2 数组

        使用数组存储时,会按照层级顺序把二叉树节点放到数组中对应的位置,如果某一个节点的左孩子或右孩子空缺,则数组相应的位置也空出来。

        为什么这样设计呢?因为这样可以更方便的在数组中定位二叉树的孩子节点和父节点。

        假设一个父节点的下标是parent,那么它的左孩子节点下标就是2*parent + 1;右孩子节点下标就是2*parent + 2。

        反过来,假设一个左孩子节点的下标是leftChild,那么它的父节点下标就是(leftChild - 1)/ 2。

        假设节点4在数组中的下标为3,节点4是节点2的左孩子,节点2的下标可以直接通过计算得出。

          节点2下标 = (3-1)/2 = 1;

        显然,对于一个稀疏的二叉树来说,用数组表示法是非常浪费空间的。

    4.二叉树的应用

      二叉树包含许多特殊的形式,每一种形式都有自己的作用,但是其主要的应用还在于进行查找操作和维持相对顺序这两个方面。  

      4.1 查找

        二叉树的树形机构使它很适合扮演索引的角色。

        这里可以介绍一下二叉查找树,它在二叉树的基础上增加了以下几个条件;

        • 如果左子树不为空,则左子树上所有节点的值均小于根节点的值
        • 如果右子树不为空,则右子树上所有节点的值均大于根节点的值
        • 左、右子树也都是二叉查找树 

        对于一个节点分布相对均衡的二叉查找树来说,如果节点总数是n,那么搜索节点的时间复杂度就是O(logn),和树的深度是一样的。

        这种依靠比较大小来逐步查找的方式,和二分查找算法非常相似。

      4.2 维持相对顺序

        这一点仍然要说二叉查找树。二叉查找树要求左子树小于父节点,右子树大于父节点,正是这样保证了二叉树的有序性。因此二叉查找树还有另外一个名字——二叉排序树

    5.二叉树的遍历

      从节点之间位置关系的角度来看,二叉树的遍历分为4种:

        ① 前序遍历

        ② 中序遍历

        ③ 后序遍历

        ④ 层序遍历

      从更宏观的角度来看,二叉树的遍历归结为两大类:

        ① 深度优先遍历(前序遍历,中序遍历,后序遍历)

        ② 广度优先遍历(层序遍历)

      5.1 深度优先遍历

        深度优先和广度优先这两个概念不止局限于二叉树,它们更是一种抽象的算法思想,决定了访问某些复杂数据结构的顺序。在访问树,图或其他一些复杂数据结构时,这两个概念常常被使用到。

        所谓深度优先,顾名思义,就是偏向于纵深,“一头扎到底”的访问方式。

        5.1.1 前序遍历

          二叉树的前序遍历,输出顺序是根节点,左子树,右子树。

        5.1.2 中序遍历

          二叉树的中序遍历,输出顺序是左子树,根节点,右子树。

        2.1.3 后序遍历

          二叉树的后序遍历,输出顺序是左子树,右子树,根节点。

        二叉树的这三种遍历方式,用递归的思路可以非常简单的实现出来,可以看一下代码实现:

     1 package com.algorithm.test;
     2 
     3 import java.util.Arrays;
     4 import java.util.LinkedList;
     5 
     6 /**
     7  * @Author Jack丶WeTa
     8  * @Date 2020/7/29 16:08
     9  * @Description 利用线性的链表转化为非线性的二叉树,然后利用递归方式完成前序、中序、后序遍历。
    10  */
    11 public class BinaryTreeTest {
    12 
    13     /**
    14      * 构建二叉树
    15      * @param inputList 输入序列
    16      * @return
    17      */
    18     public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
    19 
    20         TreeNode node = null;
    21         if(null == inputList || inputList.isEmpty()){
    22             return null;
    23         }
    24         Integer data = inputList.removeFirst();
    25         //这里的判空很关键。如果元素为空,则不再进一步递归
    26         if(null != data){
    27             node = new TreeNode(data);
    28             node.leftChild = createBinaryTree(inputList);
    29             node.rightChild = createBinaryTree(inputList);
    30         }
    31         return node;
    32     }
    33 
    34     /**
    35      * 二叉树前序遍历
    36      * @param node 二叉树节点
    37      */
    38     public static void perOrderTraveral(TreeNode node){
    39         if (null == node){
    40             return;
    41         }
    42         System.out.println(node.data);
    43         perOrderTraveral(node.leftChild);
    44         perOrderTraveral(node.rightChild);
    45     }
    46 
    47     /**
    48      * 二叉树中序遍历
    49      * @param node 二叉树节点
    50      */
    51     public static void inOrderTraveral(TreeNode node){
    52         if(null == node){
    53            return;
    54         }
    55         inOrderTraveral(node.leftChild);
    56         System.out.println(node.data);
    57         inOrderTraveral(node.rightChild);
    58     }
    59 
    60     /**
    61      * 二叉树后序遍历
    62      * @param node 二叉树节点
    63      */
    64     public static void postOrderTraveral(TreeNode node){
    65         if(null == node){
    66             return;
    67         }
    68         postOrderTraveral(node.leftChild);
    69         postOrderTraveral(node.rightChild);
    70         System.out.println(node.data);
    71     }
    72     /**
    73      * 二叉树节点
    74      */
    75     private static class TreeNode {
    76         int data;
    77         TreeNode leftChild;
    78         TreeNode rightChild;
    79 
    80         TreeNode(int data) {
    81             this.data = data;
    82         }
    83     }
    84 
    85     public static void main(String[] args) {
    86         LinkedList<Integer> inputList = new LinkedList<>(Arrays.asList(new Integer[]{3,2,9,null,null,10,null,null,8,null,4}));
    87         TreeNode treeNode = createBinaryTree(inputList);
    88         System.out.println("前序遍历:");
    89         perOrderTraveral(treeNode);
    90         System.out.println("中序遍历:");
    91         inOrderTraveral(treeNode);
    92         System.out.println("后续遍历:");
    93         postOrderTraveral(treeNode);
    94     }
    95 }

        代码种值得注意的是二叉树的构建。二叉树的构建方法有很多,这里把一个线性的链表转换成非线性的二叉树,链表节点的顺序恰恰是二叉树前序遍历的顺序,链表种的空值,代表二叉树节点的左孩子或右孩子为空的情况。

      绝大多数可以用递归解决的问题,其实都可以用另一种数据结构来解决,这种数据结构就是栈。因为递归和栈都有回溯的特性,利用栈来简单的实现二叉树的前序排列吧。代码如下;

     1 /**
     2      * 利用栈完成二叉树的前序遍历
     3      * @param node
     4      */
     5     public static void perOrderTraveralWithStack(TreeNode node){
     6         Stack<TreeNode> stack = new Stack<>();
     7         TreeNode treeNode = node;
     8         while (null != treeNode || !stack.isEmpty()){
     9             //迭代访问节点的左孩子,并入栈
    10             while (null != treeNode){
    11                 System.out.println(treeNode.data);
    12                 stack.push(treeNode);
    13                 treeNode = treeNode.leftChild;
    14             }
    15             //如果节点没有左孩子,则弹出栈顶节点,访问节点右孩子
    16             if (!stack.isEmpty()){
    17                 treeNode = stack.pop();
    18                 treeNode = treeNode.rightChild;
    19             }
    20         }
    21     }

      5.2 广度优先遍历

        如果说深度优先遍历是在一个方向上“一头扎到底”,那么广度优先遍历则恰恰相反,先在各个方向上走出1步,然后再各个方向上走出第2步、第3步......一直到各个方向全部走完。

        层序遍历,顾名思义,就是二叉树按照从根节点到叶子节点的层次关系,一层一层横向遍历各个节点,在实现层序遍历时,我们需要借助一个数据结构来辅助工作,这个数据结构就是队列。实现代码如下:

     1 /**
     2      * 利用队列实现二叉树的层序遍历
     3      * @param node
     4      */
     5     public static void levelOrderTraveral(TreeNode node){
     6         Queue<TreeNode> queue = new LinkedList<>();
     7         queue.offer(node);
     8         while (!queue.isEmpty()){
     9             TreeNode treeNode = queue.poll();
    10             System.out.println(treeNode.data);
    11             if (null != treeNode.leftChild)
    12                 queue.offer(treeNode.leftChild);
    13             if (null != treeNode.rightChild)
    14                 queue.offer(treeNode.rightChild);
    15         }
    16     }

         

  • 相关阅读:
    Windows身份验证和混合验证的差别
    Codeforces Round #273 (Div. 2) --B Random Teams
    带输出參数的存储过程的定义,以及在aso.net中调用
    数字统计
    UVa10048_Audiophobia(最短路/floyd)(小白书图论专题)
    C语言之基本算法21—可逆素数
    我在CSDN开通博客啦!
    小谈并查集及其算法实现
    C#高级编程五十四天----Lookup类和有序字典
    开发人员调试工具Chrome Workspace
  • 原文地址:https://www.cnblogs.com/JackWeTa/p/13398303.html
Copyright © 2020-2023  润新知