数据结构首页投稿(暂停使用,暂停投稿)Android知识

二叉搜索树

2017-06-22  本文已影响90人  某昆

1、前言

二叉树是非常重要的一种数据结构,二叉搜索树是其中的一种类型,其任意节点x,左子树中的关键字最大不超过x.key,右子树的关键字最小不低于x.key


Paste_Image.png

一棵随机构造的二叉搜索树的期望高度为lgN,而二叉搜索树上基本操作所花费的时间均与树的高度成正比。

2、构建二叉搜索树

根据二叉搜索树的特性,对于任意节点x,均有

x.left.key <= x.key <= x.right.key

向二叉搜索树插入新节点时,分成两种情况

3、查找二叉搜索树

二叉搜索树查找特定值非常简单,与插入类似,根据关键字值大小比较可得出

public SearchNode search2(SearchNode node, int v){
    SearchNode temp = node;
    while (temp != null && temp.value != v) {
        if (v < temp.value) {
            temp = temp.left;
        }else {
            temp = temp.right;
        }
    }
    return temp;
}

那寻找以节点x为根结点的树的最小值或最大值呢?根据二叉搜索树的特性,最小值肯定是x的左子树的左叶子节点,最大值是x的右子树的右叶子节点

public SearchNode treeMin(SearchNode node){
    SearchNode x = node;
    while (x != null && x.left != null) {
        x = x.left;
    }
    return x;
}

public SearchNode treeMax(SearchNode node){
    SearchNode x = node;
    while (x != null && x.right != null) {
        x = x.right;
    }
    return x;
}

如何寻找节点x的后继或前驱呢?

后继,遍历树时,位于节点x后的节点即为x的后继,反之则为前驱。根据不同的遍历方法,前序中序等等,会不同的后继,本文中仅讨论中序后继或中序前驱

如果使用中序遍历二叉搜索树,会发现一个奇特的现象,遍历得到的值一定是从小到大排列的,那么后继值,则刚好是大于x的最小节点。如此可分为两种情况

前驱类似,不再详述

4、删除节点

删除节点是二叉搜索树中最复杂的。命名删除的节点为z,它需要分成三种情况讨论

二叉搜索树删除节点,左右节点存在的情况下,其通用作法就是使用后继替代它

public void delete(SearchNode z){
    SearchNode y = null;
    if (z == null) {
        return;
    }
    if (z.left == null) {
        swap(z, z.right);
    }else if (z.right == null) {
        swap(z, z.left);
    }else {
        //寻找Z的后继节点,此处改为  treeMin(z.right)
        //为啥寻找z的后续节点就可以替换z的位置,二叉搜索树如果是中序遍历,那么一定是按从小到大排列的,
        //如果删除某个数之后,这个特性段然存在,从这个角度来看,就得找它的后续节点来替代z
        y = followUp(z);
        if (y.parent != z) {
            //本例中,将y的父节点和y的关系切断
            swap(y, y.right);
            //设置y的右节点为z的右节点
            y.right = z.right;
            //设置y右节点的父节点
            y.right.parent = y;
        }
        swap(z, y);
        //设置y左节点及其父节点
        y.left = z.left;
        y.left.parent = y;
    }
}

5、线索化二叉树

前文提到前驱与后继,其实二叉树作为非线性结构,原来是不存在所谓前驱与后继概念的,但二叉树遍历后,彼此结点确实存在前后关系,加上二叉树是可以使用数组保存,所以才有前驱与后继一说。

二叉树的节点一般会有值、左子节点、右子节点等指针域。n个结点有2n个指针,其中非空指针为n-1个,空白指针有n+1个。

为了不浪费指针空间,将二叉树空白指针中存在着节点的前驱与后继信息,如果无左节点则左节点指针域存放前驱。无右节点,则右节点指针域存放后继,为了标志存放的是子节点还是前驱后继,还会添加两个额外标志位。

private TAG mLeftTag = TAG.LINK;

private TAG mRightTag = TAG.LINK;

线索化二叉树,在遍历二叉树的时候判断二叉树是否存在左节点,如果不存在,则将pre设置为它的前驱,然后再查看pre是否有右节点,如果没有,则将当前节点设置为pre的后继。

算法的精髓就在于设置一个全局变量pre。

ClueNode pre;
private void makeClueTree(ClueNode node){
    if (node == null) {
        return;
    }
    makeClueTree((ClueNode)node.getLeftChild());
    
    if (!node.hasLeftChild()) {
        node.setLeftTag(TAG.THREAD);
        node.setLeftChild(pre);
    }
    if (pre != null && !pre.hasRightChild()) {
        pre.setRightTag(TAG.THREAD);
        pre.setRightChild(node);
    }
    pre = node;
    makeClueTree((ClueNode)node.getRightChild());
}

已知一个线索化二叉树,那么在遍历此二叉树时,不再需要递归,也不需要借助栈,就能直接遍历了。

private void printClueTree(ClueNode node){
    ClueNode p = node;
    while (p != null) {
        while(p.getLeftTag() == TAG.LINK && p.hasLeftChild()) {
            p = (ClueNode) p.getLeftChild();
        }
        printNode(p);
        while(p.getRightTag() == TAG.THREAD && p.hasRightChild()) {
            p = (ClueNode) p.getRightChild();
            printNode(p);
        }
        p = (ClueNode) p.getRightChild();
    }
}

ps:所有代码均已上传至本人github

上一篇下一篇

猜你喜欢

热点阅读