算法与数据结构--从二叉搜索树到红黑树(1)

定义

以层次化方式组织和存放数据的特定数据结构。

在我们日常的生活中也会有树的结构,只不过我们不会敏感到:哦 原来这就是一棵树呀。比如我们的家谱,爷爷下有爸爸,爸爸这一层又有兄弟姐妹,爸爸下一层还有你。
在这里插入图片描述

这样的结构有什么好处呢:
1)我能迅速的知道爷爷有多少子女
2)我还能迅速知道堂妹1还有个亲姐妹是谁
3)等等。。。。

所以树的最大的意义是:提高我们的搜索性能!

在树里每一个存放值的空间叫做节点
爷爷和爸爸之间的关系为父子节点
堂妹1和堂妹2分别为叔叔节点的左子节点和右子节点

二叉树

定义

二叉树一种特殊的树结构-二叉树(binary tree),它每个节点最多有两个子结点,亦称左孩子和右孩子。

我们在看上面那张家谱图,它就不是一棵二叉树,因为爷爷下有三个子女,而二叉树限制了每个节点最多有两个节点。

在这里插入图片描述

代码表示

二叉树一般是以链表的形式进行表示,我们把每个节点当作一个对象,则它有哪些属性呢?

  1. 节点本身的值
  2. 根节点,也就是最顶层的节点(只有一个)
  3. 左节点
  4. 右节点

其实节点还有父节点,但是在每个节点对象里是没有保存的,我们只需要在类里定义一个根节点的全局变量,每次去搜索一个树的时候,都从根节点至上而下搜索就性了。

/**
 * @program: java-club
 * @description: 树节点 链表的结构
 * @author: heyunpeng
 * @created: 2021/04/18 13:56
 */
public class TreeNode {
    private int val;
    private TreeNode leftTreeNode;
    private TreeNode rightTreeNode;

    public TreeNode(int val) {
        this.val = val;
    }

    public int getVal() {
        return val;
    }

    public void setVal(int val) {
        this.val = val;
    }

    public TreeNode getLeftTreeNode() {
        return leftTreeNode;
    }

    public void setLeftTreeNode(TreeNode leftTreeNode) {
        this.leftTreeNode = leftTreeNode;
    }

    public TreeNode getRightTreeNode() {
        return rightTreeNode;
    }

    public void setRightTreeNode(TreeNode rightTreeNode) {
        this.rightTreeNode = rightTreeNode;
    }

    @Override
    public String toString() {
        return "TreeNode{" +
                "val=" + val +
                ", leftTreeNode=" + leftTreeNode +
                ", rightTreeNode=" + rightTreeNode +
                '}';
    }
}

二叉搜索树

定义

在这里插入图片描述

单从名字来看,它的优势应该是表现在搜索,也就是如何从一棵二叉树里迅速的找到对应的节点,我们先来看看二叉树的规则有哪些。

  1. 如果它的左子树不为空,则左子树上结点的值都小于根结点。
  2. 如果它的右子树不为空,则右子树上结点的值都大于根结点。
  3. 子树同样也要遵循以上两点

二叉树还有个特性:它的中序遍历是有序的。
 
 

二叉搜索树的插入

要领:

  1. 插入的值最后一定是落在叶子节点的(也就是不需要变更树的结构)
  2. 插入是从根节点开始比较,如果插入的值比它大则继续找它的右子节点,反之找左子节点,直到比较节点不在有对应的子节点为止,这时候只需要把该值设置为对应的子节点即可。这其实就是一个查找的过程,这不过到查找的最后挂上了个叶子节点罢了。
/**
 * @program: java-club
 * @description: 二叉搜索树
 * @author: heyunpeng
 * @created: 2021/04/18 13:55
 */
public class SearchBinaryTree {
    private TreeNode root; //需要维护root,毕竟增删查都从root开始

    private void insert(int val) {
        if (root == null) {
            root = new TreeNode(val);
            return;
        } else {
            //从根节点开始递归,毕竟每个子节点的任务其实是一致的
            compareAndInsert(root, val);
        }
    }

    //这个过程其实是只有递没有归的
    private void compareAndInsert(TreeNode treeNode, int val) {
        //如果插入的值比节点值小,则去找左边的子节点
        if (val <= treeNode.getVal()) {
            if (treeNode.getLeftTreeNode() != null) {
                //继续递归
                compareAndInsert(treeNode.getLeftTreeNode(), val);
            } else { //如果该节点没有需要继续查找的左子节点,则直接把val设置为子节点
                TreeNode finalTreeNode = new TreeNode(val);
                treeNode.setLeftTreeNode(finalTreeNode);

            }
        } else { //反之亦然
            if (treeNode.getRightTreeNode() != null) {
                //继续递归
                compareAndInsert(treeNode.getRightTreeNode(), val);
            } else { //如果该节点没有需要继续查找的左子节点,则直接把val设置为子节点
                TreeNode finalTreeNode = new TreeNode(val);
                treeNode.setRightTreeNode(finalTreeNode);
            }
        }
    }
}

