首页 > 编程语言 >Java-数据结构-排序-(二) (๑¯∀¯๑)

Java-数据结构-排序-(二) (๑¯∀¯๑)

时间:2024-09-22 20:24:03浏览次数:3  
标签:排序 Java 基准值 int start right array 数据结构 left

文本目录:

❄️一、交换排序:

        ➷ 1、 冒泡排序:

      ▶ 代码:

         ➷ 2、 快速排序:

                  ☞ 基本思想:

                  ☞ 方法一:Hoare法

      ▶ 代码:

                   ☞ 方法二:挖坑法

      ▶ 代码:

                   ☞ 方法三:前后指针法

      ▶ 代码:

                  ☞ 优化:

                  ☑ 方法一:三数取中法

      ▶ 优化后的代码:

                  ☑ 方法二:递归到一定小的区间时,进行插入排序

      ▶ 优化后的代码:

                   ☞ 非递归实现快速排序:

       ▶ 代码:

❄️总结:


❄️一、交换排序:

     对于交换排序的思想就是:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。

      交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动


         1、 冒泡排序:

     对于这个排序方法就是我们很熟悉的一种排序了,我们的在 C语言中也使用过这种排序,在这里呢我们呢就简单的看一遍代码就可以了。来看看最优化的代码:

      ▶ 代码:

/**
 * 冒泡排序
 * 时间复杂度:这里需要分情况:
 *           没有优化:O(N^2) 优化后:最好可以达到O(N)
 * 空间复杂度:O(1)
 * 稳定性:稳定
 * @param array
 */
public static void bubbleSort(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            boolean flag = true;
            for (int j = 0; j < array.length - i - 1; j++) {
                if (array[j] > array[j + 1]) {
                    swap(array,j,j + 1);
                    flag = false;
                }
            }
            if (flag) {
                break;
            }
        }
}

这个呢就是我们的优化后的冒泡排序了。 


          2、 快速排序:

快速排序是 Hoare 提出的一种二叉树结构的排序方法。

                  ☞ 基本思想:

    任取待排序元素序列中的某个元素作为基准值,按照该排序码将其待排序集合分割成两个序列左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后所有左右序列重复该过程,直到所有元素都排在相应的位置上为止。

   我们对于快速排序呢有三种做法,我们一个一个来看:


                  ☞ 方法一:Hoare法

     对于这个方法呢,我们就是在最开始的数组中

    0 下标位置设一个left,在最后一个下标设一个right,我们的把 left 这个下标的值 作为基准值

    之后我们从后面开始找比 基准值 小的值停下来,从前面开始找比 基准值 大的值停下来,

    之后交换我们的 left 和 right 的值,之后继续走,直到left和right相遇停止,之后把 left 下标的值和我们的基准值进行交换。

这样呢,我们的 基准值 的左面都比基准值小,基准值 的右面都比基准值大

     之后我们再把基准值左面的和右面的再次执行上述的操作,直至剩下一个数据就有序了

我们来看看这个方法的流程图:

OK,这就是我们的这个方法的流程,这个呢是比较难理解的,要多看几遍如果不是很理解的话。

   但是在上面图中,我们要注意,我们的开始和结束为止呢,不能使用 left 和 right 的,我们的left和right 就是被赋值的形参,我们的实参使用 start 和 end来表示 开始与结束位置。


      ▶ 代码:

我们来看看这个方法一的代码:

/**
 * 快速排序的 Hoare 方法
 *
 * 时间复杂度:
 *      最坏的情况下:当数据为1 2 3 4 5 6 7 8 9 10 ......或者
 *                        .......10 9 8 7 6 5 4 3 2 1  时候呢为 O(N^2)
 *      最好的情况下:O(N*logN)
 *      我们使用快排呢都是 O(N*logN)
 * 空间复杂度:
 *      最坏的情况下:O(N)
 *           都在一面,我们的递归需要开辟空间
 *
 *      最好的情况下:O(logN)
 * 稳定性:
 *      不稳定的
 * @param array
 */
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
}
private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }

        int pivot = partition(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
}
private static int partition(int[] array,int left,int right) {
        int tmp = array[left];
        int tmpleft = left;//把一开始的left位置保存下来,方便我们后面进行交换

        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }

            while (left < right && array[left] <= tmp) {
                left++;
            }
            //两个if语句判断结束之后呢,我们的 right 是比 tmp 小的
            //left 是比 tmp 大的
            //进行交换
            swap(array,left,right);
        }
        //循环结束,我们的left和right相遇,把基准值交换
        swap(array,left,tmpleft);
        //这时候我们的 left 就是我们的 基准值 交换后的下标
        return left;
}

