二叉排序树(Java版)

导读:本篇文章讲解 二叉排序树(Java版),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1、 二叉排序树的定义

二叉排序树,又称二叉搜索树是一颗空树,或者具有下列性质的二叉树:

  • 每个结点都有一个作为搜索依据的关键码(key),所有结点的关键码互不相同。
  • 左子树(如果存在)上所有结点的关键码都小于根节点的关键码。
  • 右子树(如果存在)上所有结点的关键码都大于根结点的关键码。
  • 左子树和右子树也是二叉排序树。
    在这里插入图片描述

2、非递归查询

在这里插入图片描述
二叉排序树如上图所示,每一个结点有四个域,数据域指向父节点的指针
指向左孩子的指针指向右孩子的指针

结点定义如下:
BstNode

public class BstNode {
    public int data;
    public BstNode parent;
    public BstNode leftChild;
    public BstNode rightChild;

    public BstNode(int date) {
        this.data = date;
    }
}

算法思想: 非递归遍历,首先需要一个指针ptr指向根结点,如果根结点的关键码等于要查询的val,则找到了,如果val大于根结点的关键码,则ptr指向其右孩子,如果val小于根结点的关键码,则ptr指向其左孩子,然后重复如上操作,依次循环比较,直到ptr为空或找到为止。

代码如下:

private BstNode findVal(BstNode ptr, int val) {
    while (ptr != null && ptr.data != val) {
        if (ptr.data > val) {
            ptr = ptr.rightChild;
        } else {
            ptr = ptr.leftChild;
        }
    }
    return ptr;
}

public BstNode findVal(int val) {
    return findVal(root,val);
}

3、递归查询

算法思想: 在二叉排序树上进行搜索,从根结点开始,沿着某一个分支逐层向下进行比较判等的过程。
假设想要在二叉排序树中搜索关键码为val的元素,搜索过程从根结点开始,如果根结点为空,则搜索不成功,直接返回;否则用给定值val与根结点的关键码进行比较:
如果给定值等于根结点的关键码,则搜索成功;
如果给定值小于根结点的关键码,则继续递归搜索根结点的左子树;
否则,递归搜索根结点的右子树。

代码如下:

private BstNode findValue(BstNode ptr, int val) {
    if (ptr == null || ptr.data == val) {
        return ptr;
    } else if (ptr.data > val) {
        return findValue(ptr.leftChild, val);
    } else {
        return findValue(ptr.rightChild, val);
    }
}

public BstNode findValue(int val) {
    return findValue(root,val);
}

4、找二叉排序树的第一个结点

找二叉排序树的第一个结点,就是相当于找这颗树的最小的结点,也就是最左侧的那个结点。
在这里插入图片描述
代码如下:

private BstNode first(BstNode ptr) {
    while (ptr != null && ptr.leftChild != null) {
        ptr = ptr.leftChild;
    }
    return ptr;
}

5、找二叉排序树的最后一个结点

找二叉排序树的最后一个结点,就是相当于找这颗排序二叉树的最大的一个结点,也就是最右侧的那个结点。

private BstNode last(BstNode ptr) {
    while (ptr != null && ptr.rightChild != null) {
        ptr = ptr.rightChild;
    }
    return ptr;
}

6、非递归的中序遍历(不用栈)

非递归的中序遍历,我们可以先找到他的第一个结点进行打印,然后寻找它的下一个结点进行打印,直到全部打印结束。

代码如下:

/**
* 非递归中序遍历
*/
public void niceInOrder() {
   for (BstNode p =  first(root); p != null; p = next(p)) {
       System.out.print(p.data + " ");
   }
}

那么现在要解决的就是如何写next()方法了,也就是如何去寻找结点的后继,下一个要打印的结点。
在这里插入图片描述
首先找到第一个结点09,打印完09后,判断该结点的右子树是否为null,为null,那么它的后继就是它的父节点,即打印17,打印完17以后,判断其右子树是否为null,不为null,以它的右孩子为根节点,找到该子树的第一个结点23,打印完23后判断其右子树是否为null,为null,打印他的父节点45,打印完45后,判断其右子树也为null,那么此时他的后继为谁呢?显然不是它的父节点,而是53这个结点。所以编码如下:

