二叉搜索树Java实现(增删改查遍历等操作)

是一种特殊结构的二叉树

二叉排序树(BinarySortTree),又称二叉查找树、二叉搜索树。

二叉搜索树需满足以下四个条件:

  • 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 任意节点的左、右子树也分别为二叉查找树;
  • 没有键值相等的节点。

二叉排序树性质

按中序遍历二叉排序树,所得到的中序遍历序列是一个递增有序序列。

二叉搜索树就是具备上述四种性质的二叉树。

下面进行的二叉排序树的增删改查操作都是基于节点Node中key的大小构建二叉排序树

二叉排序树的插入

在二叉排序树中插入新结点,要保证插入后的二叉树仍符合二叉排序树的定义。   
插入过程:
- 若二叉排序树为空,则待插入结点*S作为根结点插入到空树中;
- 当非空时,将待插结点关键字S->key和树根关键字t->key进行比较,若s->key = t->key,则无须插入,若s->key< t->key,则插入到根的左子树中,若s->key> t->key,则插入到根的右子树中。而子树中的插入过程和在树中的插入过程相同,如此进行下去,直到把结点*s作为一个新的树叶插入到二叉排序树中,或者直到发现树已有相同关键字的结点为止。
- 插入完成

二叉排序树的查找

假定二叉排序树的根结点指针为 root ,给定的关键字值为 K ,则查找算法可描述为:

  • ① 置初值: q = root ;
  • ② 如果 K = q -> key ,则查找成功,算法结束;
  • ③ 否则,如果 K < q -> key ,而且 q 的左子树非空,则将 q 的左子树根送 q ,转步骤②;否则,查找失败,结束算法;
  • ④ 否则,如果 K > q -> key ,而且 q 的右子树非空,则将 q 的右子树根送 q ,转步骤②;否则,查找失败,算法结束。

二叉排序树的删除

假设被删结点是*p,其双亲是*f,不失一般性,设*p*f的左孩子,下面分三种情况讨论:   
- ⑴ 若结点*p是叶子结点,则只需修改其双亲结点*f的指针即可。  
- ⑵ 若结点*p只有左子树PL或者只有右子树PR,则只要使PL或PR 成为其双亲结点的左子树即可。   
- ⑶ 若结点*p的左、右子树均非空,先找到*p的中序后继(或前驱)节点*s(注意*s*p的右子树中的key最小的结点,它的左子树为空),然后步骤如下:
- 找到 *p 的节点的直接中序后继节点(即其右子树中key值最小的节点 ),并删除此直接中序后继节点。
- 将此后继节点的 key、value 值赋给待删除节点的 key,value值。

例如删除上图一中 key 值为 10 的节点,这时就需要用 key 值为 10 的节点的中序后继节点(节点 11)来代替 key 值为 10 的节点,并删除 key 值为 10 的节点的中序后继节点,由中序遍历相关规则可知, key 值为 10 的节点的直接中序后继节点一定是其右子树中 key 值最小的节点,所以此中序后继节点一定不含子节点或者只含有一个右孩子,删除此中序后继节点就属于上述 1,2 所述情况。图一中 key 值为 10 的节点的直接中序后继节点 为 11,节点 11 含有一个右孩子 12。

步骤:

a、找到 key 值为 10 的节点的直接中序后继节点(即其右子树中值最小的节点 11),并删除此直接中序后继节点。

b、将此后继节点的 key、value 值赋给待删除节点的 key,value值。

实例demo

节点类


package org.vincent.strategy.binarysearch;

class Node {
    int key;
    int value;
    Node leftChild;
    Node rightChild;

    public Node(int key, int value) {
        this.key = key;
        this.value = value;
        this.leftChild = null;
        this.rightChild = null;
    }

    public Node(int key, int value, Node leftChild, Node rightChild) {
        super();
        this.key = key;
        this.value = value;
        this.leftChild = leftChild;
        this.rightChild = rightChild;
    }

    public Node() {

    }

    @Override
    public String toString() {
        return "Node [key=" + this.key + ", value=" + this.value + ", leftChild=" + this.leftChild + ", rightChild=" + this.rightChild + "]";
    }

    public int getKey() {
        return this.key;
    }

    public void setKey(int key) {
        this.key = key;
    }