这里要注意我们要从后往前开始找,不能从前往后,因为可能会和大的值进行交换

这样就把 7 交换到 前面去了,不可以。 

而且我们的从后往前找和从前往后找的时候一定要有等于号,如果没有就会出现死循环的情况: 这个呢就是我们快速排序的 Hoare 方法了。我们来看看下一种方法:


                   ☞ 方法二:挖坑法

    挖坑法就是我们一开始把我们 left 位置设置的基准值 拿出来放到 pivot 变量中

    我们之后 从后往前走 right 找到比 pivot 小的元素 放到我们的 left 中(因为我们一开始把 left 的基准值拿出来了,所以相当于里面没有元素),放完之后我们的 right 停下,之后再移动我们的 left 从前面找比 pivot 的值大的元素,放到我们的 right 中

    之后循环这个过程,直至我们的 right 和 left 相遇,这时把 pivot 放到 left 中,这样就实现了我们的基准值的左面都比其小,右面都比其大。

     之后再在基准值的左面和右面都执行这个操作,直至剩余一个元素。

    我们呢可以发现啊,对于这个挖坑法呢和上面我们介绍的 Hoare 的方法还是很相似的。我们来看看这个挖坑法的流程图:

 这个呢就是我们挖坑法的流程图了,虽然我没有画的完全,但是呢还是可以理解的,我们呢来看代码如何编写: 


      ▶ 代码:
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
    }
    private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }

        int pivot = partition(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
    }

    private static int partition(int[] array,int left,int right) {
        int tmp = array[left];//这个就是基准值
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            //找到比 基准值小的放到 left中
            array[left] = array[right];
            
            while (left < right && array[left] <= tmp) {
                left++;
            }
            //找到比 基准值大的放到 right中
            array[right] = array[left];
        }
        //right和left相遇后,把 tmp 放到left中
        array[left] = tmp;
        return left;
    }

    我们一般以挖坑法为主,挖坑法是三种方法最重要的,如果题中没有要求使用哪种方法,我们默认使用挖坑法。

这个方法就结束了,我们来看看下一种方法:前后指针法。


                   ☞ 方法三:前后指针法

    对于这个方法就是,在 left 的位置设置一个 prev 在 left +1 的位置设置一个 cur

    之后我们判断 cur 的元素是否小于 left 的元素(基准值),并且 prev 的下一个元素是否等于 cur 的元素

    如果 cur 的元素小于left 的元素的值并且 prev 的下一个元素不等于 cur 的元素,就把cur 和prev 的元素进行交换。反之,则 cur++。

     一直循环这个操作,直至 cur 大于了right 这个位置。这个时候,我们把 prev的值和left 的值进行交换。

     就实现了 基准值的左面都是小于基准值的,右面都是大于基准值的。

我们来看流程图:

  这个呢就是我们的呢前后指针的流程图了,我虽然没有在上图中把遍历左面和右面的流程画出来,但是呢和我们图中的执行流程是差不多的,我呢就不在这里画了,我们来直接看代码: 


      ▶ 代码:
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
    }
    private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }

        int pivot = partition2(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
    }

    //前后指针法
    private static int partition2(int[] array,int left,int right) {
        int prev = left;
        int cur = left + 1;
        while (cur <= right) {

            if (array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array,cur,prev);
            }

            cur++;
        }
        swap(array,prev,left);

        return prev;
    }

这个时候我们返回的是 prev 而非 left,这里需要注意。


                  ☞ 优化:

当我们使用快排的时候可能会出现这种情况:

我们有两种优化方式: 

                  ☑ 方法一:三数取中法

     我们的这个是什么意思呢?我们上面的数据为例,就是找到 left 和 right 的中位数 mid 这时候呢,我们从 left 和 right 和 mid 中找到一个中位数,和我们的的开始位置进行交换,就是我们的基准值了。我们来看看例子:

这就是我们 三数取中法, 这样是不是就不是单分支的了,就可以变成多分支的了。

那么我们要如何才能找到我们的中间值呢?我们有两种情况:

第一种情况:array[left] < array[right] 的时候。

第二种情况:array[left] > array[right] 的时候。

简单来说就是 找中间值 和 开始的位置进行交换,之后再执行我们的 挖坑法 来进行快速排序。


      ▶ 优化后的代码:
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
    }
    private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }

        //优化:三数取中法
        int mid = getMiddleNum(array,start,end);
        swap(array,start,mid);

        int pivot = partition(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
    }

    //优化:三数取中法
    private static int getMiddleNum(int[] array,int left,int right) {
        int mid = (left + right) / 2;

        if (array[left] < array[right]) {
            if (array[mid] < array[left]) {
                return left;
            } else if (array[mid] > array[right]) {
                return right;
            }else {
                return mid;
            }
        }else {
            if (array[mid] > array[left]) {
                return left;
            } else if (array[mid] < array[right]) {
                return right;
            }else {
                return mid;
            }
        }
    } 