next函数:

/**
* 寻找下一个结点,它的后继
*/
private BstNode next(BstNode ptr) {
   if (ptr == null) {
       return null;
   }
   if (ptr.rightChild != null) {
       return first(ptr.rightChild);
   } else {
       BstNode pa = ptr.parent;
       while (pa != null && pa.rightChild == ptr) {
           ptr = pa;
           pa = ptr.parent;
       }
       return pa;
   }
}

7、构建排序二叉树

算法思想: 如果root为null,表明这是一颗空树,直接将结点插入,如果根结点不为null,那么我们需要把val值与根结点的data进行比较,如果大于就往右子树寻找位置,小于就往左子树寻找位置,但是最后我们要插入的p结点要指向它的父节点,所以我们需要用一个pa指针保存其父节点的位置。

代码如下:

/**
 * 插入结点
 */
public boolean insert(int val) {
    //如果根节点为null,直接插入
    if (root == null) {
        root = new BstNode(val);
        return true;
    }
    BstNode p = root;
    BstNode pa = null;
    while (p != null && p.data != val) {
        pa = p;
        p = val > p.data ? p.rightChild : p.leftChild;
    }
    //如果该树中有相同结点,插入失败
    if (p != null && p.data == val) {
        return false;
    }
    //找到位置
    p = new BstNode(val);
    p.parent = pa;
    if (pa.data > val) {
        pa.leftChild = p;
    }else {
        pa.rightChild = p;
    }
    return true;
}

测试类

public class TestBstTree {
    public static void main(String[] args) {
        BinarySortTree bst = new BinarySortTree();
        //构建二叉排序树
        int[] arr = {53, 17, 78, 9, 45, 65, 23, 81, 94, 88, 100, 87};
        for (int i = 0; i < arr.length; i++) {
            bst.insert(arr[i]);
        }
        //中序遍历
        bst.niceInOrder();
    }
}

运行结果:
在这里插入图片描述

8、逆向遍历二叉排序树

逆向遍历二叉排序树,与非递归中序遍历二叉排序树相反,我们需要先找到他的最后一个结点进行打印,然后寻找它的前驱结点进行打印,直到全部打印结束。

public void reNiceInOrder() {
    for (BstNode p = last(root); p != null; p = prev(p)) {
        System.out.print(p.data + " ");
    }
    System.out.println();
}

prev函数

private BstNode prev(BstNode ptr) {
    if (ptr == null) {
        return null;
    }
    if (ptr.leftChild != null) {
        return last(ptr.leftChild);
    } else {
        BstNode pa = ptr.parent;
        while (pa != null && pa.leftChild == ptr) {
            ptr = pa;
            pa = ptr.parent;
        }
        return pa;
    }
}

测试类

public class TestBstTree {
    public static void main(String[] args) {
        BinarySortTree bst = new BinarySortTree();
        //构建二叉排序树
        int[] arr = {53, 17, 78, 9, 45, 65, 23, 81, 94, 88, 100, 87};
        for (int i = 0; i < arr.length; i++) {
            bst.insert(arr[i]);
        }
        //中序遍历
//        bst.niceInOrder();
        //逆向遍历
        bst.reNiceInOrder();
    }
}

运行结果:
在这里插入图片描述

9、删除二叉排序树的结点

删除二叉排序树的一个结点,
如果该树是一颗空树,那么就不需要删除了,
如果树不为空,那么我们需要在该树中寻找该结点,
没有找到,不需要删除了
找到了又分为三种情况,
1、结点为叶子结点,
2、结点为单分支结点
3、结点为双分支结点

