Java 数据结构之二叉搜索树(头歌平台,详细注释)

目录

第1关:二叉搜索树的介绍与构建

任务描述

相关知识

二叉搜索树

什么是二叉搜索树

二叉搜索树的构建

代码: 

第2关:二叉搜索树的删除

任务描述

相关知识

后继结点

二叉搜索树的删除

代码: 

第3关:二叉搜索树的查找

任务描述

相关知识

二叉搜索树的查找

代码: 


第1关:二叉搜索树的介绍与构建

任务描述

顾名思义,一棵二叉搜索树是以一棵二叉树来组织的,但是在组织的时候需要满足一些特定的性质。

本关任务:构建一棵二叉搜索树,向其中添加结点。

相关知识
二叉搜索树
什么是二叉搜索树

二叉搜索树(binary search tree),又称二叉排序树,它或者是空树,或者是满足如下性质的二叉树:

  1. 若它的左子树非空,则左子树上所有结点的值均小于根结点的值;
  2. 若它的右子树非空,则右子树上所有结点的值均大于根结点的值;
  3. 左、右子树本身又各是一棵二叉排序树。

简而言之,二叉搜索树中任一结点x,其左(右)子树中任一结点y(若存在)的关键字必小(大)于x的关键字。

下图是两棵二叉搜索树:

可以看到,对于树中的任何结点,其值大于左结点,小于右结点。 如果我们以中序遍历遍历一棵二叉搜索树,所得到的中序序列是一个递增有序序列。

上图二叉搜索树的中序遍历结果均为2 3 4 5 7 8

二叉搜索树的构建

构建一棵二叉搜索树,实际是构建一棵二叉树,但在添加结点时,需要满足二叉搜索树的性质。 以输入序列5 3 7 2 4 8为例,构建一棵二叉搜索树。如下图所示:

(1)二叉搜索树为空,5为根结点;

(2)输入值3比根结点5小,且此时根结点的左结点为空,所以3添加为根结点的左结点;

(3)输入值7比根结点5大,添加为根结点的右结点;

(4)输入值2比根结点5小,应添加至根结点的左结点,但此时根结点的左结点是323比较,小于,所以添加为3的左结点;

(5)输入值4比根结点5小,应添加至根结点的左结点,但此时根结点的左结点是343比较,大于,所以添加为3的右结点;

(6)输入值8比根结点5大,应添加至根结点的右结点,但此时根结点的左结点是787比较,大于,所以添加为7的右结点。

循环中小于当前需要置入结点放左边,大于当前置入结点放右边

代码: 
package step1;

/**
 * Created by zengpeng on 2018/3/3.
 */
public class BSTree {

    private TreeNode root;//根结点

    public BSTree() {
        root = null;
    }

    /**
     * 向树root中插入key
     *
     * @param key 要插入的值
     */
    public void insert(int key) {
        /********** Begin *********/  
    //二叉搜索树,严格要求左子树的值小于中间结点,右子树的值大于中间结点
    TreeNode x = root;//创建一个结点x,储存根结点root
    TreeNode p = null;//创建一个结点p,初始化为空
    while (x != null) {//如果x不为空则继续,找到需要插入的结点的前一个位置
    p = x;//用p储存x结点
    if (key < x.item) {//如果值小于当前结点的值,则进入左子树
    x = x.leftChild;//将左子树储存x
    } else {//如果值大于当前结点的值,则进入右子树
    x = x.rightChild;//将右子树储存x
    }
    }
    //找到位置后再进行判断,应该构建左还是右
    if (null == p) {//如果p为空,代表没有根结点
    root = new TreeNode(key);//根结点储存需要构建的当前结点
    } else if (key < p.item) {//如果值小于p结点的值
    p.leftChild = new TreeNode(key);//p的左子树构建当前结点
    } else {//如果值大于p结点的值
      p.rightChild = new TreeNode(key);//p的右子树构建当前结点
    }
        /********** End *********/
    }

    public void preOrder() {
        preOrder(root);
    }

