Fork me on GitHub

数据结构之二叉树

为什么要用树?

  在有序数组中用二分查找法查找数据项很快,查找数据的时间为O(logN),但是插入数据项很慢,平均来讲要移动数组中一半的数据项。而在链表中插入和删除操作很快,但是查找必须从头开始,平均需要访问N/2个数据项。就这样,树产生了,既能像链表那样快速的插入和删除,又能像有序数组那样快速查找。

介绍树的一些术语:

路径:顺着连接节点的边从一个节点到另一个节点,所经过的节点的顺序排列成为路径。

:顶端的节点。一棵树只有一个根。从根到其它任何一个节点都必须有一条(而且只有一条)路径。

父节点:每个节点(除了根)都恰好有一条边向上连接到另一个节点,上面的节点就称为下面节点的父节点。除了根节点,树的任何节点都有且只有一个父节点。

子节点:每个节点都可能有一条或多条边想下连接其他节点,下面的这些节点就称为它的“子节点”。

叶子结点:没有子节点的节点称为“叶子结点”或简称“叶节点”。

访问:当程序控制流程到达某个节点时,就称为“访问”这个节点。

遍历:遍历意味着要遵循某种特定的顺序访问书中所有节点。

:一个节点的层数是指从根开始到这个节点有多少“代”。假设根是第0层,它的子节点就是第一层。

二叉树:如果树中每个节点最多只有两个子节点,这样的树就称为“二叉树”。二叉树每个节点的两个子节点称为“左子节点”和“右子节点”。

下面重点介绍二叉搜索树。

二叉搜索树是一个节点的左子节点的关键字小于这个节点,右子节点的关键字大于这个节点。

节点类:

public class TreeNode {
    //节点值
    int value;
    //左子节点
    TreeNode left;
    //右子节点
    TreeNode right;
    public TreeNode(int value){
        this.value = value;
        this.left = null;
        this.right = null;
    }
    public TreeNode(){
        this.value = 0;
        this.left = null;
        this.right = null;
    }

    public int getValue() {
        return value;
    }

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

    public TreeNode getLeft() {
        return left;
    }

    public void setLeft(TreeNode left) {
        this.left = left;
    }

    public TreeNode getRight() {
        return right;
    }

    public void setRight(TreeNode right) {
        this.right = right;
    }
}

二叉搜索树类:(插入相同数据无效)

public class BinarySearchTree {
    
    //根节点
    private TreeNode root;
    
    /**
     *无参构造函数
     */
    public BinarySearchTree() {
        this.root = null;
    }
    
    /**
     * 构造函数
     * @param value
     */
    public BinarySearchTree(int value) {
        root = new TreeNode(value);
    }
    
    /**
     * 插入一条数据
     * @param value
     */
    public void insertNode(int value) {
        //新增节点
        TreeNode newNode = new TreeNode(value);
        //判断二叉树是否为空
        if (this.root == null) {
            root = newNode;
            return;
        }
        //父节点
        TreeNode parent = null;
        //当前节点从root开始比较
        TreeNode current = root;
        while (true) {
            //如果节点中已经有此元素,返回
            if (value == current.value)
                return;
            //插入的元素比根节点元素小,向左子节点走
            else if (value < current.value) {
                parent = current;
                current = current.left;
                //左子节点为空就插入
                if (current == null ) {
                    parent.left = newNode;
                    return;
                }
            } else {//插入的元素比根节点元素大,向右子节点走
                parent = current;
                current = current.right;
                //右子节点为空就插入
                if (current == null ) {
                    parent.right = newNode;
                    return ;
                }
            }
        }    
    }
    //分析二叉搜索树,删除某一节点后,补充的节点为其右子节点序列中最小的(即右子孙节点中最小的)。
    public void deleteNode(int value) {
        if(this.root == null){
            return;
        }
        if(search(value)){
            
            TreeNode deleteNode = this.root;
            TreeNode parentNode = null;
            while(true){//得到要删除节点和此节点的父节点
                if(value == deleteNode.getValue()){
                    break;  
                } else if(value < deleteNode.getValue()){
                    parentNode = deleteNode;
                    deleteNode = deleteNode.getLeft();
                } else{
                    parentNode = deleteNode;
                    deleteNode = deleteNode.getRight();
                }
            }
            
            if(deleteNode.left == null && deleteNode.right == null){//删除节点无子节点
                //若删除的节点是根节点
                if(deleteNode == this.root){
                    this.root = null;
                }else{
                    if(parentNode.getValue()>deleteNode.getValue()){
                        parentNode.left = null;
                    } else{
                        parentNode.right = null;
                    }
                }
            } else if(deleteNode.right == null && deleteNode.left != null){//删除节点只有左子节点
                //若删除的节点是根节点
                if(deleteNode == this.root){
                    this.root = deleteNode.left;
                }else{
                    if(parentNode.getValue()>deleteNode.getValue()){
                        parentNode.left = deleteNode.left;
                    } else{
                        parentNode.right = deleteNode.left;
                    }
                }
            } else if(deleteNode.left == null && deleteNode.right != null){//删除节点只有右子节点
                //若删除的节点是根节点
                if(deleteNode == this.root){
                    this.root = deleteNode.right;
                }else{
                    if(parentNode.getValue()>deleteNode.getValue()){
                        parentNode.left = deleteNode.right;
                    } else{
                        parentNode.right = deleteNode.right;
                    }
                }
            } else{//删除节点的左右子节点均存在
                TreeNode rightMinNode = getRightMinTreeNode(deleteNode);//得到右子节点里面最小的节点(后继节点)
                rightMinNode.left = deleteNode.left;
                rightMinNode.right = deleteNode.right;//将后继节点替换成要删除的节点(另一种方法是直接将里面的int数据替换,这样就不需要节点替换)
                //若删除的节点是根节点
                if(deleteNode == this.root){
                    this.root = rightMinNode;
                }else{
                    if(parentNode.getValue()>deleteNode.getValue()){
                        parentNode.left = rightMinNode;
                    } else{
                        parentNode.right = rightMinNode;
                    }
                }
            }
            deleteNode.left = null;
            deleteNode.right = null;
            deleteNode = null;//这个删除节点无用了
        }
    }
    