先考虑结点为叶子结点
在这里插入图片描述
结点为叶子结点的话,如果该结点是父节点的左孩子,那么将父节点的左指针置为空,反之,如果该结点是父节点的右孩子,那么将父节点的右指针置为空

BstNode p = findValue(root, val);
if (p == null) return false;//没有找到
//找到,且为叶子结点
BstNode pa = p.parent;
if (pa.leftChild == p) {
    pa.leftChild = null;
} else {
    pa.rightChild = child;
}

然后考虑结点为单分支结点
在这里插入图片描述

我们可以改进上述的删除叶子结点的代码,使代码既能删除叶子结点,又可以删除单分支节点

BstNode p = findValue(root, val);
if (p == null) return false;//没有找到

//找到了,且为叶子结点或单分支结点
BstNode pa = p.parent;
BstNode child = p.leftChild != null ? p.leftChild : p.rightChild;
if (pa.leftChild == p) {
    pa.leftChild = child;
} else {
    pa.rightChild = child;
}

接下来,如果该结点,既是单分支结点,又是根节点呢
在这里插入图片描述
如上图所示,如果删除的结点是53,53这个结点的父节点是个null,那么我们就只能把root直接指向其孩子了。再次改进代码

BstNode p = findValue(root, val);
if (p == null) return false;//没有找到

//为叶节点或单分支结点或单根分支结点
BstNode pa = p.parent;
BstNode child = p.leftChild != null ? p.leftChild : p.rightChild;
if (pa == null) {//判断是否是单根分支结点
    root = child;//是单分支结点直root直接指向其孩子结点
} else {
    if (pa.leftChild == p) {
        pa.leftChild = child;
    } else {
        pa.rightChild = child;
    }
}

最后我们要考虑删除双分支结点了
在这里插入图片描述
比如我们要删除的是78结点,删除了78结点后,我们需要维护这颗二叉排序树的,即在它的右孩子中找到最小的结点来接替他,换句话说就是找到它的后继来替换他的位置,所以我们可以用狸猫换太子来删除一个双分支结点,即把其后继赋值给他,然后删除后继就可以了,后继必然是一个叶子结点,那么就可以复用上面的代码啦!

public boolean remove(int val) {
    if (root == null) return false; //如果是空树,不删
    BstNode p = findValue(root, val); 
    if (p == null) return false;//没有找到,不删

    //删除的结点是双分支结点
    if (p.leftChild != null && p.rightChild != null) {
        BstNode next = next(p);//找到其后继
        p.data = next.data;//后继的值赋给他
        p = next;//后继赋值给他,即后面把后继删除即可,此时后继必定是一个叶子结点
    }

    //结点为叶子结点或单分支结点或单根分支结点
    //pa其父节点
    BstNode pa = p.parent;
    BstNode child = p.leftChild != null ? p.leftChild : p.rightChild;
    if (pa == null) {
        root = child;
    } else {
        if (pa.leftChild == p) {
            pa.leftChild = child;
        } else {
            pa.rightChild = child;
        }
    }
    return true;
}

完整代码

BstNode

public class BstNode {
    public int data;

    public BstNode parent;
    public BstNode leftChild;
    public BstNode rightChild;

    public BstNode(int date) {
        this.data = date;
    }
}

BinarySortTree

public class BinarySortTree {
    private BstNode root;

    /**
     * 非递归查询
     */
    private BstNode findVal(BstNode ptr, int val) {
        while (ptr != null && ptr.data != val) {
            if (ptr.data > val) {
                ptr = ptr.rightChild;
            } else {
                ptr = ptr.leftChild;
            }
        }
        return ptr;
    }

    public BstNode findVal(int val) {
        return findVal(root, val);
    }

    /**
     * 二叉排序树的递归查询
     */
    private BstNode findValue(BstNode ptr, int val) {
        if (ptr == null || ptr.data == val) {
            return ptr;
        } else if (ptr.data > val) {
            return findValue(ptr.leftChild, val);
        } else {
            return findValue(ptr.rightChild, val);
        }
    }