这个就是我们的第一种优化方法。 


                  ☑ 方法二:递归到一定小的区间时,进行插入排序

      我们来想,当我们对一组数据进行排序的时候呢,是不是 越排序数据越趋于有序的,在我们上个博客中介绍 直接插入排序的时候呢,也介绍了当数据越有序效率越高所以我们可以在我们递归到一定小的区间时候呢,我们使用直接插入排序,来减少递归的次数,减少内存的开销

      我们的思路就是这样的,我们来看看代码是如何编写的,不能直接使用 直接插入排序,因为我们有区间,不是对整个数据进行 直接插入排序。

上一篇博客的传送门:

Java-数据结构-排序-(一) (。・ω・。)


      ▶ 优化后的代码:
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
    }
    private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }
        //优化:直接插入排序
        //当快排到一定的区间的时候我们的使用直接插入排序,来减少内存的开销
        //这里的区间是自定义的
        if (end - start +1 <= 8) {
            insetSortRange(array,start,end);
            return;
        }

        //优化:三数取中法
        int mid = getMiddleNum(array,start,end);
        swap(array,start,mid);

        int pivot = partition(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
    }
    //优化:直接插入排序
    private static void insetSortRange(int[] array,int start,int end) {
        for (int i = start + 1; i <= end; i++) {
            int tmp = array[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (array[j] > tmp) {
                    array[j + 1] = array[j];
                }else {
                    array[j + 1] = tmp;
                    break;
                }
            }
            //这是当我们的 j < 0的时候呢,
            //我们退出循环之后相当于 j+1 为0下标
            array[j + 1] = tmp;
        }
    }

这时候的整体的代码就是我们的最优状态了。 


                   ☞ 非递归实现快速排序:

      对于 快速排序 的非递归方法呢,我们需要借用 栈 来完成,我们的步骤就是:

1、先把数据进行一次 挖坑法 排序,这时候呢我们就有了 pivot 这个位置。

2、判断 pivot 的左右是否是存在 2个或 2个以上的元素,判断方法:

     如果 :pivot > start + 1 左面就有 2个或 2个以上的元素

     如果: pivot < end - 1  右面就有 2个或 2个以上的元素

3、我们把 start 这个小标 、pivot - 1 这个下标、pivot + 1 这个下标 和 end 这个下标按顺序进行        入栈操作。

4、我们出栈顶数据先给 end 再给 start ,这时候使用出栈的 start 和 end 值进行 挖坑法排序

5、排完序后,再执行 操作2 和 操作3,之后是操作4,直至栈为空。就排好序了。

 

我们来看看代码如何编写的,理解之后呢就非常的简单了: 


       ▶ 代码:
public static void quickSort(int[] array) {
        quickNor(array,0,array.length - 1);
    }
 //快速排序的非递归实现
    private static void quickNor(int[] array,int start,int end) {
        Stack<Integer> stack = new Stack<>();
        //求第一次挖坑法之后的 基准值位置
        int pivot = partition(array,start,end);
        //我们的把start、pivot - 1 、pivot + 1 、end 都入栈
        //我们还需要判断 基准值的左面和右面是否是大于等于2个的元素
        if (pivot > start + 1) {
            stack.push(start);
            stack.push(pivot - 1);
        }
        if (pivot < end - 1) {
            stack.push(pivot + 1);
            stack.push(end);
        }

        while (!stack.isEmpty()) {
            //出两个栈顶,先给end 后给start
            end = stack.pop();
            start = stack.pop();
            pivot = partition(array,start,end);
            //再次求完pivot 之后呢,在执行入栈操作
            if (pivot > start + 1) {
                stack.push(start);
                stack.push(pivot - 1);
            }
            if (pivot < end - 1) {
                stack.push(pivot + 1);
                stack.push(end);
            }
        }
    }
//挖坑法
    private static int partition(int[] array,int left,int right) {
        int tmp = array[left];//这个就是基准值
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            //找到比 基准值小的放到 left中
            array[left] = array[right];

            while (left < right && array[left] <= tmp) {
                left++;
            }
            //找到比 基准值大的放到 right中
            array[right] = array[left];
        }
        //right和left相遇后,把 tmp 放到left中
        array[left] = tmp;
        return left;
    }

