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

文本目录:

❄️一、交换排序:

        ➷ 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 后给startend = 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,我们这次的博客就到这里就结束了,我们这次博客虽然我们只介绍了 一类排序——交换排序,但是呢我们这篇博客是非常重要的,因为我们的快速排序是经常使用的排序方法,所以要多加理解这个排序。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/434113.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Brave编译指南2024 MacOS篇-获取源码(三)

引言 在上一篇文章中,我们介绍了Brave浏览器的基本特性,以及编译Brave所需的系统要求和推荐工具。现在,我们将进入编译过程的第一个实际步骤:获取Brave的源代码。这一步骤对于后续的编译和开发工作至关重要。 1. Brave源码的获取途径 Brave的源码托管在GitHub上,任何人都可以…

Scrapy框架入门

一、Scrapy简介 Scrapy是一款快速而强大的web爬虫框架&#xff0c;基于Twisted的异步处理框架、Twisted是事件驱动的。 Scrapy是由python实现的爬虫框架&#xff1a;架构清晰、可扩展性强、可以灵活完成需求。 一、Scrapy应用 scrapy及其他模块的安装 pip3 install scrapy…

【漏洞复现】数字通云平台智慧政务 login 存在登录绕过漏洞

》》》产品描述《《《 数字通云平台智慧政务OA产品是基于云计算、大数据、人工智能等先进技术&#xff0c;为政府部门量身定制的智能化办公系统。该系统旨在提高政府部门的办公效率、协同能力和信息资源共享水平&#xff0c;推动电子政务向更高层次发展。 》》》漏洞描述《《《…

【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)

文章目录 从零实现 list 容器&#xff1a;细粒度剖析与代码实现前言1. list 的核心数据结构1.1节点结构分析&#xff1a; 2. 迭代器设计与实现2.1 为什么 list 需要迭代器&#xff1f;2.2 实现一个简单的迭代器2.2.1 迭代器代码实现&#xff1a;2.2.2 解释&#xff1a; 2.3 测试…

设置VsCode搜索时排除文件,文件列表中隐藏文件

按照《VsCode gdb gdbserver远程调试C程序》中介绍的方法&#xff0c;配置好VsCode后&#xff0c;打开一个C/C工程&#xff0c;发现左侧的面板会显示编译时生成的中间文件&#xff08;比如.d和.o文件&#xff09;。我们可以通过设置隐藏掉一些我们不需要打开的文件以简洁面板…

如何使用 DomCrawler 进行复杂的网页数据抓取?

在互联网时代&#xff0c;数据是宝贵的资源。无论是市场分析、客户洞察还是内容聚合&#xff0c;从网页中抓取数据都是一项关键技能。Symfony 的 DomCrawler 是一个强大的工具&#xff0c;可以帮助开发者从复杂的网页中提取所需的数据。本文将详细介绍如何使用 DomCrawler 进行…

SO-ELM预测 | MATLAB实现SO-ELM蛇群算法优化极限学习机多输入单输出

回归预测 | MATLAB实现SO-ELM蛇群算法优化极限学习机多输入单输出 目录 回归预测 | MATLAB实现SO-ELM蛇群算法优化极限学习机多输入单输出效果一览基本介绍程序设计效果一览 基本介绍 Matlab实现SO-ELM蛇群算法优化极限学习机多变量回归预测 1.data为数据集,7个输入特征,1个输…

C#和数据库高级:密封类和方法覆盖

文章目录 一、密封类关键字&#xff1a;sealed方法覆盖 面向对象三大特性总结 一、密封类 关键字&#xff1a;sealed 方法覆盖 面向对象三大特性总结