    public BstNode findValue(int val) {
        return findValue(root, val);
    }

    /**
     * 寻找第一个结点
     */
    private BstNode first(BstNode ptr) {
        while (ptr != null && ptr.leftChild != null) {
            ptr = ptr.leftChild;
        }
        return ptr;
    }

    /**
     * 寻找最后一个结点
     */
    private BstNode last(BstNode ptr) {
        while (ptr != null && ptr.rightChild != null) {
            ptr = ptr.rightChild;
        }
        return ptr;
    }

    /**
     * 寻找下一个结点,它的后继
     */
    private BstNode next(BstNode ptr) {
        if (ptr == null) {
            return null;
        }
        if (ptr.rightChild != null) {
            return first(ptr.rightChild);
        } else {
            BstNode pa = ptr.parent;
            while (pa != null && pa.rightChild == ptr) {
                ptr = pa;
                pa = ptr.parent;
            }
            return pa;
        }
    }

    /**
     * 非递归中序遍历
     */
    public void niceInOrder() {
        for (BstNode p = first(root); p != null; p = next(p)) {
            System.out.print(p.data + " ");
        }
    }


    /**
     * 插入结点
     */
    public boolean insert(int val) {
        //如果根节点为null,直接插入
        if (root == null) {
            root = new BstNode(val);
            return true;
        }
        BstNode p = root;
        BstNode pa = null;
        while (p != null && p.data != val) {
            pa = p;
            p = val > p.data ? p.rightChild : p.leftChild;
        }
        //如果该树中有相同结点,插入失败
        if (p != null && p.data == val) {
            return false;
        }
        //找到位置
        p = new BstNode(val);
        p.parent = pa;
        if (pa.data > val) {
            pa.leftChild = p;
        } else {
            pa.rightChild = p;
        }
        return true;
    }

    private BstNode prev(BstNode ptr) {
        if (ptr == null) {
            return null;
        }
        if (ptr.leftChild != null) {
            return last(ptr.leftChild);
        } else {
            BstNode pa = ptr.parent;
            while (pa != null && pa.leftChild == ptr) {
                ptr = pa;
                pa = ptr.parent;
            }
            return pa;
        }
    }

    public void reNiceInOrder() {
        for (BstNode p = last(root); p != null; p = prev(p)) {
            System.out.print(p.data + " ");
        }
        System.out.println();
    }

    //删除结点
    public boolean remove(int val) {
        if (root == null) return false; //如果是空树,不删
        BstNode p = findValue(root, val);
        if (p == null) return false;//没有找到,不删

        //删除的结点是双分支结点
        if (p.leftChild != null && p.rightChild != null) {
            BstNode next = next(p);//找到其后继
            p.data = next.data;//后继的值赋给他
            p = next;//后继赋值给他,即后面把后继删除即可,此时后继必定是一个叶子结点
        }

        //结点为叶子结点或单分支结点或单根分支结点
        //pa其父节点
        BstNode pa = p.parent;
        BstNode child = p.leftChild != null ? p.leftChild : p.rightChild;
        if (pa == null) {
            root = child;
        } else {
            if (pa.leftChild == p) {
                pa.leftChild = child;
            } else {
                pa.rightChild = child;
            }
        }
        return true;
    }

}

TestBstTree

public class TestBstTree {
    public static void main(String[] args) {
        BinarySortTree bst = new BinarySortTree();
        //构建二叉排序树
        int[] arr = {53, 17, 78, 9, 45, 65, 23, 81, 94, 88, 100, 87};
        for (int i = 0; i < arr.length; i++) {
            bst.insert(arr[i]);
        }
        bst.niceInOrder();
        //测试删除结点
        int val;
        Scanner scanner = new Scanner(System.in);
        val = scanner.nextInt();
        while (val != -1) {
            System.out.println(bst.remove(val));
            bst.niceInOrder();
            val = scanner.nextInt();
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/95477.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!