赞
踩
1、基本概念:树就是一种类似现实生活中的树的数据结构(倒置的树)。任何一颗非空树只有一个根节点。
2、一棵树具有以下特点:
3、树的形状如下图所示:
4、区别:
层数:从根节点开始算,为第一层。
高度:从最下面的叶子节点开始算,且从0开始计数。
深度:从根节点开始算,从0开始计数。
树的基本名词解释:
二叉树(Binary tree)是每个节点最多只有两个分支(即不存在分支度大于 2 的节点)的树结构。
二叉树 的分支通常被称作“左子树”或“右子树”。并且,二叉树 的分支具有左右次序,不能随意颠倒。
二叉树 的第 i 层至多拥有 2^(i-1)
个节点,深度为 k 的二叉树至多总共有 2^(k+1)-1
个节点(满二叉树的情况),至少有 2^(k) 个节点
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是 满二叉树。也就是说,如果一个二叉树的层数为 K,且结点总数是(2^k) -1 ,则它就是 满二叉树。如下图所示:
除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则这个二叉树就是 完全二叉树 。
大家可以想象为一棵树从根结点开始扩展,扩展完左子节点才能开始扩展右子节点,每扩展完一层,才能继续扩展下一层。如下图所示:
细心的小伙伴可能发现了,当根节点的值为 1 的情况下,若父结点的序号是 i,那么左子节点的序号就是 2i,右子节点的序号是 2i+1。这个性质使得完全二叉树利用数组存储时可以极大地节省空间,以及利用序号找到某个节点的父结点和子节点,后续二叉树的存储会详细介绍。
平衡二叉树 是一棵二叉排序树,且具有以下性质:
平衡二叉树的常用实现方法有 红黑树、AVL 树、替罪羊树、加权平衡树、伸展树 等。
在给大家展示平衡二叉树之前,先给大家看一棵树:
这是一颗在极端情况下,退化为链表的树,叫斜树。
但是,如果二叉树退化为一个链表了,那么那么树所具有的优秀性质就难以表现出来,效率也会大打折,为了避免这样的情况,我们希望每个做 “家长”(父结点) 的,都 一碗水端平,分给左儿子和分给右儿子的尽可能一样多,相差最多不超过一层,如下图所示:
二叉树的存储主要分为 链式存储 和 顺序存储 两种:
和链表类似,二叉树的链式存储依靠指针将各个节点串联起来,不需要连续的存储空间。
每个节点包括三个属性:
- class TreeNode {
- int val;
-
- TreeNode left;
-
- TreeNode right;
-
- TreeNode() {
- }
-
- TreeNode(int val) {
- this.val = val;
- }
-
- TreeNode(int val, TreeNode left, TreeNode right) {
- this.val = val;
- this.left = left;
- this.right = right;
- }
- }
顺序存储就是利用数组进行存储,数组中的每一个位置仅存储节点的 data,不存储左右子节点的指针,子节点的索引通过数组下标完成。根结点的序号为 1,对于每个节点 Node,假设它存储在数组中下标为 i 的位置,那么它的左子节点就存储在 2i 的位置,它的右子节点存储在下标为 2i+1 的位置。
一棵完全二叉树的数组顺序存储如下图所示:
完全二叉树和普通二叉树存储的区别
如果我们要存储的二叉树不是完全二叉树,在数组中就会出现空隙,导致内存利用率降低
二叉树通常有两种访问方式,DFS和BFS。其中DFS又分为前序、中序、后序三种方式。
因为二叉树的特性,所有的子树都为二叉树。所以遍历二叉树,可以用递归的方式实现。把每次遍历都简化为求解子问题。遍历子树来解决。
口诀:根左右
前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
步骤:
若二叉树为空则结束返回,否则:
(1)访问根结点。
(2)前序遍历左子树。
(3)前序遍历右子树 。
代码:
- public void preOrder(TreeNode root){
- if(root == null){
- return;
- }
- system.out.println(root.data);
- preOrder(root.left);
- preOrder(root.right);
- }
口诀:左右根
二叉树的中序遍历,就是先递归中序遍历左子树,再输出根结点的值,再递归中序遍历右子树
步骤:
若二叉树为空则结束返回,否则:
(1)中序遍历左子树。
(2)访问根结点。
(3)中序遍历右子树 。
代码:
- public void inOrder(TreeNode root){
- if(root == null){
- return;
- }
- inOrder(root.left);
- system.out.println(root.data);
- inOrder(root.right);
- }
口诀:左右根
二叉树的后序遍历,就是先递归后序遍历左子树,再递归后序遍历右子树,最后输出根结点的值
步骤:
若二叉树为空则结束返回,否则:
(1)后序遍历左子树。
(2)后序遍历右子树 。
(3)访问根结点。
代码:
- public void postOrder(TreeNode root){
- if(root == null){
- return;
- }
- postOrder(root.left);
- postOrder(root.right);
- system.out.println(root.data);
- }
层次遍历:二叉树的层次遍历 ,顾名思义就是指从二叉树的第一层(根节点)开始,从上至下逐层遍历,在同一层中,则按照从左到右的顺序对节点逐个访问。在逐层遍历过程中,按从顶层到底层的次序访问树中元素,在同一层中,从左到右进行访问。
算法思想:(BFS)广度优先搜索
用一个队列保存被访问的当前节点的左右孩子以实现层序遍历。
在进行层次遍历的时候,设置一个队列结构,遍历从二叉树的根节点开始,首先将根节点指针入队列,然后从队头取出一个元素,每取一个元素,执行下面两个操作:
1、访问该元素所指向的节点
2、若该元素所指节点的左右孩子节点非空,则将该元素所指节点的左孩子指针和右孩子指针顺序入队。此过程不断进行,当队列为空时,二叉树的层次遍历结束。
知识点:队列
队列是一种仅支持在表尾进行插入操作、在表头进行删除操作的线性表,插入端称为队尾,删除端称为队首,因整体类似排队的队伍而得名。它满足先进先出的性质,元素入队即将新元素加在队列的尾,元素出队即将队首元素取出,它后一个作为新的队首。
思路:
二叉树的层次遍历就是按照从上到下每行,然后每行中从左到右依次遍历,得到的二叉树的元素值。对于层次遍历,我们通常会使用队列来辅助:
因为队列是一种先进先出的数据结构,我们依照它的性质,如果从左到右访问完一行节点,并在访问的时候依次把它们的子节点加入队列,那么它们的子节点也是从左到右的次序,且排在本行节点的后面,因此队列中出现的顺序正好也是从左到右,正好符合层次遍历的特点。
具体做法:
代码:
- import java.util.*;
- public class Solution {
- public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
- ArrayList<ArrayList<Integer> > res = new ArrayList();
- if(root == null)
- //如果是空,则直接返回空数组 fast-template
- return res;
- //队列存储,进行层次遍历
- Queue<TreeNode> q = new ArrayDeque<TreeNode>();
- q.add(root);
- while(!q.isEmpty()){
- //记录二叉树的某一行
- ArrayList<Integer> row = new ArrayList();
- int n = q.size();
- //因先进入的是根节点,故每层结点多少,队列大小就是多少
- for(int i = 0; i < n; i++){
- TreeNode cur = q.poll();
- row.add(cur.val);
- //若是左右孩子存在,则存入左右孩子作为下一个层次
- if(cur.left != null)
- q.add(cur.left);
- if(cur.right != null)
- q.add(cur.right);
- }
- //每一层加入输出
- res.add(row);
- }
- return res;}
- }
二叉树是十分重要的数据结构,我们需要掌握二叉树的四种遍历方式。本文中,针对深度优先搜索的遍历:1、前序遍历。2、中序遍历。3、后序遍历。均是采用递归的方式进行操作。然而,这三种方式同样还可以通过栈的特点,已非递归的方式进行遍历。这也是我们需要重点掌握的。层次遍历则是利用队列先进先出的特点,每遍历一层后,进入队列的元素个数就是该层元素个数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。