数据结构和算法 - 二叉树 - 相关概念

在了解树之前的相关知识

1、树是在什么样子的条件下面产生的?

在计算机中,电脑里面的文件中是存在索引的,在图书馆中,书也是存在索引的,这都是为了实现快速查找的目的。想一下,所谓的数据结构,就是为了实现数据的快速增删改查,提高响应效率,使得系统看起来运行时流畅的,不然用户在使用的过程中,点击一次响应一天,那这个系统(软件)就没有人使用了。

在进行查找的时候,使用遍历的方式,是非常的消耗电脑的内存的,可以,但是有更好的查询方式。这个时候查找出来了二分查找,使得查询的效率得到了提升,从二分查找得到启示,使用树的结构进行元素的查找就算是查询10亿条数据,最坏情况下查询30次(因为查询的元素个数是2^30 ,30 也代表查询的次数 ,2^30次方表示可以放进去二叉树的元素数目,就是每一个节点都可以有两个子节点,所以是2的n 次方(n 为层数) )

2、树的存储方式

存储方式一般是使用数组或者链表的方式进行树的存储;
数组形式适合完全二叉树;
链表形式适合普通的树的表达;

3、树的遍历方式(Source:MOOC)

1、前序遍历

在这里插入图片描述

2、中序遍历

在这里插入图片描述

3、后序遍历

在这里插入图片描述

4、层序遍历

二叉树遍历的基本思想:
将二维结构的树状转换成为一维结构的线性结构
在这里插入图片描述

创建一个队列,把根结点 A 放进去,然后A 从队列里面抛出来,B C 放进去;

在这里插入图片描述

B 出来,把它的左右儿子放到队列里面去,就这样一直的进行循环即可
在这里插入图片描述

得到了最终的访问结果如下所示:
在这里插入图片描述

在这里插入图片描述

层序遍历主要做三件事情:
1、把队列里面最前面的元素抛出来;
2、把抛出来的元素进行打印;
3、把它的两个左儿子,右儿子结点放到队列里面去;

4、二叉树的结构的应用

(1)输出二叉树中的所有的叶子结点

在前序遍历中简单的加一行代码即可,当一个结点没有左儿子,没有右儿子的时候,我们才进行打印,其他的时候不打印,进行控制
在这里插入图片描述

(2)求解二叉树的高度

思想:求解二叉树中左右子树的最大高度 + 1;
也就是 max(HeighLeft,HeightRight)
在这里插入图片描述
上面使用了后序遍历求解了二叉树的高度

(3) 二元运算表达式树及其遍历

在这里插入图片描述

(4)判断使用了两种遍历序列能否唯一确定一颗二叉树?

告诉前序,后序两个,不能唯一确定,因为左右的边界是不清楚的;

在这里插入图片描述

使用前序,中序可以唯一确定二叉树

在这里插入图片描述

在这里插入图片描述

(5)树的同构问题

在这里插入图片描述

在这里插入图片描述

解决同构问题的关键点:
如何求解出来树的根结点呢?

关键:
1、二叉树是用什么进行表示的呢?
2、建立起来二叉树
3、进行二叉树的结构的判断,判断其是不是同构的二叉树

上面图片中的两棵树,左右经过了有限次数的盖子的相互互换,可以转变成为相同的树,所以认定两个树是同构的;

5、二叉树的一些典型应用

5.1 二叉搜索树(动态的查找)(BST)(二叉搜索树)(二叉查找树)

在这里插入图片描述
静态查找:查找的时候元素是不变的;(有二分查找)
动态查找:查找的时候伴随着增删改查等操作;
在这里插入图片描述
需要满足的条件:左子树小于根节点的数值,右子树大于根结点的数值

二叉搜索树的常用函数:

在这里插入图片描述

一、树

一个树只有一个根节点 父节点 兄弟节点 子节点 兄弟节点

1、空树

没有任何节点,就是空树; 一个树可以只有一个节点:就是根节点; 子树,左子树,右子树

2、节点的度

节点的度就是子树的个数,下面树中节点 1 的度就是 5;

节点 61 的度就是 0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yCO6kmgm-1632539807114)(img.png)]

3、叶子节点

叶子节点就是度为 0 的节点,就是没有子树的节点;
上图中的 4 就是叶子节点;
非叶子节点:度不为 0 的节点;

4、层数

根节点是第一层,后面的一次递增
从 1 开始,不是从 0 开始的;

5、节点的深度

从根节点到当前节点的唯一路径 比如 2 的深度是 2 ; 223 的深度是 4 ;是从1 开始的, 根节点是 1 ;

6、高度

从当前节点到最远叶子节点的路径上的节点数目 比如:2 的高度是 3;
5 的高度是 2 ;
从自己本身开始,自己就是 1 ;

7、树的深度等于树的高度

8、有序树

上图中的 2 3 4 4 5 6 都是顺序排列的

9、无序树

2 3 4 5 6 之间的顺序是没有规则的; 无序树也叫做自由树;

二、二叉树

1、二叉树的特点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mMWrEbOr-1632539807116)(img_1.png)]

(1)每个节点的度最大为 2 ; 最多拥有两个子树; 度的值为:0 1 2

