【数据结构】二叉树

一、树的存储结构

1.1 树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

  • 根节点没有前驱结点
  • 除根结点外,其余的结点被分为M(M>0)个互不相交的集合T1,T2,…Tm;每一个集合又是一棵与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
  • 树是递归定义的。

注意:在树形结构中,子树之间不能有交集。
在这里插入图片描述


在这里插入图片描述

1.2 树的结点

结点: 使用树结构存储的每一个数据元素都被称为“结点”;例如上图中的A,B,C…都是一个结点

叶子结点或终端结点: 如果结点没有任何子结点,那么此结点称为叶子结点(叶结点,终端结点);例如上图中的K,L,F…都是这棵树的叶子结点。

双亲结点或父结点: 若一个结点含有子结点,则这个结点称为其子结点的父结点;上图中的A是B,C,D的父结点。

孩子结点或子结点: 一个结点含有的子树的根结点称为该结点的子结点;上图中的K为E的子结点。

根结点: 一棵树中,没有双亲结点的结点;只有A结点

非终端结点或分支结点: 度不为0的结点;例如A,B,C,D,E,H

兄弟结点: 具有相同父结点的结点互称为兄弟结点;E和F的父节点都是B,所以它们为兄弟结点。

堂兄弟结点: 双亲在同一层的结点互为堂兄弟;B,C,D或者K,L,M为堂兄弟结点

结点的祖先: 从根到该结点所经分支上的所有结点;A是祖先

1.3 子树和空树

子树: 在上图中,整棵树的根结点为结点 A,而如果单看结点 B、E、F、K、L 组成的部分来说,也是棵树,而且节点 B 为这棵树的根结点。所以称 B、E、F、K、L 这几个结点组成的树为整棵树的子树;同样,结点 E、K、L 构成的也是一棵子树,根结点为 E。

注意:单个结点也是一棵树,只不过根结点就是它本身。上图中,结点 K、L、F 等都是树,且都是整棵树的子树。

知道了子树的概念后,树可以这样定义:树是由根结点和若干棵子树构成的

空树: 如果集合本身为空,那么构成的树就被称为空树。空树中没有结点。

1.4 结点的度和层次

结点的度: 一个结点含有子树的个数称为该结点的度;例如A结点,在其下分为3颗子树,所以该结点的度为3.

树的度: 一棵树中,所有结点度的最大值称为树的度;上图中各个结点中的最大值为3,所以树的度为3.

结点的层次: 从根开始定义起,根为第1层,根的子结点为第2层;上图中,A 结点在第一层,B、C、D 为第二层,E、F、G、H、I、J 在第三层,K、L、M 在第四层。

树的高度或深度: 树中结点的最大层次;上图中树的深度为 4。

1.5 森林

子孙: 以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
森林: 由m(m>=0)棵互不相交的树组成的集合称为森林,分别以 B、C、D 为根结点的三棵子树就可以称为森林。

1.6 树的表示形式

树不同于线性结构,它是非线性的,其存储的是具有“一对多”关系的数据元素的集合;其表示方法有双亲表示法,孩子表示法、孩子双亲表示法、孩子兄弟表示法等等。我们这里就简单的了解其中最常用的孩子兄弟表示法。

class Node {
	int value; // 树中存储的数据
	Node firstChild; // 第一个孩子引用
	Node nextBrother; // 下一个兄弟引用
}

在这里插入图片描述

1.7 树的应用

  • 文件系统管理(目录和文件)

在这里插入图片描述

在这里插入图片描述

二、 二叉树

2.1 概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

在这里插入图片描述
从上图中可以得出二叉树的条件

  1. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
  2. 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2;

所以 1a) 就是一棵二叉树,而 b) 则不是。
在这里插入图片描述
注:对于任意的二叉树都是由以下几种情况复合而成的
在这里插入图片描述

2.2 二叉树的性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 2 ^( i -1)(i>0)个结点
  2. 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2^(k - 1) (k>=0)
  3. 任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1