*完整代码会在文章最后给出,需要注意的是目前的实现并没有考虑线程安全问题的

我这里是通过递归的方式进行的编写,当然你也可以通过直接遍历循环的方式实现。

其实直接通过程序我们可以看出来它的时间复杂度为logn,我们每一层都进行了大小比较,来决定接下来是否左或者右的路由,则其实就是一种二分查找的思想嘛。当然我们如果需要给搜索二叉树插入n个数,那它的时间复杂度则为nlogn
 
 

搜索二叉树的查找

查找其实和插入的方法是一致的,直接贴代码

	private TreeNode targetTreeNode = null; //全局变量,表示被找到的节点
    private TreeNode find(int val) {
        compareAndFind(root, val);
        return targetTreeNode;
    }

    private void compareAndFind(TreeNode treeNode, int val) {
        if (treeNode != null) {
            //如果值与节点值相等,则找到了该节点了,停止递,并给全局变量targetTreeNode赋值
            if (val == treeNode.getVal()) {
                targetTreeNode = treeNode;
                return;
            } else if (val < treeNode.getVal()) {
                if (treeNode.getLeftTreeNode() != null) {
                    //继续左子节点查找
                    compareAndFind(treeNode.getLeftTreeNode(), val);
                }
            } else {
                if (treeNode.getLeftTreeNode() != null) {
                    //继续右子节点查找
                    compareAndFind(treeNode.getRightTreeNode(), val);
                }
            }

        }
    }

 
 

搜索二叉树的删除

在这里插入图片描述
删除可就比查找和查找麻烦多了,总共可以分为3中情况:

  1. 情况a,删除的为叶子节点,直接取消掉父节点与之连接的指针

  2. 情况b,删除的为树节点,但是这个树节点下面只有一个子节点,则直接将这个子节点替换父节点然后挂在爷爷节点即可。

  3. 情况c,删除的为树节点,但是这个树节点有左右两个子节点,这种情况下的删除简直是灾难。

    先说奥义:
    step1:找出后继节点,一个节点的后继节点是指,这个节点在中序遍历序列中的下一个节点,记住中序遍历是有序的,其实就是找第一个比他大的节点。

    step2:用后继节点去代替被删除节点的位置。

    而最重要的就是何为后继节点,这里又要分为3种情况了。。。。

    1) 后继节点为待删除节点的子节点,后继节点直接代替待删除节点。
    在这里插入图片描述

    2)后继节点不为待删除节点的子节点
    2.1)后继节点没有右节点,同样可以直接代替
    在这里插入图片描述
    在这里插入图片描述
    2.2)后继节点有右节点,就要先将后继节点的右子节点去替换后继节点的位置,然后再将后继节点去替换待删除节点的位置
    在这里插入图片描述

在这里插入图片描述

删除节点的代码逻辑如下:

  • 通过调用查找方法,找到待删除的节点

  • 判断待删除节点是否是叶子节点,直接去掉

  • 判断待删除节点是子树,但是只有一个子节点,直接将子节点去替代待删除节点的位置

  • 判断待删除节点是子树,但是却有两个节点
    1)找出后继节点,(这里是一定能找到的,可以想想为什么)
    2)若后继节点就是待删除节点的子节点,则执行替代操作
    3)若后继节点不是待删除节点的子节点,且没有右子节点(其实这个后继节点就是个叶子节点了),执行替换操作
    4)若后继节点不是待删除节点的子节点,且有有子节点,就要先将后继节点的右子节点去替换后继节点的位置,然后再将后继节点去替换待删除节点的位置。

  • 节点删除的代码实现先放着,等有空再实现吧。。。。

 
 

二叉搜索树的缺陷

比如我们现在新建一个搜索二叉树,并插入如下有序数组[1,2,3,4,5,6]
这种情况下会出现一种极端现象,二叉树直接退化为链表了,而这也是红黑树或者平衡二叉树诞生的背景,它们的出现就是为了解决二叉搜索树的退化问题。
在这里插入图片描述

红黑树请看下一篇文章 算法与数据结构–从二叉搜索树到红黑树(2)

