排序算法之二叉排序树

二叉排序树:是一种树,我们用这种结构存储数据

那么什么才算二叉排序树?

二叉排序树:
* 特点:又称为二叉查找树,要么是一个空树要么符合下面列性质
* 若左子树不为空,则左子树上的所有节点的值均小于它的根结构的值
* 若右子树不为空,则右子树上的所有节点的值均大于它的根结构的值
* 并且根节点的左右子树都要满足上面两个条件才称为二叉排序树
* 根据前两个性质可得二叉排序树不能有重复数值
* 这个树因为左边都是小数,中间都是中数,右边都是大数,所以我们直接通过中序遍历便可按顺序输出
* 目的:为了提高查找和插入删除关键字的速度

所有的步骤全在代码中写了,直接贴代码。

package sorttree;


import org.omg.Messaging.SYNC_WITH_TRANSPORT;

import java.util.ArrayList;
import java.util.List;

//二叉排序树
/*二叉排序树:
* 特点:又称为二叉查找树,要么是一个空树要么符合下面列性质
* 若左子树不为空,则左子树上的所有节点的值均小于它的根结构的值
* 若右子树不为空,则右子树上的所有节点的值均大于它的根结构的值
* 并且根节点的左右子树都要满足上面两个条件才称为二叉排序树
* 根据前两个性质可得二叉排序树不能有重复数值
* 这个树因为左边都是小数,中间都是中数,右边都是大数,所以我们直接通过中序遍历便可按顺序输出
* 目的:为了提高查找和插入删除关键字的速度
*
* 创建的算法我想了很久:这里说下它和创建普通二叉树的区别
*普通二叉树:创建时递归一次建立一个
*排序二叉树:它必须递归找到它该放的位置才建立
* */

class TreeNode{
    int data;//数据域
    TreeNode leftNode;//左子树
    TreeNode rightNode;//右子树
}



public class SortTree {
    static TreeNode treeNode = new TreeNode();//设置根节点为类变量。
    static TreeNode cordNode = new TreeNode();//设置记录节点为类变量。

    public static void main(String[] args) {
        int i;
        int[] sort = new int[]{42, 12, 23, 64, 74, 34, 76, 24, 53, 63, 6};//建立一个普通数组:
        //TreeNode treeNode = new TreeNode();//设置根节点
        treeNode.data = sort[0];//初始化根节点是数组第一个数
        TreeNode parNode = new TreeNode();//设置记录双亲的节点
        //TreeNode cordNode = new TreeNode();//设置记录最后的节点
        for (i = 1; i < sort.length; i++)//对sort的所有元素进行排序,前面已经对第一个数赋值了,直接从第二个数开始操作
            createSortTree(sort[i], treeNode);
        printTree(treeNode);//中序遍历输出
        System.out.println(" ");
        searchRorF(treeNode, 25, parNode, cordNode);//查找顺序二叉树的点
        // treeNode = insert(treeNode, 13);//第一种插入调用语句
        treeNode = insert(treeNode, 13, cordNode);
        printTree(treeNode);//中序遍历输出
        System.out.println(" ");
        treeNode=deletBST(treeNode,treeNode,42);
        printTree(treeNode);
    }

    //需求:每次只确定sort数组中的一个单元,,对这个单元排序,
    public static void createSortTree(int i, TreeNode treeNode) {//完成一个无序数列到一个二叉排序树的建立
        /*建立思想:先选取第一个数字为根节点,依次读取顺序表的内容,将大于这个节点放在右子树,小于节点的放在左子树
         * 通过循环遍历每个数,对这个数按照要求一直遍历整个树,直到找到适合它的位置,循环结束创建完成
         *
         * 参数:int[] sortTree用户输入的列表,获得
         * */

        if (i < treeNode.data)//判断节点是否小于当前根节点,是的话向左子树遍历
        {

            if (treeNode.leftNode == null) {//如果没有左子树,就要连接
                TreeNode treeNode1 = new TreeNode();
                treeNode1.data = i;
                treeNode.leftNode = treeNode1;

            } else {
                createSortTree(i, treeNode.leftNode);
            }
        } else if (i > treeNode.data)//判断节点是否大于当前根节点,是的话向右子树遍历
        {

            if (treeNode.rightNode == null) {//如果没有右子树,就要连接
                TreeNode treeNode1 = new TreeNode();
                treeNode1.data = i;
                treeNode.rightNode = treeNode1;
            } else {
                createSortTree(i, treeNode.rightNode);
            }
        }


    }