性质 3 的计算方法为:对于一个二叉树来说,除了度为 0 的叶子结点和度为 2 的结点,剩下的就是度为 1 的结点(设为 n1),那么总结点 n=n0+n1+n2。
同时,对于每一个结点来说都是由其父结点分支表示的,假设树中分枝数为 B,那么总结点数 n=B+1。而分枝数是可以通过 n1 和 n2 表示的,即 B=n1+2* n2。所以,n 用另外一种方式表示为 n=n1+2*n2+1。
两种方式得到的 n 值组成一个方程组,就可以得出 n0=n2+1。

2.3 满二叉树

如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。


上图就是满二叉树。

满二叉树除了满足普通二叉树的性质,还具有以下性质:

  1. 满二叉树中第 i 层的节点数为 2^(n-1) 个。
  2. 深度为 k 的满二叉树必有 2^k-1 个节点 ,叶子数为 2 ^(k-1)。
  3. 满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。
  4. 具有 n 个节点的满二叉树的深度为 log2(n+1)。

2.4 完全二叉树

如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。

在这里插入图片描述

如图 a) 所示是一棵完全二叉树,图 b) 由于最后一层的节点没有按照从左向右分布,因此只能算作是普通的二叉树。

完全二叉树除了具有普通二叉树的性质,它自身也具有一些独特的性质:

  1. 具有n个结点的完全二叉树的深度k为 ⌊log2n⌋+1。上取整

⌊log2n⌋ 表示取小于 log2n 的最大整数。例如,⌊log2 4⌋ = 2,而 ⌊log2 5⌋ 结果也是 2。

  1. . 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
    若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
    若2i+1<n,左孩子序号:2i+1,否则无左孩子
    若2i+2<n,右孩子序号:2i+2,否则无右孩子

2.5 二叉树的存储

二叉树的存储结构有两种,分别为顺序存储链式存储

顺序存储

二叉树的顺序存储,指的是使用顺序表(数组)存储二叉树。需要注意的是,顺序存储只适用于完全二叉树。换句话说,只有完全二叉树才可以使用顺序表存储。因此,如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树。

完全二叉树的顺序存储,仅需从根节点开始,按照层次依次将树中节点存储到数组即可。
在这里插入图片描述

例如,存储图 2 所示的完全二叉树,其存储状态如图 3 所示:
在这里插入图片描述
我们在讲优先级队列时仔细讲解

链式存储

其实二叉树并不适合用数组存储,因为并不是每个二叉树都是完全二叉树,普通二叉树使用顺序表存储或多或少会存在空间浪费的现象。

常见的链式存储方式:

// 孩子表示法
class Node {
	int val; // 数据域
	Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
	Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
}
// 孩子双亲表示法
class Node {
	int val; // 数据域
	Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
	Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
	Node parent; // 当前节点的根节点
}

我们主要用孩子表示法来说明。

2.6 二叉树的遍历

遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题(比如:打印节点内容)
我们用这颗树来组织;
在这里插入图片描述

public class BinaryTree {
    static class TreeNode {
        private char val; // 节点值
        private TreeNode left; // 左结点的引用
        private TreeNode right; // 右结点的引用
        public TreeNode(char val) {
            this.val = val;
        }
    }
    public TreeNode root;
    //简单的构建二叉树
	public void createBinaryTree(){
        TreeNode A = new TreeNode('A');
        TreeNode B = new TreeNode('B');
        TreeNode C = new TreeNode('C');
        TreeNode D = new TreeNode('D');
        TreeNode E = new TreeNode('E');
        TreeNode F = new TreeNode('F');
        TreeNode G = new TreeNode('G');
        TreeNode H = new TreeNode('H');
        A.left = B;
        A.right = C;
        B.left = D;
        B.right = E;
        C.left = F;
        C.right = G;
        E.right = H;
        root = A;
    }
}

前序遍历(先序遍历)

在这里插入图片描述