    public void inOrder() {
        inOrder(root);
    }

    public void postOrder(){
        postOrder(root);
    }

    private void preOrder(TreeNode node) {
        if (node != null) {
            System.out.print(node.item + " ");
            preOrder(node.leftChild);
            preOrder(node.rightChild);
        }
    }

    private void inOrder(TreeNode node) {
        if (node != null) {
            inOrder(node.leftChild);
            System.out.print(node.item + " ");
            inOrder(node.rightChild);
        }
    }

    private void postOrder(TreeNode node) {
        if (node != null) {
            postOrder(node.leftChild);
            postOrder(node.rightChild);
            System.out.print(node.item + " ");
        }
    }


    public static class TreeNode {
        private TreeNode leftChild;
        private TreeNode rightChild;
        private int item;

        public TreeNode(int item) {
            this(null, null, item);
        }

        public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
            this.leftChild = leftChild;
            this.rightChild = rightChild;
            this.item = item;
        }
    }
}

以下是测试样例:

测试输入:

  1. 3 2 1 0 21

预期输出:

  1. 前序遍历: 3 2 1 0 21
  2. 中序遍历: 0 1 2 3 21
  3. 后序遍历: 0 1 2 21 3

第2关:二叉搜索树的删除

任务描述

在上一关,我们构建了一棵二叉搜索树,并且可以向其添加结点数据,本关将实现在二叉搜索树的删除功能。

本关任务是:实现二叉搜索树的删除功能。

相关知识
后继结点

给定一棵二叉搜索树中的一个结点,有时候需要按中序遍历的次序查找它的后继。如果所有的关键字互不相同,则一个结点x的后继结点是大于x的最小关键字的结点。

上图中,结点66的后继结点为6820的后继结点为66

二叉搜索树的删除

从一棵二叉搜索树T中删除一个结点z的整个策略可分为三个基本情况:

  • 如果z没有孩子结点,那么只是简单地将它删除,并修改它的父结点;
  • 如果z只有一个孩子结点,那么将这个孩子提升到树中z的位置上,并修改z的父结点,使其指向z的孩子结点;
  • 如果z有两个孩子,那么找z的后继结点y,并让y占据树中z的位置。z的原来右子树部分成为y的新的右子树,并且z的左子树成为y的新的左子树。(根据二叉搜索树的性质,后继结点y一定在z的右子树中)。

下列是几种情况的图示: (1)删除没有子结点的结点;

因为结点150没有孩子结点,所以直接把其删掉。

(2)删除有一个子结点的结点;

结点50有一个孩子结点,我们只需把其孩子结点提升到结点50的位置即可。

(3)删除有两个子结点的结点;

对于有两个子结点的结点50,在其右子树中找到其后继结点66,使后继结点相应的替换结点50

删除的方法是如果有左子树,以左子树中最大值作为新的树根,否则,以右子树最小值作为树根。

代码: 
package step2;

/**
 * Created by zengpeng on 2018/3/14.
 */
public class BSTree {
    private TreeNode root;//根结点

    public BSTree() {
        root = null;
    }

    /**
     * 向树root中插入a
     *
     * @param key 要插入的值
     */
    public void insert(int key) {
        TreeNode x = root;
        TreeNode p = null;//始终指向x的父结点
        while (x != null) {
            p = x;
            if (key < x.item) {
                x = x.leftChild;
            } else {
                x = x.rightChild;
            }
        }
        if (null == p) {//空树
            root = new TreeNode(key);
        } else if (key < p.item) {
            p.leftChild = new TreeNode(key);
        } else {
            p.rightChild = new TreeNode(key);
        }
    }

    /**
     * 在树root中删除结点key
     *
     * @param key
     * @return
     */
    public void delete(int key) {
        root = delete(root, key);
    }