    public static void printTree(TreeNode treeNode) {//中序输出顺序二叉树 ,接受参数为排好序的二叉树顶点,
        if (treeNode != null) {//如果递归到的节点为空,结束这层递归,返回上一次递归执行
            printTree(treeNode.leftNode);//先一直搜寻左节点到最底部,当递归到底部的下一个节点时为空,我们通过最外层的控制语句结束这层递归
            System.out.print(treeNode.data + " ");//当上面的那层递归结束到了这里说明当前节点是最后一个节点,输出这个节点便是最底层的左节点
            printTree(treeNode.rightNode);//开始递归查询它的右节点,直到查到空为止
        }
    }


    public static boolean searchRorF(TreeNode treeNode, int num, TreeNode parNode, TreeNode cordNode) {
        if (treeNode == null)//第一次传进来判断传入的数是否为空,为空则直接返回false结束查找,之后传进来如果为空也说明找完了并没找到那个数
        {
            cordNode = parNode;//如果没找到,将双亲结点赋值给记录节点

            return false;//返回false

        } else if (treeNode.data == num)//再判断传入的点是否等于treeNode的data,如果相等则返回true退出。
        {
            cordNode = treeNode;//如果找到,则赋值给找到的节点
            System.out.println("找到了要查找的数据");
            return true;
        } else if (num < treeNode.data) { //如果传入值小于当前结点数据,则去它的左子树查找。
            return searchRorF(treeNode.leftNode, num, treeNode, cordNode);
        } else { //如果传入值大于当前结点数据,则去它的右子树查找。
            return searchRorF(treeNode.rightNode, num, treeNode, cordNode);
        }
    }

