数据结构之--二叉树(Java代码)

来源:互联网 发布:医院排队叫号系统源码 编辑:程序博客网 时间:2024/05/28 03:04

为什么要使用树呢?

在有序数组中,查找十分的快速,但是在插入数据的时候要移动数据,所以效率低下。

在链表中,插入和删除数据十分的快速,但是查找的时候却只能从头开始,依次的访问链表中的每一个数据,知道找到该数据为止,所以效率也低下。


总的来说:

1.在有序数组插入数据慢

2.在链表中查询慢


而树这种数据结构,既能够像链表那样快速插入和删除,又能像有序数组那样快速查找。

而树也有很多种,在本篇中,只涉及到--“二叉树”。


那什么是二叉树呢?

树的每一个结点最多只能有两个子节点,这样的树就称为“二叉树”。如图所示:

     



二叉树的每一个节点的两个子节点称为“左子节点” 和 “右子节点”。

二叉树中的节点不是必须有两个子节点,它可以只有一个左子节点 或 一个 右子节点 , 或者干脆没有子节点(这种情况也叫叶节点,像现实叶子一样,不能再产生分支了。)


下面要讲的二叉树在学术上称为:二叉搜索树(又称:二叉排序树,二叉查找树)

特点:

树的每一个节点的左子节点的关键字小于这个节点,它的右子节点的关键字大于或等于这个父节点。


1.跟链表一样,需要有一个Node类来表示节点。

package BinaryTree;/** * Created by Hubbert on 2017/11/19. */public class Node {    public int data;    public Node leftChild;    public Node rightChild;    public void displayNode(){        System.out.print("{"+ data + "}");    }}


2.还需要有一个表示树本身的类,由于这个类实例化的对象含有所有的节点,这个类是Tree类。

它只有一个数据字段:一个表示根的Node变量。 它不需要包含其他节点的数据字段,因为其他节点都可以从根开始访问到。

这个是Tree类的一个“骨架”

public class Tree {    public Node root; // 唯一的一个数据字段,根节点。    //查找    public void find( int key ){    }    //插入    public void insert( int key ){    }    //删除    public void delete ( int key ) {    }        //....等其他方法}


下面分别介绍方法

1.查找--find()方法:

//查找    public Node find( int key ){        Node current = root;        while( current.data != key){            if( key < current.data ){ //如果要查找的key值 小于 current的值                current = current.leftChild;    // go left            } else {                current = current.rightChild;   // 大于 -> go right            }            if( current == null ){  //如果没有孩子了,则返回null                return null;            }        }        return current;    }
这个过程用current来保存正在查看的节点。参数key是要查找的值。

查找的话只能从root根节点开始查找,因为只有根节点可以访问,因此,一开始把current设置为root。



2.插入-insert()方法:

要插入节点,必须找到插入的地方。这很像要找一个不存在的节点的过程,如前面说的找不到节点的那种情况。从根节点开始查找一个相应的节点,它将是新节点的父节点。

当父节点找到了,新的节点就可以连接到它的左子节点或右子节点了。

//插入    public void insert(int key){        //1.先创建要插入的节点        Node newNode = new Node(key);        //2.先判断根节点是否为空,若为空直接将newNode复制给root节点        if( root == null ){            root = newNode;        } else {            Node current = root;            Node parent;            while(true){                parent = current; // 保留当前的节点                if( key < current.data ){ //如果要插入的值iData小于 当前节点的值                    current = current.leftChild; //则继续查找下一个current的左子节点                    if(current == null){ //如果当前节点的左子节点为空                        parent.leftChild = newNode; //则将new的节点赋给parent.left                        return ;                    }                } else {                    current = current.rightChild;                    if(current == null){                        parent.rightChild = newNode;                        return;                    }                }            }        }    }



3.遍历树

遍历树的意思是根据一种特定顺序访问树的每一个节点。

有三种简单的方法遍历树:前序(preOrder),中序(inOrder),后序(postOrder)。

其中最常用的中序遍历(inOrder)

3.1中序遍历

中序遍历二叉搜索树会使所有的节点按关键字值升序被访问到。

遍历树最简单的方法是递归。这个方法只需做三件事:

1.调用自身来遍历节点的左子树。

2.访问这个节点。

3.调用自身来遍历节点的右子树。