    private TreeNode delete(TreeNode x, int key) {
        /********** Begin *********/
        if (x == null)//x为根结点,如果根结点为空
            return null;//返回空
            //递归实现
        if (key < x.item) {//如果删除的值小于当前结点
        x.leftChild = delete(x.leftChild, key);//再次进入delete方法,往左子树继续搜寻需要删除的结点
        } else if (key > x.item) {
        x.rightChild = delete(x.rightChild, key);//再次进入delete方法,往右子树继续搜寻需要删除的结点
        } else {//如果等于,由于是二叉搜索树需要严格满足条件
        if (x.leftChild == null) return x.rightChild;//如果左子树为空,返回右子树的结点
        if (x.rightChild == null) return x.leftChild;//如果右子树为空,返回左子树的结点
        TreeNode t = x;//用t储存x结点
        x = min(t.rightChild);//寻找t右子树的最小值结点,并用x储存,需要用最小结点代替要被删除的结点的位置
        x.rightChild = deleteMin(t.rightChild);//删除找到的最小结点
        x.leftChild = t.leftChild;//让最小结点的左子树变为要删除的结点的左子树
}
        return x;//返回


        /********** End *********/
    }

    /**
     * 删除树x中的最小结点
     *
     * @param x
     * @return
     */
    private TreeNode deleteMin(TreeNode x) {
        if (x.leftChild == null) return x.rightChild;
        x.leftChild = deleteMin(x.leftChild);
        return x;
    }

    /**
     * 查找树x中的最小结点
     *
     * @param x
     * @return
     */
    private TreeNode min(TreeNode x) {
        TreeNode p = x;
        while (p.leftChild != null) {
            p = p.leftChild;
        }
        return p;
    }

    public void preOrder() {
        preOrder(root);
    }

    private void preOrder(TreeNode node) {
        if (node != null) {
            System.out.print(node.item + " ");
            preOrder(node.leftChild);
            preOrder(node.rightChild);
        }
    }

    public void inOrder() {
        inOrder(root);
    }

    private void inOrder(TreeNode node) {
        if (node != null) {
            inOrder(node.leftChild);
            System.out.print(node.item + " ");
            inOrder(node.rightChild);
        }
    }

    public void postOrder() {
        postOrder(root);
    }

    private void postOrder(TreeNode node) {
        if (node != null) {
            postOrder(node.leftChild);
            postOrder(node.rightChild);
            System.out.print(node.item + " ");
        }
    }


    public static class TreeNode {
        private TreeNode leftChild;
        private TreeNode rightChild;
        private int item;

        public TreeNode(int item) {
            this(null, null, item);
        }

        public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
            this.leftChild = leftChild;
            this.rightChild = rightChild;
            this.item = item;
        }
    }
}

以下是测试样例:

测试输入:3 2 1 0 21 预期输出:

  1. 前序遍历: 3 2 1 0 21
  2. 中序遍历: 0 1 2 3 21
  3. 后序遍历: 0 1 2 21 3
  4. 前序遍历: 21 2 1 0
  5. 中序遍历: 0 1 2 21
  6. 后序遍历: 0 1 2 21

第3关:二叉搜索树的查找

任务描述

我们经常需要查找一个存储在二叉搜索树中的关键字,本关卡将讨论并实现查找功能。

本关任务:实现二叉搜索树的查找功能,如果指定的元素x存在树中则返回true,否则返回false

相关知识
二叉搜索树的查找

给定一个关键字key,我们要判断key是否在指定的二叉搜索树T中,如果存在则返回true,不存在则返回false

这个查找过程从树根结点开始,并沿着这棵树中的一条简单路径向下进行,如下图所示。对于遇到的每个结点x,比较keyx。如果两个关键字相等,查找终止。如果key小于x,查找在x的左子树中继续,因为二叉搜索树的性质意味着key不可能被存储在x的右子树中。对称地,如果key大于x,查找在右子树中继续。

为了查找上图中的关键字为13的结点,从树根开始沿着15->6->7->13路径进行查找。

代码: 
package step3;

/**
 * Created by zengpeng on 2018/3/14.
 */
public class BSTree {
    private TreeNode root;//根结点

    public BSTree() {
        root = null;
    }