实现思想:访问根结点—>根的左子树—>根的右子树
动态图解:先序遍历就是小人从根结点开始,依次遍历得到结点值,最后回到原点

    void preOrder(TreeNode root){
        //递归结束条件:结点为空
        if (root == null) {
            return;
        }
        System.out.print(root.val + " ");
        preOrder(root.left);
        preOrder(root.right);
    }

中序遍历

在这里插入图片描述

实现思想:根的左子树—>根节点—>根的右子树
动态图解:中序遍历的结果就是把结点依次投影。

    void inOrder(TreeNode root){
        if (root == null) {
            return;
        }
        preOrder(root.left);
        System.out.print(root.val + " ");
        preOrder(root.right);
    }

后序遍历

在这里插入图片描述

实现思想:根的左子树—>根节点—>根的右子树
动态图解:后序遍历就像剪葡萄,只能一个个剪,按照掉落顺序即为后序遍历结果。

    void postOrder(TreeNode root){
        if (root == null) {
            return;
        }
        preOrder(root.left);
        preOrder(root.right);
        System.out.print(root.val + " ");
    }

层序遍历

实现思想:从根结点开始,从左到右,至上而下的访问每一个结点。

    void levelOrder(TreeNode root){
        if (root == null) return;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            System.out.print(cur.val + " ");
            if (cur.left != null) {
                queue.offer(cur.left);
            }
            if (cur.right  != null) {
                queue.offer(cur.right);
            }
        }
    }

2.7二叉树的基本操作

获取树中节点的个数

	int nodeCount = 0;
    // 获取树中节点的个数
    int size1(TreeNode root) {
        if (root == null) {
            return 0;
        }
        nodeCount++;
        size1(root.left);
        size1(root.right);
        return nodeCount;
    }
    //子问题求解
    int size(TreeNode root){
        return root == null ? 0 : size(root.left) + size(root.right) + 1;
    }

获取叶子节点的个数

 // 遍历  =  求叶子结点个数
    static int leafCount = 0;
    void getLeafNodeCount(TreeNode root) {
        if(root == null) {
            return;
        }
        if(root.left == null && root.right == null) {
            leafCount ++;
        }
        getLeafNodeCount(root.left);
        getLeafNodeCount(root.right);
    }
       // 子问题思路-求叶子结点个数
    int getLeafNodeCount2(TreeNode root) {
        if(root == null) {
            return 0;
        }
        if(root.left == null && root.right == null) {
            return 1;
        }
        return getLeafNodeCount2(root.left) + getLeafNodeCount2(root.right);
    }

获取第K层节点的个数

// 获取第K层节点的个数
    int getKLevelNodeCount(TreeNode root, int k){
        if (root == null || k <= 0) {
            return 0;
        }
        return k == 1 ? 1 : getKLevelNodeCount(root.left, k - 1) + getKLevelNodeCount(root.right, k - 1);
    }

获取二叉树的高度

// 获取二叉树的高度
    int getHeight(TreeNode root){
        if (root == null) {
            return 0;
        }
        int leftMax = getHeight(root.left);
        int rightMax = getHeight(root.right);
        return Math.max(leftMax, rightMax) + 1;
    }

检测值为value的元素是否存在

// 检测值为value的元素是否存在
    TreeNode find(TreeNode root, char val){
        if (root == null) return null;
        if (root.val == val) {
            return root;
        }
        TreeNode ret = find(root.left, val);
        if (ret != null) {
            return ret;
        }
        ret = find(root.right, val);
        if (ret != null) {
            return ret;
        }
        return null;
    }

判断一棵树是不是完全二叉树

boolean isCompleteTree(TreeNode root){
        if (root == null) {
            return true;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node != null) {
                queue.offer(node.left);
                queue.offer(node.right);
            } else {
                break;
            }
        }
        while (!queue.isEmpty()) {
            TreeNode tmp = queue.peek();
            if (tmp != null) {
                return false;
            }
            queue.poll();
        }
        return true;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zzt.opkk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值