    public int getValue() {
        return this.value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node getLeftChild() {
        return this.leftChild;
    }

    public void setLeftChild(Node leftChild) {
        this.leftChild = leftChild;
    }

    public Node getRightChild() {
        return this.rightChild;
    }

    public void setRightChild(Node rightChild) {
        this.rightChild = rightChild;
    }

}

二叉排序树接口方法


package org.vincent.strategy.binarysearch;

public abstract class AbsBinaryTree {
    public abstract Node find(int key);// 查找指定节点

    public abstract boolean update(int key, int value);

    public abstract void insert(int key, int value); // 插入节点

    public abstract boolean delete(int key); // 删除指定节点

    public abstract Node getDirectPostNode(Node delNode); // 得到待删除节点的直接后继节点

    public abstract void preOrder(Node rootNode); // 先序遍历树

    public abstract void inOrder(Node rootNode); // 中序遍历树

    public abstract void postOrder(Node rootNode); // 后序遍历树
}

二叉排序树具体实现


package org.vincent.strategy.binarysearch;

public class BinaryTree extends AbsBinaryTree {

    private Node root;

    public Node getRoot() {
        return this.root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    // 二叉排序树查找节点
    // 找到和key相等则返回相应节点,否则返回 null。
    @Override
    public Node find(int key) {
        // TODO Auto-generated method stub
        Node currentNode = this.root;
        // currentNode.key和 key不等才需要循环
        while ((currentNode != null) && (currentNode.key != key)) {
            if (key < currentNode.key) {
                currentNode = currentNode.leftChild;
            } else if (key > currentNode.key) {
                currentNode = currentNode.rightChild;
            }
        }
        return currentNode;

    }

    @Override
    public void insert(int key, int value) {
        // TODO Auto-generated method stub
        if (this.root == null) {
            this.root = new Node(key, value);
            return;
        }
        Node currentNode = this.root;
        Node parentNode = this.root;// 指向currentNode节点的父节点
        boolean isLeftChild = true;
        // 寻找插入位置
        while (currentNode != null) {
            parentNode = currentNode;
            if (key < currentNode.key) {
                currentNode = currentNode.leftChild;
                isLeftChild = true;
            } else if (key > currentNode.key) {
                currentNode = currentNode.rightChild;
                isLeftChild = false;
            } else {
                // 插入的节点key和二叉树中节点key相等无需插入
                // parentNode 和 currentNode两个引用指向相同Node对象,引用变量相等,只需要更改value
                break;
            }
        }
        // 插入节点
        if (parentNode != currentNode) {
            Node newNode = new Node(key, value);
            if (isLeftChild) {
                parentNode.leftChild = newNode;
            } else {
                parentNode.rightChild = newNode;
            }

        } else {
            // 如果待插入节点和二叉树中节点一样;则只要更改值
            currentNode.setValue(value);
        }
    }

    @Override
    public boolean delete(int key) {
        // TODO Auto-generated method stub
        Node currentNode = this.root;// 用来保存待删除节点
        Node parentNode = this.root;// 用来保存待删除节点的父亲节点
        boolean isLeftChild = true;// 用来保存待删除节点是父亲节点的左孩子还是右孩子
        // 寻找删除节点并记录删除节点的父节点以及他是父节点的左孩子还是右孩子
        while ((currentNode != null) && (currentNode.key != key)) {
            parentNode = currentNode;
            if (key < currentNode.key) {
                currentNode = currentNode.leftChild;
                isLeftChild = true;
            } else {
                currentNode = currentNode.rightChild;
                isLeftChild = false;
            }
        }
        if (currentNode == null) return false;// 空树
        // 要删除的节点为叶子节点,删除的第一种情况
        if ((currentNode.leftChild == null) && (currentNode.rightChild == null)) {
            if (currentNode == this.root) {
                this.root = null;
            } else if (isLeftChild) {
                parentNode.leftChild = null;
            } else {
                parentNode.rightChild = null;
            }
            // 要删除的节点只有左孩子 第二种情况
        } else if ((currentNode.rightChild == null) && (currentNode.leftChild != null)) {
            if (currentNode == this.root) {
                this.root = currentNode.leftChild;
            } else if (isLeftChild) {
                parentNode.leftChild = currentNode.leftChild;
            } else {
                parentNode.rightChild = currentNode.leftChild;
            }
            // 要删除的节点只有右孩子 第三种情况
        } else if ((currentNode.leftChild == null) && (currentNode.rightChild != null)) {
            if (currentNode == this.root) {
                this.root = currentNode.rightChild;
            } else if (isLeftChild) {
                parentNode.leftChild = currentNode.rightChild;
            } else {
                parentNode.rightChild = currentNode.rightChild;
            }
        } // 最后一种情况,待删除节点既有左子树又有右子树
        else {
            // 将待删除节点的右子树最小节点赋值给删除节点的key,value,那么删除后新的二叉树也是二叉排序树
            // 思路:删除右子树中key值最小的节点,并返回,然后用这个节点的值赋值删除节点的key和value
            // 右子树中key最小的节点一定不含左子树,所以删除这个key最小的节点一定是属于叶子节点或者只有右子树的节点
            Node directPostNode = this.getDirectPostNode(currentNode);
            currentNode.key = directPostNode.key;
            currentNode.value = directPostNode.value;
        }

        return true;
    }

    // 获取到待删除节点的中序直接后继节点。将该后继节点从二叉树中删除并返回
    @Override
    public Node getDirectPostNode(Node delNode) {
        // TODO Auto-generated method stub
        // 方法作用为得到待删除节点的直接后继节点

        Node parentNode = delNode;// 用来保存待删除节点的直接后继节点的父亲节点
        Node direcrPostNode = delNode;// 用来保存待删除节点的直接后继节点
        Node currentNode = delNode.rightChild;// 待删除节点右子树
        while (currentNode != null) {
            parentNode = direcrPostNode;
            direcrPostNode = currentNode;
            currentNode = currentNode.leftChild;
        }
        if (direcrPostNode != delNode.rightChild) {// 从树中删除此直接后继节点
            parentNode.leftChild = direcrPostNode.rightChild;// 后继节点的父节点指向后继节点的右孩子
            direcrPostNode.rightChild = null;// 直接后继节点右孩子为空
        }
        return direcrPostNode;// 返回此直接后继节点
    }

    @Override
    public void preOrder(Node rootNode) {
        // TODO Auto-generated method stub
        if (rootNode != null) {
            System.out.println(rootNode.key + " " + rootNode.value);
            this.preOrder(rootNode.leftChild);
            this.preOrder(rootNode.rightChild);
        }
    }

    @Override
    public void inOrder(Node rootNode) {
        // TODO Auto-generated method stub
        if (rootNode != null) {
            this.inOrder(rootNode.leftChild);
            System.out.println(rootNode.key + " " + rootNode.value);
            this.inOrder(rootNode.rightChild);
        }
    }

    @Override
    public void postOrder(Node rootNode) {
        // TODO Auto-generated method stub
        if (rootNode != null) {
            this.postOrder(rootNode.leftChild);
            this.postOrder(rootNode.rightChild);
            System.out.println(rootNode.key + " " + rootNode.value);
        }
    }

    /**
     * // 基于二叉排序树查找find查找节点,然后通过Node的setValue将新值赋值过去。
     */
    @Override
    public boolean update(int key, int value) {
        // TODO Auto-generated method stub
        Node node = this.find(key);
        node.setValue(value);
        return true;
    }

}

测试用例

package org.vincent.strategy.binarysearch;

public class BinaryTreeTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        BinaryTree tree = new BinaryTree();
        tree.insert(6, 6);// 插入操作,构造图一所示的二叉树
        tree.insert(3, 3);
        tree.insert(14, 14);
        tree.insert(16, 16);
        tree.insert(10, 10);
        tree.insert(9, 9);
        tree.insert(13, 13);
        tree.insert(11, 11);
        tree.insert(12, 12);
        System.out.println("删除前中序遍历结果,结果是一个递增有序序列");
        tree.inOrder(tree.getRoot());// 中序遍历操作
        tree.update(12, 200);
        System.out.println("更新节点值中序遍历结果  key=12的值");
        tree.inOrder(tree.getRoot());
        System.out.println("删除节点10之后遍历结果");

        tree.delete(10);// 删除操作
        tree.inOrder(tree.getRoot());
    }

}

参考文献:

参考博文

Java二叉排序树

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章