    /**
     * 向树root中插入a
     *
     * @param key    要插入的值
     */
    public void insert(int key) {
        TreeNode x = root;
        TreeNode p = null;//始终指向x的父结点
        while (x != null) {
            p = x;
            if (key < x.item) {
                x = x.leftChild;
            } else {
                x = x.rightChild;
            }
        }
        if (null == p) {//空树
            root = new TreeNode(key);
        } else if (key < p.item) {
            p.leftChild = new TreeNode(key);
        } else {
            p.rightChild = new TreeNode(key);
        }
    }

    /**
     * 判断树root中是否包含key,包含则返回true,不包含返回false
     *
     * @param key
     * @return
     */
    public boolean search(int key) {
        /********** Begin *********/
        TreeNode p=root;//用p结点储存根结点
        while(p!=null&&key!=p.item)//如果p不为空,并且要查找的值不为p结点的值
        {
        if(key<p.item)//如果要查找的值小于p结点的值
        {
        p=p.leftChild;//让p储存为p的左子树,因为小于p结点值的,结点都在左边
        }
        else//如果要查找的值大于p结点的值
        {
        p=p.rightChild;//让p储存为p的大子树,因为大于p结点值的,结点都在右边
        }
        }
        if(p==null)//如果p为空则表示没找到
        return false;//返回false
        else//找到了
        return true;//返回true
        /********** End *********/
    }


    /**
     * 在树root中删除结点key
     *
     * @param key
     * @return
     */
    public void delete(int key) {
        root = delete(root, key);
    }

    private TreeNode delete(TreeNode x, int key) {
        if (x == null) {
            return null;
        }

        if (key < x.item) {
            x.leftChild = delete(x.leftChild, key);
        } else if (key > x.item) {
            x.rightChild = delete(x.rightChild, key);
        } else {
            if (x.leftChild == null) return x.rightChild;
            if (x.rightChild == null) return x.leftChild;
            TreeNode t = x;
            x = min(t.rightChild);
            x.rightChild = deleteMin(t.rightChild);
            x.leftChild = t.leftChild;
        }

        return x;
    }

    /**
     * 删除树x中的最小结点
     * @param x
     * @return
     */
    private TreeNode deleteMin(TreeNode x) {
        if (x.leftChild == null) return x.rightChild;
        x.leftChild = deleteMin(x.leftChild);
        return x;
    }

    /**
     * 查找树x中的最小结点
     *
     * @param x
     * @return
     */
    private TreeNode min(TreeNode x) {
        TreeNode p = x;
        while (p.leftChild != null) {
            p = p.leftChild;
        }
        return p;
    }

    public void preOrder() {
        preOrder(root);
    }

    public void inOrder() {
        inOrder(root);
    }

    public void postOrder() {
        postOrder(root);
    }

    private void preOrder(TreeNode node) {
        if (node != null) {
            System.out.print(node.item + " ");
            preOrder(node.leftChild);
            preOrder(node.rightChild);
        }
    }

    private void inOrder(TreeNode node) {
        if (node != null) {
            inOrder(node.leftChild);
            System.out.print(node.item + " ");
            inOrder(node.rightChild);
        }
    }

    private void postOrder(TreeNode node) {
        if (node != null) {
            postOrder(node.leftChild);
            postOrder(node.rightChild);
            System.out.print(node.item + " ");
        }
    }


    public static class TreeNode {
        private TreeNode leftChild;
        private TreeNode rightChild;
        private int item;

        public TreeNode(int item) {
            this(null, null, item);
        }

        public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
            this.leftChild = leftChild;
            this.rightChild = rightChild;
            this.item = item;
        }
    }
}

以下是测试样例:

测试输入:1 5 4 3 2 6 预期输出:

  1. 删除前树中的结点:1 5 4 3 2 6
  2. 删除后树中的结点:5 4 3 2
  3. false true true true true false

注意:二叉搜索树中,如果存在左右子树,则左子树上的结点都小于根结点,右结点子树上的结点都大于根结点,严格要求左子树小于根结点,右子树大于根结点

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

星与星熙.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值