到这里我们的交换排序就结束了。


❄️总结:

     OK,我们这次的博客就到这里就结束了,我们这次博客虽然我们只介绍了 一类排序——交换排序,但是呢我们这篇博客是非常重要的,因为我们的快速排序是经常使用的排序方法,所以要多加理解这个排序。

   我们在下一篇博客就会把排序收尾,让我们尽情期待吧!!!拜拜~~~

标签:排序,Java,基准值,int,start,right,array,数据结构,left
From: https://blog.csdn.net/2303_80388948/article/details/142421862

相关文章

  • springboot-ssm-java企业客户关系满意度评价管理系统 o1iv4
    目录项目介绍技术栈具体实现截图开发核心技术:开发工具和技术详细视频演示核心代码部分展示系统设计操作可行性可行性论证系统测试个人心得详细视频演示源码获取方式项目介绍本javaweb+maven项目采用的数据库是Mysql,使用Springboot框架开发,十分方便,也具有跨平台的优......
  • 【JavaSE】Java注解
    什么是注解         我们最早使用的注解有:方法重写@Override,在编译期间进行硬性检测,加在方法上就表明该方法是从父类重写过来的。        Java注解(Annotation)又称Java标注,它可以用来对类、方法、属性、参数、包等进行标注,然后让编译器或运行时其他类进......
  • Java 方法传参机制
    普通变量传参在Java中,一个方法可以选择需要参数或者无需参数,当方法需要参数时,传入参数都发生了什么变化是一个非常值得研究的问题,由此而来,我已自己所学来简单讲解一下其中的机制。实例1这是一个交换两个数值的方法,方法传入了两个形参a和b,交换之后并打印出来。class......
  • 数据结构与算法之间有何关系?
    相信很多人都应该上个《数据结构与算法》这门课吧,而这两个概念也如孪生兄弟一样经常被拿出来一起讨论。那它们究竟是一个什么样子的关系呢? 听到数据结构与算法我第一反应是想到了Pascal语言之父尼古拉斯·沃斯在他的《Algorithms+DataStructures=Programs》著作中提出了......
  • 基于Java Springboot 超市云购物系统
    一、作品包含源码+数据库+设计文档万字+全套环境和工具资源+部署教程二、项目技术前端技术:Html、Css、Js、Vue3、Element-ui数据库:MySQL后端技术:Java、SpringBoot、MyBatis三、运行环境开发工具:IDEA数据库:MySQL8.0数据库管理工具:Navicat10以上版本环境配置软件:JDK......
  • 基于Java Springboot 共享单车管理系统
    一、作品包含源码+数据库+设计文档万字+PPT+全套环境和工具资源+部署教程二、项目技术前端技术:Html、Css、Js、Vue3、Element-ui数据库:MySQL后端技术:Java、SpringBoot、MyBatis三、运行环境开发工具:IDEA数据库:MySQL8.0数据库管理工具:Navicat10以上版本环境配置软件......
  • 基于Java Springboot 中医学习服务管理系统
    一、作品包含源码+数据库+设计文档万字+全套环境和工具资源+部署教程二、项目技术前端技术:Html、Css、Js、Vue3、Element-ui数据库:MySQL后端技术:Java、SpringBoot、MyBatis三、运行环境开发工具:IDEA数据库:MySQL8.0数据库管理工具:Navicat10以上版本环境配置软件:JDK......
  • 基于Java Springboot 共享单车管理系统
    一、作品包含源码+数据库+设计文档万字+PPT+全套环境和工具资源+部署教程二、项目技术前端技术:Html、Css、Js、Vue3、Element-ui数据库:MySQL后端技术:Java、SpringBoot、MyBatis三、运行环境开发工具:IDEA数据库:MySQL8.0数据库管理工具:Navicat10以上版本环境配置软件......
  • Java创建接口详细过程
    在Java中,创建mapper、dao(数据访问对象)、service、serviceImpl(service实现类)、controller等组件是构建企业级应用常见的分层架构模式。这种分层架构有助于实现高内聚低耦合的设计,提高代码的可维护性和可扩展性。mapperrXML文件创建MapperXML文件用于定义Java对象和数据库表......
  • 从代码到部署:GitHub Actions实现Java项目CI/CD的完整实践
    从代码到部署:GitHubActions实现Java项目CI/CD的完整实践在现代软件开发中,持续集成和持续部署(CI/CD)已经成为了团队高效交付代码的关键策略。通过GitHubActions,可以轻松配置CI/CD流水线,实现从代码提交到部署的自动化工作流。本文将基于英语听力网站(studytool.site)项目介......