(2)左子树和右子树之间是存在差异的;

(3)即使某个节点只有一颗子树,也是需要区分左子树和右子树

2、二叉树是有序树吗?

是的,因为左右子树是有严格的要求的

3、二叉树的性质

在这里插入图片描述

(1)非空二叉树的第 i 层,最多有 2 * i - 1 个节点 (i >= 1); 树从第一层开始计算的,不是 0 层开始计算的

(2)在高度为 h 二叉树上面,里面最多存在着 2^h - 1 个节点(h >= 1); 就是所有的元素都是满的;

(3)对于任何的非空二叉树,如果叶子节点的个数是 n0,度为 2 的节点个数为 n2 ,则有 n0 = n2 + 1

(4)二叉树的边数目

4、真二叉树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CdhIDqZ8-1632539807117)(img_2.png)]
所有节点的度要么是 0 要么是 2; 对于满二叉树,相关的定义需要严格一些;

下面的不是真二叉树:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KpfhzkCN-1632539807120)(img_4.png)]

5、满二叉树极其相关的性质

  • 满二叉树的节点的度要么是 0 ,要么是 2 ,并且所有的叶子节点都是在最后一层; 满二叉树是真二叉树的加强版本;
  • 在同样高度的二叉树中,满二叉树的叶子节点的数目是最多的,总节点的数目是最多的;
  • 满二叉树一定是真二叉树,真二叉树不一定是满二叉树;
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BYuF430n-1632539807121)(img_5.png)]

满二叉树的叶子节点的数量:2^(h - 1)
总节点的数目为:n = 2^h - 1;

6、完全二叉树

叶子节点只会出现在最后两层,并且最后一层的叶子节点都是靠着左边进行对齐的; 最后一层的叶子节点是需要靠着左边进行对齐的; 如果在最后一层是在右边对齐的,就不是完全二叉树,需要加以区别;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2abQc5MS-1632539807121)(img_6.png)]

7、完全二叉树的性质

  • 度为 1 的节点只有左子树;
  • 度为 1 的节点,要么是 0 个要么是 1 个;
  • 完全二叉树的倒数第二层一定是一个满二叉树;
  • 完全二叉树放满了就是满二叉树;
  • 同样节点数量的二叉树,完全二叉树的高度是最小的;

拥有 n 个节点的二叉树,从 1 开始编号,第一个节点就是根节点,当 i > 1 的时候,它的父节点 的编号为:floor(i / 2) ;
floor 是向下取整的函数;

如果 2i <= n ,左子节点的编号为: 2i;

2i > n 没有左子节点;

面试题目(计算叶子节点的数目)

一个完全二叉树有 768 个节点,求叶子节点的个数:

在这里插入图片描述
总节点数量:n n 为奇数 叶子节点的数量: n0 = (n + 1) / 2;

n 为偶数 叶子节点数量: n0 = n / 2;

结算叶子节点的数量: n0 = n / 2 + 1 / 2;

使用取整函数进行公式的统一: n0 = floor(n / 2 + 1 / 2); 完美解决
上面提到的向上,向下取整都是可以互相替换的 ceiling();

关于上面的公式总结:
叶子节点的个数:
n0 = floor((n + 1) / 2) = ceiling(n / 2)

非叶子节点的数目:
n1 + n2 = floor(n / 2) = ceiling((n - 1) / 2)

8、真二叉树,满二叉树,完全二叉树的区别

真二叉树:
所有的非叶子节点的度都是 2

满二叉树:
所有的非叶子节点的度都是 2 ,并且所有的叶子节点都在最后一层

完全二叉树:
二叉树,从左到右,从上到下面,按照顺序依次的,进行放置数据;

三、二叉搜索树(binary search tree)

在这里插入图片描述

// 二叉搜索树也叫做二叉查找树或者二叉排序树
public class BinarySearchTree {
}

1、二叉搜索树的相关性质

1、任意一个节点的值都是大于其左子树的所有节点的值
2、任意一个节点的值都小于其右子树的所有节点的值
3、左右子树也是一颗二叉搜索树

二叉搜索树可以大大提高搜索数据的效率;
二叉搜索树存储的元素必须是可以进行比较的,比如 int double 等;

2、二叉搜索树的接口设计

在这里插入图片描述
二叉树的插入,删除元素的操作,和插入的顺序是没有关系的,所以说二叉树是没有索引的相关概念的,也就是说,用不到;

四、搜索二叉树实现源代码

重要代码实现

add()