Java项目实战II基于Java+Spring Boot+MySQL的厨艺交流平台设计与实现(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在美食文化…

记一次停车场后台管理系统漏洞挖掘

漏洞描述 停车场后台管理系统是一种专为停车场管理者设计的综合管理平台&#xff0c;旨在提供全面、高效、智能的停车场运营管理解决方案&#xff0c;系统利用现代信息技术&#xff0c;如物联网、大数据、云计算等&#xff0c;实现对停车场内车辆进出、车位管理、费用结算、安…

多模态——基于XrayGLM的X光片诊断的多模态大模型

0.引言 近年来&#xff0c;通用领域的大型语言模型&#xff08;LLM&#xff09;&#xff0c;如ChatGPT&#xff0c;已在遵循指令和生成类似人类的响应方面取得了显著成就。这些成就不仅推动了多模态大模型研究的热潮&#xff0c;也催生了如MiniGPT-4、mPLUG-Owl、Multimodal-G…

【工具分享】DoNex勒索病毒解密工具

前言 DoNex勒索软件首次被发现于2024年3月&#xff0c;是由一系列早期勒索软件演变而来&#xff0c;包括2022年4月首次出现的Muse、2022年11月的假冒LockBit 3.0&#xff0c;以及2023年5月的DarkRace。这款勒索软件主要针对美国、意大利和荷兰的企业&#xff0c;使用复杂的加密…

日本IT-正社员、契约社员、个人事业主该如何选?

正社員&#xff1a;就是「正规社员」的意思&#xff0c;按照公司的规定而直接雇用&#xff0c;而且没有制定雇用期间&#xff0c;基本上是以终身雇用至退休年龄&#xff08;70岁&#xff09;为前提。而被雇用的一方需要听从公司的业务命令&#xff0c;包括职位或职场的调迁&…

AI名词扫盲

本篇章主要介绍一些AI研究方向的名词以及解释&#xff0c;后续会持续补充&#xff0c;名词解释与时间顺序无关&#xff0c;欢迎各位大佬们在评论区查漏补缺。 目录 AI&#xff08;Artificial Intelligence&#xff0c;人工智能&#xff09;卷积神经网络&#xff08;CNN&#xf…

巧用枚举消除条件判断

shigen坚持更新文章的博客写手&#xff0c;记录成长&#xff0c;分享认知&#xff0c;留住感动。个人IP&#xff1a;shigen 在上一篇的文章结合HashMap与Java 8的Function和Optional消除ifelse判断中有讲到如何结合HashMap与Java 8的Function和Optional消除ifelse判断&#xff…

Linux进程切换以及调度算法

目录 Linux进程切换以及调度算法 Linux进程切换介绍 前置知识 进程切换过程分析 调度算法 补充知识 Linux进程切换以及调度算法 Linux进程切换介绍 前面提到&#xff0c;分时操作系统下&#xff0c;CPU会在多个进程中做高速切换以实现多个进程看似同时执行的&#xff0…

open-resty 服务安装redis插件

从github下载 作者&#xff1a;程序那点事儿 日期&#xff1a;2023/11/16 22:04 lua-resty-redis-cluster cd /usr/local/openresty/modules #进入到modules目录git clone https://github.com/cuiweixie/lua-resty-redis-cluster.git #下载插件mv lua-resty-redis-cluster/ …

字节跳动青训营x豆包Marscode 技术训练营报名啦!

最近字节跳动青训营又开营了&#xff0c;作为第二次参加的我来给没有了解过的同学从几个方面简单介绍一下。 青训营是什么 青训营是字节跳动 稀土掘金 社区发起的技术系列培训 & 人才选拔项目&#xff0c;面向在校大学生&#xff0c; 课程全程免费&#xff0c;包含前端、…

git小乌龟

下载git小乌龟 官方地址 Download – TortoiseGit – Windows Shell Interface to Git git小乌龟下载 选择自己对应的版本进行下载 安装完成后我们会发现是英文&#xff0c;这对我们这些英语不好的很不友好&#xff0c;所以就需要下载语言包 下载对应语言包 安装完成后我们…

Java Web —— 第十天(SpringBoot原理)

SpringBoot框架之所以使用起来更简单更快捷&#xff0c;是因为SpringBoot框架底层提供了两个非常重要的 功能&#xff1a;一个是起步依赖&#xff0c;一个是自动配置。 通过SpringBoot所提供的起步依赖&#xff0c;就可以大大的简化pom文件当中依赖的配置&#xff0c;从而解决…