搜索二叉树完整代码如下:

package com.yan.javaClub.algorithms.tree;

/**
 * @program: java-club
 * @description: 树节点 链表的结构
 * @author: heyunpeng
 * @created: 2021/04/18 13:56
 */
public class TreeNode {
    private int val;
    private TreeNode leftTreeNode;
    private TreeNode rightTreeNode;

    public TreeNode(int val) {
        this.val = val;
    }

    public int getVal() {
        return val;
    }

    public void setVal(int val) {
        this.val = val;
    }

    public TreeNode getLeftTreeNode() {
        return leftTreeNode;
    }

    public void setLeftTreeNode(TreeNode leftTreeNode) {
        this.leftTreeNode = leftTreeNode;
    }

    public TreeNode getRightTreeNode() {
        return rightTreeNode;
    }

    public void setRightTreeNode(TreeNode rightTreeNode) {
        this.rightTreeNode = rightTreeNode;
    }

    @Override
    public String toString() {
        return "TreeNode{" +
                "val=" + val +
                ", leftTreeNode=" + leftTreeNode +
                ", rightTreeNode=" + rightTreeNode +
                '}';
    }
}

package com.yan.javaClub.algorithms.tree;

/**
 * @program: java-club
 * @description: 二叉搜索树
 * @author: heyunpeng
 * @created: 2021/04/18 13:55
 */
public class SearchBinaryTree {
    private TreeNode root; //需要维护root,毕竟增删查都从root开始

    private void insert(int val) {
        if (root == null) {
            root = new TreeNode(val);
            return;
        } else {
            //从根节点开始递归,毕竟每个子节点的任务其实是一致的
            compareAndInsert(root, val);
        }
    }

    //这个过程其实是只有递没有归的
    private void compareAndInsert(TreeNode treeNode, int val) {
        //如果插入的值比节点值小,则去找左边的子节点
        if (val <= treeNode.getVal()) {
            if (treeNode.getLeftTreeNode() != null) {
                //继续递归
                compareAndInsert(treeNode.getLeftTreeNode(), val);
            } else { //如果该节点没有需要继续查找的左子节点,则直接把val设置为子节点
                TreeNode finalTreeNode = new TreeNode(val);
                treeNode.setLeftTreeNode(finalTreeNode);

            }
        } else { //反之亦然
            if (treeNode.getRightTreeNode() != null) {
                //继续递归
                compareAndInsert(treeNode.getRightTreeNode(), val);
            } else { //如果该节点没有需要继续查找的左子节点,则直接把val设置为子节点
                TreeNode finalTreeNode = new TreeNode(val);
                treeNode.setRightTreeNode(finalTreeNode);
            }
        }
    }

    //中序遍历 左根右
    //把握奥义 根节点输出
    private void traverse() {
        mid_traverse(root);
    }

    //拆分为子任务
    //有递有归
    private void mid_traverse(TreeNode treeNode) {
        //递的停止条件
        if (treeNode != null) {
            mid_traverse(treeNode.getLeftTreeNode());//左
            System.out.println(treeNode.getVal());//根节点输出
            mid_traverse(treeNode.getRightTreeNode());//右
        }
    }

    private TreeNode targetTreeNode = null; //全局变量,表示被找到的节点
    private TreeNode find(int val) {
        compareAndFind(root, val);
        return targetTreeNode;
    }

    private void compareAndFind(TreeNode treeNode, int val) {
        if (treeNode != null) {
            //如果值与节点值相等,则找到了该节点了,停止递,并给全局变量targetTreeNode赋值
            if (val == treeNode.getVal()) {
                targetTreeNode = treeNode;
                return;
            } else if (val < treeNode.getVal()) {
                if (treeNode.getLeftTreeNode() != null) {
                    //继续左子节点查找
                    compareAndFind(treeNode.getLeftTreeNode(), val);
                }
            } else {
                if (treeNode.getLeftTreeNode() != null) {
                    //继续右子节点查找
                    compareAndFind(treeNode.getRightTreeNode(), val);
                }
            }

        }
    }

    public static void main(String[] args) {
        SearchBinaryTree searchBinaryTree = new SearchBinaryTree();
        //插入一段无序的数组
        int[] array = {5,2,6,8,9,1,3,10};
        for (int i : array) {
            searchBinaryTree.insert(i);
        }
        //中序遍历出来,看看是否是有序排列的
        System.out.println("***打印遍历结果*****");
        searchBinaryTree.traverse();

        //查找
        System.out.println("***打印查找值为5的节点*****");
        System.out.println(searchBinaryTree.find(5));
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值