实现思路
 * if null == root
 * 		 在第一个位置进行元素的创建以及元素的连接即可

 * if null != root
 * 		 1、找到需要插入元素的父节点;
 *		 2、创建新的节点
 *		 3、parent.left = node || parent.right = node
    public void add(E element) {
        elementNotNullCheck(element);
        if (null == root) {
            root = new Node<>(element, null);
            System.out.println(root.element); 
            size++;
            return;
        } else {
            // 定义一个父节点,目的是与下面的联合使用,找到插入元素的饿父节点,方便元素的插入操作;
            Node<E> parent = root;
            // 找到父节点
            Node<E> node = root;
            int cmp = 0;
            while (node != null) {
                cmp = compare(node.element, element);

                // parent 变量是为了找出父节点,经过循环 node 在改变,parent 的位置也是在改变的
                parent = node;

                if (cmp > 0) {
                    node = node.right; // node 是中间变量,可以进行遍历操作,因为是树,所以进行左右的遍历
                } else if (cmp < 0) {
                    node = node.left;
                } else { // 剩下了返回值是 == 0 的;
                    return;
                }
            }

            // 循环出来找到了父节点的位置;
            // 找到了父节点所在的位置,进行父节点下面的额元素的插入,到底是在左边还是右边
            Node<E> newNode = new Node<>(element, parent);
            if (cmp > 0) {
                parent.left = newNode;
                System.out.println("添加到了父节点的左节点" + newNode.element);
            } else {
                parent.right = newNode;
                System.out.println("添加到了父节点的右节点" + newNode.element);
            }
            size++;
        }
    }

全部代码实现

public class BinarySearchTree<E> {
    // 创建在下面进行判断操作时候使用的比较器;
    private final Comparator<E> comparator;
    // 节点的根节点属性
    Node<E> root;
    // 节点的数目属性
    private int size;

    // 对于二叉树里面的比较器的赋值
    public BinarySearchTree(Comparator<E> comparator) {
        this.comparator = comparator;
    }

    // 可能是不会进行比较器的传递的
    public BinarySearchTree() {
        this(null);
    }


    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void clear() {

    }

    /**
     * if null == root
     * 在第一个位置进行元素的创建以及元素的连接即可
     * <p>
     * if null != root
     * 1、找到需要插入元素的父节点;
     * 2、创建新的节点
     * 3、parent.left = node || parent.right = node
     * <p>
     * 遇到的插入的值已经在树中存在了,应该怎么处理?
     * 1、直接return
     * 2、使用了覆盖
     *
     * @param element 需要放进去的元素的值
     */
    public void add(E element) {
        elementNotNullCheck(element);
        if (null == root) {
            root = new Node<>(element, null);
            System.out.println(root.element); 
            size++;
            return;
        } else {
            // 定义一个父节点,目的是与下面的联合使用,找到插入元素的饿父节点,方便元素的插入操作;
            Node<E> parent = root;
            // 找到父节点
            Node<E> node = root;
            int cmp = 0;
            while (node != null) {
                cmp = compare(node.element, element);

                // parent 变量是为了找出父节点,经过循环 node 在改变,parent 的位置也是在改变的
                parent = node;

                if (cmp > 0) {
                    node = node.right; // node 是中间变量,可以进行遍历操作,因为是树,所以进行左右的遍历
                } else if (cmp < 0) {
                    node = node.left;
                } else { // 剩下了返回值是 == 0 的;
                    return;
                }
            }

            // 循环出来找到了父节点的位置;
            // 找到了父节点所在的位置,进行父节点下面的额元素的插入,到底是在左边还是右边
            Node<E> newNode = new Node<>(element, parent);
            if (cmp > 0) {
                parent.left = newNode;
                System.out.println("添加到了父节点的左节点" + newNode.element);
            } else {
                parent.right = newNode;
                System.out.println("添加到了父节点的右节点" + newNode.element);
            }

            size++;
        }
    }

    public void remove(E element) {

    }

    public boolean contains(E element) {
        return false;
    }

    private void elementNotNullCheck(E element) {
        if (element == null) {
            try {
                throw new IllegalAccessException("element not be null!!!");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param e1
     * @param e2
     * @return 返回值等于 0;代表,两个元素 e1 和 e2 是相等的;
     * > 0, e1 > e2
     * < 0, e1 < e2
     */
    private int compare(E e1, E e2) {
        // 比较器不是空的,那就是有了比较器,直接使用比较器进行比较即可
        if (comparator != null) {
            return comparator.compare(e1, e2);
        }

        // 传进来的参数。没有对其进行比较器的传入,默认两个数值一定是可以进行比较的,把 e1 进行强制的类型转换,进行比较的方法的调用;
        // 强制进行了类型的转换之后,e1 具有了 Comparable 的功能;
        return ((java.lang.Comparable<E>) e1).compareTo(e2);
    }


    // 在二叉树里面,需要对于一个节点进行维护,保证各个节点之间的连线的正确性
    private static class Node<E> {
        E element;
        Node<E> left; // 表示开辟了内存空间,至于是否存放相关的 Node 数据类型,看后面的程序的需要
        Node<E> right;
        Node<E> parent;

        public Node(E element, Node<E> parent) { // element parent 才是必要的,其他的不是必要的,这两个元素是比较常用的
            this.element = element;
            this.parent = parent;
        }
    }
}

五、遍历

5.1 前序遍历

先找到根节点,然后找到其左子树,然后遍历右子树
在这里插入图片描述

5.2 中序遍历

先访问左子树,在访问根节点,再访问右子树
访问出来的数字是从小到大的呈现;
先访问右子树,在访问中间节点,再访问左节点;
在这里插入图片描述

5.3 后序遍历

根节点放在了后面进行遍历
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值