    //第三种方法把存储结点设置为全局变量,就不实现了,因为还要改seach的方法

//第二种方法:insert里直接包含了查找位置的代码,这样可以直接获得记录节点,但是无法使用递归,只能使用迭代循环操作
    //参数说明
    public static TreeNode insert(TreeNode treeNode, int num, TreeNode cordNode) {
        TreeNode insNode = new TreeNode();//初始化要插入的节点,
        TreeNode operaNode;//操作用的点
        operaNode=treeNode;//把根结点赋值给操作点,让操作点进行操作,保留根节点,最后返回
        insNode.data = num;//赋值
        if (operaNode == null) {//如果根节点为空直接将值赋给根节点
            operaNode = insNode;
            return treeNode;//完成操作返回这个数
        }
        while (operaNode != null) {//当节点为空时说明到了这个结点该插入的位置了
            if (operaNode.data == num)//如果相等,则退出,不允许有重复值
                return treeNode;//相等返回没操作的根节点就行了
            else if (num < operaNode.data) {//如果查询数小于当前结点数
                cordNode = operaNode;//先记录父节点
                operaNode = operaNode.leftNode;//向左子树查询
            } else {//如果查询数大于当前结点
                cordNode = operaNode;//先记录父节点
                operaNode = operaNode.rightNode;//向右子树查询
            }
        }
        //循环完就得到了这个该插入的位置
        //判断插入到哪里
        System.out.println("插入"+cordNode.data);
        if (num < cordNode.data) {//小于的话插入左子树
            cordNode.leftNode = insNode;
        } else {
            cordNode.rightNode = insNode;
            }
            return treeNode;
    }


/*删除算法的思路:
先通过递归找到要删除的元素
*要删除的结点如果只有左子树,删除这个点后连接左子树
*要删除的结点如果只有右子树,删除这个点后连接右子树
* 要删除的结点如果有左右子树,找到它的大于它的最小结点或者小于它的最大结点,然后替换,再删除结点,这时候这个结点的位置只有左或者右子树,再连接左右子树就行了
* 在中序遍历中他们三个结点相邻,所以选择他们替换不会对序列有任何影响
* */
/**
 * rootNode再这里设置是因为最后要返回根节点
 *treeNode在这里设置是操作的结点
 * 算法思路:
 * 先找到要删除的点,没找到的话就直接退出,找到的话通过delet函数进行删除
 *
 ***/

public static TreeNode deletBST(TreeNode rootNode,TreeNode treeNode,int num) {
    if (num != treeNode.data) {//如果没找到则结束
        return rootNode;
    }
        else{
            if(num==treeNode.data)//找到就进行删除操作
                return delete(rootNode,treeNode);
        else if (num < treeNode.data) {//如果小于去左子树
                return deletBST(rootNode,treeNode.leftNode,num);
            }
            else
               return deletBST(rootNode,treeNode.rightNode,num);
            //如果小于去右子树
        }
    }

//删除操作思路:
//先判断是否只有左右子树,有的话直接删除
//如果左右子树都有,我们找到小于要删除点的最大结点记为max
//这个max点便是要删除点的下一个左结点的右子树的最后一个右结点
//如果max点有右子树 ,那么要拼接替换删除后的右子树
//如果max点只有左子树没有右子树,那么所找的点max只有左子树,替换后拼接左子树就好
public static TreeNode delete(TreeNode rootNode,TreeNode treeNode){
    TreeNode operaNode;//用作这个节点向下遍历最后得到max结点,传进来的结点并不动
    TreeNode cordNode;//这个节点用来记录max结点的前驱,功能代码中会讲
                if(treeNode.rightNode==null) {//如果只有左子树
                    treeNode = treeNode.leftNode;//拼接
                    return rootNode;
                }
               else if (treeNode.leftNode==null) {//只有右子树
                    treeNode = treeNode.rightNode;//拼接
                    return rootNode;
                }
               else {//如果左右子树都有,先找到那个max点,就是要删除结点的下一个左结点的最右结点
                      cordNode=treeNode;//先将传入结点赋给操作结点以便进行接下来的操作
                      operaNode=treeNode.leftNode;//先获得要删除结点的左结点,
                    while(operaNode.rightNode!=null){//一直查找左结点的最后一个右结点,这个循环结束后,得到的opera为max结点
                        //保存当前结点为前驱节点后,向右子树继续遍历
                        cordNode=operaNode;
                        operaNode=operaNode.rightNode;
                    }
                    treeNode.data=operaNode.data;//把查到的max的值覆盖到要删除节点
                    if (cordNode!=treeNode){//如果前驱结点不等于删除位置结点,说明刚开始的要删除的结点的左结点有右结点,这是max是最后一个右结点,并没有右子树
                        cordNode.rightNode=operaNode.leftNode;//我们把max结点的左子树赋值给前驱结点的右子树后,相当于去除了这个结点
                        return rootNode;
                    }else {//到这步说明刚开始的要删除的结点的左结点没右结点,我们只需要将左子树拼接就好
                        cordNode.leftNode = operaNode.leftNode;
                        return rootNode;
                    }
                }
}
}

//因为java语句不像c语言直接操作地址,我想了三种办法来插入
//第一种:两个查找法。我又写了一个seach方法,专门返回记录的结点,通过两个seach方法结合使用
    //先通过返回boolean值的seach方法来确定是否有相同值,再通过返回记录结点的seach方法返回记录结点。
    //然后进行判断插入操作


    /// /顺序二叉树数的查找。接受一个数字,每次判断它是大于还是小于当前点,如果大于去这个点的右子树寻找,如果小于到这个数的左子树寻找
    // 直到最后判断了所有的点
    //参数说明:
    // TreeNode treeNode:根节点   int num:查找的数
    //  TreeNode parNode:记录双亲节点用  TreeNode cordNode最后返回的节点(如果没找到就是最后查找的哪个null节点的双亲,找到的话就是找到的那个元素)
    //  一开始parnode为null,因为根节点没有双亲结点
