树和二叉树

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     }

     

原文地址:https://www.cnblogs.com/JackWeTa/p/13398303.html