    /**
     *返回右子节点里面最小的节点(即后继节点,并且将此节点剥离出来)
     * 这里不进行非空判断了,假设查找的节点及其右子节点均存在
     */
    public TreeNode getRightMinTreeNode(TreeNode rootNode){
        TreeNode parentNode = rootNode;
        TreeNode currentNode = rootNode.right;
        if(currentNode.left == null){
            parentNode.right = currentNode.right;
            currentNode.right = null;
        }else{
            while(currentNode.left != null){
                parentNode = currentNode;
                currentNode = currentNode.left;
            }
            parentNode.left = currentNode.right;
            currentNode.right = null;
        }
        return currentNode;
    }
    
    /**
     * 查找节点
     * @param value
     * @return
     */
    public boolean search(int value) {
        if (this.root == null ) {
            return false;
        }
        TreeNode current =this.root;
        boolean tag = false;
        while (true) {
            if (value == current.value) {
                tag=true;
                break;
            } else if (value < current.value) {
                current = current.left;
                if (current == null ) {
                    tag = false; 
                    break;
                }
            } else {
                current = current.right;
                if (current == null ) {
                    tag = false; 
                    break;
                }
            }
        }
        return tag;
    }
    
    /**
     * 查找节点
     * @param value
     * @return
     */
    public TreeNode searchNode(TreeNode rootNode,int value){
        if (rootNode == null ) {
            return null;
        }
        if (value == rootNode.value) {
            return rootNode;
        } else if (value < rootNode.value) {
            return searchNode(rootNode.left,value);
        } else {
            return searchNode(rootNode.right,value);
        }
    }
    
    /**
     * 遍历二叉树打印(左根右)(中序遍历)
     */
    public void Traversal() {
        Traversal(this.root);
    }
    
    private void Traversal(TreeNode treeNode) {
        //若为空,返回
        if ( treeNode == null ) {
            return;
        }
        //递归打印左节点
        Traversal(treeNode.left);
        //打印此节点
        System.out.print(treeNode.value+"  ");
        //打印右节点
        Traversal(treeNode.right);
    }
}

测试类:

public class Test {

    public static void main(String[] args) {
        BinarySearchTree bs = new BinarySearchTree();
        bs.insertNode(11);
        bs.insertNode(9);
        bs.insertNode(8);
        bs.insertNode(6);
        bs.insertNode(10);
        bs.insertNode(15);
        bs.insertNode(13);
        bs.insertNode(12);
        bs.insertNode(17);
        bs.insertNode(16);
        bs.insertNode(19);
        System.out.print("删除前:");
        bs.Traversal();
        bs.deleteNode(9);
        bs.deleteNode(15);
        bs.deleteNode(11);
        System.out.print("\n删除后:");
        bs.Traversal();
    }
}

结果:

节点的查找与插入都比较简单,删除节点有点复杂。本程序是根据三种情况处理的:

1,删除节点无子节点;

2,删除节点只有一个子节点(分左右的情况);

3,删除节点的左右子节点均存在。

这三种情况都需要考虑到删除节点是否根节点,前两种很好理解也很好处理,对于第三种,将待删除的节点删除后,取而代之的是后继节点(即右子节点中值最小的)。

 

posted @ 2018-02-09 14:20  爱跑步的星仔  阅读(182)  评论(0编辑  收藏  举报