//    public static  TreeNode searchNode(TreeNode treeNode,int num,TreeNode parNode,TreeNode cordNode){
//        if(treeNode==null)//第一次传进来判断传入的数是否为空,为空则直接返回false结束查找,之后传进来如果为空也说明找完了并没找到那个数
//         {
//             cordNode=parNode;//如果没找到,将双亲结点赋值给记录节点
//             System.out.println("没找到要查找的数据");
//            return cordNode;//返回记录结点
//
//        }
//       else if(treeNode.data==num)//再判断传入的点是否等于treeNode的data,如果相等则返回true退出。
//        {
//            cordNode=treeNode;//如果找到,则赋值给找到的节点
//            System.out.println("找到了要查找的数据");
//            return cordNode;
//        }
//         else     if (num<treeNode.data) { //如果传入值小于当前结点数据,则去它的左子树查找。
//            return searchNode(treeNode.leftNode, num,treeNode,cordNode);
//        }
//        else     { //如果传入值大于当前结点数据,则去它的右子树查找。
//            return searchNode(treeNode.rightNode,num,treeNode,cordNode);
//        }
//        }
        /*方法说明:
        * 方法思路:先查找节点,如果查找到不操作,因为排序二叉树不允许有重复数字
        *   没查找到节点,这时cordTree会返回查到最后返回的空节点的双亲节点,也就是我们要插入的地方
        *   如果这个记录节点为null,说明排序二叉树为空,当作根节点使用
        *   如果这个值小于当前记录节点,当作cord的左子树
        *   如果这个值大于当前记录节点,当作cord的右子树
        * 方法参数:接受排序二叉树根节点和要插入的数
        * */
//        public static TreeNode insert(TreeNode treeNode,int ins){
//            TreeNode parNode=new TreeNode();//设置记录双亲的节点
//            TreeNode cordNode=new TreeNode();//设置最后获得元素的节点
//            TreeNode insNode=new TreeNode();//初始化要插入的节点
//            insNode.data=ins;//赋值
//            if(!searchRorF(treeNode,ins,parNode,cordNode)) {//先查找节点,如果查找到不操作,因为排序二叉树不允许有重复数字
//                cordNode=searchNode(treeNode,ins,parNode,cordNode);
//                if (cordNode == null)  //如果这个记录节点为null,说明排序二叉树为空,当作根节点使用
//                    treeNode = insNode;//将这个要插入的结点当作根结点
//                else if (ins < cordNode.data) { //如果这个值小于当前记录节点,当作cord的左子树
//                    cordNode.leftNode = insNode;
//                }
//                else  {   //如果这个值大于当前记录节点,当作cord的右子树
//                    cordNode.rightNode = insNode;
//                }
//                return treeNode;
//            }else {
//                return treeNode;//有关键字退出
//            }
//        }
// }

创建的算法我想了很久:这里说下它和创建普通二叉树的区别
普通二叉树:创建时递归一次建立一个
排序二叉树:它必须递归找到它该放的位置才建立


这里着重说下插入方法,我看的大话结构里用的是c语言可以直接用指针操作地址,因此可以在插入操作里直接调用查询操作找到记录点进行插入,而java无法直接操作地址,只能通过返回值,问题在于返回值只能返回一个,我既需要查询里判断是否查到的false/true值,又需要记录结点,所以这条路走不通。

我想了三种办法:
1.最简单的方法,修改根结点和记录节点都为全局变量,这样就可以直接在函数里对结点进行操作。
2.写两个查询方法,一个返回true/false,另一个返回记录结点,通过两个返回值便可进行操作,但是时间复杂度很高
3.可以把查询的方法融入到insert的方法,这样不用再查询一次,提高程序效率。

猜你喜欢

转载自blog.csdn.net/sunmeok/article/details/80836255