    public void inOrder(Node localNode){        if(localNode != null){            inOrder(localNode.leftChild);            System.out.print(localNode.data + " ");            inOrder(localNode.rightChild);        }    }


题目:如何判断一个二叉树为二叉搜索树?

public class IsBSTree {    static boolean flag = true;    static int last = Integer.MIN_VALUE;    public static boolean isBSTree(Node root) {        if (root.leftChild != null && flag)            isBSTree(root.leftChild);//先递归到二叉树的最左的节点,这时把该节点赋值为last,如果比last小,则不为二叉排序树        if (root.data < last)            flag = false;        last = root.data;        if (root.rightChild != null && flag)            isBSTree(root.rightChild);        return flag;    }}


4.查找最大值或最小值

最小值: 先走到根的左结点处,一直往左找,找到一个没有左节点的节点。

最大值,即先走到根的右结点,一直找到一个没有右节点的节点。

//查找最小值    public Node findMin(){        Node current = root; //从根节点开始找        Node leftLast = null; //存储最左结点        while ( current != null ) {            leftLast = current;            current = current.leftChild;        }        return leftLast;    }    //查找最大值    public Node findMax(){        Node current = root;        Node rightLast = null;        while(current != null){            rightLast = current;            current =  current.rightChild;        }        return rightLast;    }

5.删除节点

删除节点在二叉排序树中是十分复杂的。

有三种情况:

1.删除没有子节点的节点

2.删除只有一个子节点的节点

3.删除有两个子节点的节点

复杂度从上到下逐渐递增。

//删除    public boolean delete ( int key ) {        //需要先找到待删除的节点        Node current = root;        Node parent = root;        boolean isLeftChild = true;        while( current.data != key ){            parent = current;            if( key < current.data ){                isLeftChild = true;                current = current.leftChild;            } else {                isLeftChild = false;                current = current.rightChild;            }            if( current == null ){                return false;            }        }        //找到节点后        //1.没有左右子节点        if( current.leftChild == null && current.rightChild == null){            //首先判断是否为根节点,如果是,则为空子树            if( current == root ){                root = null;            }            //判断待删除节点是否为左子树            if(isLeftChild){                parent.leftChild = null;            } else {                parent.rightChild = null;            }            //2.第二种情况,待删除节点只有一个左子节点或右子节点        } else if ( current.rightChild == null ){ //不能写成current.leftChild != null ,双子节点也是这种情况            if(current == root){                root = current.leftChild;                //两种情况,是在parent节点的左边还是右边?                //例如:   在父节点的左子树                在父节点的右子树                //          10--->     parent节点        <--- 10                //         /                                   \                //        7 --- >      待删除      <----       13                //       /                                    /                //      4                                    11            } else if (isLeftChild) {                //左边                parent.leftChild = current.leftChild;            } else {                //右边                parent.rightChild = current.leftChild;            }        } else if (current.leftChild == null) {            if(current == root){                root = current.rightChild;            } else if(isLeftChild){                parent.leftChild = current.rightChild;            } else {                parent.rightChild = current.rightChild;            }            //第3种情况。            //找到后继节点的方法下面有写。        } else {            //获取后继节点            Node successor = getSusseccor(current);            if(current == root){                root = successor;            } else if(isLeftChild){                parent.leftChild = successor;            } else {                parent.rightChild = successor;            }            successor.leftChild = current.leftChild;        }        return true;    }



3.删除两个子节点的节点,就不能只用它的一个子节点代替它而已。如图所示:



假设要删除25,并且用它的根35去取代它。那么35的左子节点是谁?是要删除节点25的左子节点15,还是35原来的左子节点的30?

然而这两种情况30都会放的不对,但又不能删掉它。


删除的方法是去找待删除节点的中序后继节点。

中序后继节点:比该节点的值 “次高“ 的节点是它的中序后继,可以简称为该节点的后继。



那要怎么找到这个中序后继节点呢?

首先去找待删除节点的右子节点,因为右子节点一定是比待删除节点是大的。然后找到待删除节点的右子节点的左子节点(如果有的话),顺着左子节点的路径一直找一下去。

这个路径上的最后一个左子节点就是带删除节点的后继了。


为什么可以用这个算法呢?

这里实际是要找比待删除节点的值大的节点集合里面最小的一个节点。当找到待删除节点的右子节点后,在右子节点下的所有子树的值都会比待删除的节点大。

现在要找到这颗树中的最小值,在二叉搜索树中,想要找到最小值,只需要沿着左子节点一直找下去即可。

因此,这个算法可以找到比初始结点大的最小的节点,它就是它要找到的后继。


如果待删除节点的右子节点没有左子节点。那么这个右子节点就是后继了。如图:


找到后继的代码:

//找到后继节点的方法:    public Node getSusseccor(Node delNode){        Node successorParent = delNode;        Node successor = delNode;        Node current = delNode.rightChild;        while (current != null ){            successorParent = successor;            successor = current;            current = current.leftChild;        }        if(successor != delNode.rightChild){            successorParent.leftChild = successor.rightChild;            successor.rightChild = delNode.rightChild;        }        return successor;    }


正如看到的那样,后继可能有两种情况。current是待删除的节点。

1.后继有可能就是current的右子节点

2.后继有可能就是current的右子节点的左子孙节点。


如果是第一种情况

1.把current的父节点的右子节点(有可能也是左子节点)的rightChild指向后继。

2.然后把current的leftChild节点接到后继的leftChild字段。

如图所示:



看到这里就会发现删除是十分的棘手的。

有些程序员,尝试着躲避它,在Node类加一个Boolean字段,名称如isDeleted.。要删除一个节点的时候,就把这个节点的这个字段置为true。

这样,删除的节点不会改变树的结构。当然,这样做存储中还保留着这种”已经删除“的节点。