排序的简单理解(下)

4.交换排序

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

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

4.1 冒泡排序

        冒泡排序(Bubble Sorting)即:通过对待排序的序列从前往后,依次比较相邻元素的值,若发现逆序则交换位置,使较大的元素逐渐移动到后部

4.1.1 算法分析

        下面的分析以将序列{2, 9, 7, 10, 30}从小到大排序为例!

        基本思想就是,在每一趟排序最终实现将该趟得到的最大的数移到序列的最后端

        核心操作就是通过比较相邻两个元素实现当相邻的两个元素逆序的时候,我们就交换它们。

第1趟排序:
        第1趟排序共比较了4次,将最大的数30冒泡到了序列的尾部。

第2趟排序:
        由于第一趟排序已经将最大是数30给冒泡到了最末端,因此在本次排序中,不需要再比较最后一个元素,故此本趟共比较了3次,将子序列(前四个元素)中最大的数10(整个序列中倒数第二大的数)冒泡到了子序列的尾端(原序列的倒数第二个位置)。

第3趟排序:
        在第三趟排序时,同理,倒数两个元素位置已经确定,即第一、第二大的数已经排好位置,只需要再将倒数第三大的数确认即可。故比较2次,实现倒数第三大的数9的位置确定。

第4趟排序:
        在第四趟排序时,只有第一、第二个元素的位置还不确定,只需要比较一次,若逆序,则交换即可。到此,排序算法完成,原序列已经排序成为一个递增的序列!

小结

  • 一共进行了数组大小-1次趟排序,即外层循环arr.length-1次;
  • 每趟排序进行了逐趟减小次数的比较,即内层循环arr.length-i-1次,i从0依次增加。

4.1.2 代码实现

        详细代码如下:

/*** @version 1.0* 冒泡排序*/public static void main(String[] args) {int[] array = {2,9,7, 10, 30};//排序前System.out.println("排序前:" + Arrays.toString(array));//冒泡排序for (int i = 0; i < array.length - 1; i++) {System.out.println("第" + (i+1) + "趟排序开始!");for (int j = 0; j < array.length - i - 1; j++) {//如果前面的数比后面的数大,则交换if(array[j] > array[j+1]){//交换int temp = array[j];array[j] = array[j+1];array[j+1] = temp;}System.out.println("------第" + (j+1) + "趟排序: " + Arrays.toString(array));}System.out.println("第" + (i+1) + "趟排序完成: " + Arrays.toString(array));System.out.println("================================================");}//输出排序后的结果System.out.println("排序后:" + Arrays.toString(array));}

        结果展示:

排序前:[2, 9, 7, 10, 30]
第1趟排序开始!
------第1趟排序: [2, 9, 7, 10, 30]
------第2趟排序: [2, 7, 9, 10, 30]
------第3趟排序: [2, 7, 9, 10, 30]
------第4趟排序: [2, 7, 9, 10, 30]
第1趟排序完成: [2, 7, 9, 10, 30]
================================================
第2趟排序开始!
------第1趟排序: [2, 7, 9, 10, 30]
------第2趟排序: [2, 7, 9, 10, 30]
------第3趟排序: [2, 7, 9, 10, 30]
第2趟排序完成: [2, 7, 9, 10, 30]
================================================
第3趟排序开始!
------第1趟排序: [2, 7, 9, 10, 30]
------第2趟排序: [2, 7, 9, 10, 30]
第3趟排序完成: [2, 7, 9, 10, 30]
================================================
第4趟排序开始!
------第1趟排序: [2, 7, 9, 10, 30]
第4趟排序完成: [2, 7, 9, 10, 30]
================================================
排序后:[2, 7, 9, 10, 30]

进程已结束,退出代码0

4.1.3 算法的不足及优化

        我们仔细观察一下我们上述数组在进行冒泡法时每一个外循环后的结果:

                 

        我们发现其实在第一次外循环结束之后,我们的当前数组已经完成了冒泡法整个算法的最终结果,但是由于我们的算法规定,它依旧要跑array.length-1次外循环,故此我们需要完成代码的优化,当我们的数组提前完成排序后,就要提前结束外循环,终止我们的冒泡排序;

        我们可以发现一个无序的数组在经过冒泡算法的排序之后,这些元素的位置在最后都是固定的,每一次的内循环,相应的元素可以理解为都是往那个自己最终的位置上跑,但是当一次内循环之后,发现我们当前的元素位置和该次内循环开始之前的元素位置一样,没有发生变化,这时候我们可以确认我们的元素都已经到达了自己的最终位置,可以提前终止冒泡算法,不在进行其他的外循环;

        所以最终的解决方案就是我们设置一个flag标志位,来判断当前内循环前后数组的元素有没有发生顺序的变化,优化代码如下图所示;

 public static void main(String[] args) {int[] array = {5, 1, 2, 3, 4};//排序前System.out.println("排序前:" + Arrays.toString(array));boolean flag = false; //用于标记是否进行了交换,true则说明进行了交换,false表示无//冒泡排序for (int i = 0; i < array.length - 1; i++) {System.out.println("第" + (i+1) + "趟排序开始!");for (int j = 0; j < array.length - i - 1; j++) {//如果前面的数比后面的数大,则交换if(array[j] > array[j+1]){//交换flag = true; //标记进行了交换int temp = array[j];array[j] = array[j+1];array[j+1] = temp;}System.out.println("------第" + (j+1) + "趟排序: " + Arrays.toString(array));}System.out.println("第" + (i+1) + "趟排序完成: " + Arrays.toString(array));System.out.println("================================================");if (!flag){//如果没有进行交换则直接退出,说明排序已经完成break;}else {//回退flag = false;}}//输出排序后的结果System.out.println("排序后:" + Arrays.toString(array));}

        测试结果: 

                          

        如图所示,经过优化后,我们相比于之前的代码,减少了两次内循环,提前结束了冒泡算法,大大的节省了时间资源; 

4.1.4 冒泡排序的特性总结

1、冒泡排序是一种非常容易理解的排序

        时间复杂度:O(N^2)

        空间复杂度:O(1)

        稳定性:稳定

2、什么时候最快?
        当输入的数据已经是正序时,我们的内循环和外循环的执行次数比较少;

4.2 快速排序

        快速排序是对冒泡排序的一种改进。基本思想为:通过一趟排序将要排序的数据分割为独立的两个部分,其中一部分的所有数据比另外一部分的所有数据要小,然后按照此方法对这两部分分别进行快速排序,整个过程可以递归进行,以此达到整个数据变成有序序列。

4.2.1 算法分析(递归)

        快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
        (1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
        (2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。
        (3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
        (4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

        下面来举例来详细的分析:

        首先,我们会把数组中的一个数当作基准数,然后从两边进行检索。

        其次,按照如下步骤进行:

                1、先从右边检索比基准数小的
                2、再从左边检索比基准数大的
                3、一旦检索到,就停下,并将检索到的两个元素进行交换
                4、重复上述步骤,直到检索相遇,则替换基准数,并更新区间,递归进行,最终序列会变得有序

        接下来就以{6,1,8,0,2,9,5,3,7}为例详细来分析一下步骤,具体分析一下第一趟排序:以6为基准数的步骤:

1、红色块标识基准数,left、right初始位置如图所示

2、right不断向左移动,寻找比基准数6小的数,如图所示,找到了3

3、此时left开始移动,不断向右移动,寻找比基准数大的数,找到了8,这时,left、right都找到了对应的数,进行交换:

4、right继续向左寻找比基准数6小的数,找到后停止移动,此时left继续向右寻找比基准数大的数,当left与right都找到对应的数后,再次将二者的数值进行交换。

5、重复上述步骤,一直到left与right相遇,二者共同指向了5的位置,则将基准数与该位置的数进行交换,这样就可以观察到,6的左边都是比6小的,右边都是比6大的。

6、该过程需要递归进行,直到序列有序。即以5为基准数,递归6左边的区间,再以9为基数递归6右边的区间,反复进行,直到left >right退出。

4.2.2 代码实现

        冒泡法代码如下:

public static void quickSort(int[] arr, int left, int right) {//边界条件if (left > right){return;}//定义基准数和左右指针int l = left;int r = right;int base = arr[left];//循环,将比基准数小的放在左边,比基准数大的放在右边while (l != r){//先从右边找比基准数小的,停下while (arr[r] >= base && l < r){r--;}//从左边找比基准数大的,停下while (arr[l] <= base && l < r){l++;}//此时已经找到对应的l 和 r,进行交换int temp = arr[l];arr[l] = arr[r];arr[r] = temp;}//至此,基准数两边都按照需要排好了,只需要将基准数与lr相遇的位置进行交换arr[left] = arr[l];arr[l] = base;//打印中间结果System.out.println(Arrays.toString(arr));//先向左找quickSort(arr, left, r-1);//向右递归quickSort(arr, l+1, right);}

        测试代码:

public static void main(String[] args) {int[] arr = {6,1,8,0,2,9,5,3,7};quickSort(arr, 0, arr.length-1);System.out.println("排序后: " + Arrays.toString(arr));
}

        测试结果:

4.2.3 快速排序非递归实现  

思路:

  • 建立一个栈

  • 先让一组数据的起点入栈

  • 再让一组数据的终点出栈

  • 然后两次出栈,分别作为该数据的起点与终点

  • 然后经过我们上面所写的方法进行排序后

  • 再将两组数据进行入栈

  • 以此循环直到栈为空

        代码实现: 

    //快速排序递归实现public int[] quickSortPlus(int[] array) {int[] arr = Arrays.copyOf(array,array.length);Deque<Integer> stack = new LinkedList<>();int left = 0;int right = array.length-1;int pivot = 0;stack.push(left);stack.push(right);while (!stack.isEmpty()) {right= stack.pop();left = stack.pop();pivot = partition(arr,left,right);if(pivot > left+1) {stack.push(left);stack.push(pivot-1);}if(pivot < right-1) {stack.push(pivot+1);stack.push(right);}}return arr;}private  int partition(int[] array,int left,int right) {int tmp = array[left];while (left < right) {while (left< right && array[right] >= tmp) {right--;}array[left] = array[right];while (left< right && array[left] <= tmp) {left++;}array[right] = array[left];}array[left] = tmp;return left;}

4.2.4 特性总结

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫 快速 排序

2. 时间复杂度: O(N*logN)

3. 空间复杂度:O(logN)

4. 稳定性:不稳定

5. 归并排序

        归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并,归并排序核心步骤图解:

5.1算法分析 

合并相邻有序子序列 

...以此类推

...

...

如果当其中的一个子序列全部转移到temp数组中时,另外未空的子序列的元素直接全部按当前顺序移入到temp中即可。

5.2 代码实现

        代码部分:

static int count = 0;public static void main(String[] args) {int[] arr = {10, 6, 7, 1, 3, 4, 2,9};int[] temp = new int[arr.length];mergeSort(arr, 0, arr.length - 1, temp);System.out.println("归并排序后: arr[] = " + Arrays.toString(arr));}//归并排序public static void mergeSort(int[] arr, int left, int right, int[] temp){if (left < right){int mid = left - (left - right) / 2;//向左递归分解mergeSort(arr, left, mid, temp);//向右递归分解mergeSort(arr, mid + 1, right, temp);//排序 合并merge(arr, left, mid, right, temp);}}/*** 合并的方法* @param arr  排序的原始数组* @param left  左边有序序列的初始索引* @param mid  中间索引* @param right  右边索引* @param temp  中转数组*/public static void merge(int[] arr, int left, int mid, int right, int[] temp){int i = left; //初始化i,左边有序序列的初始索引int j = mid + 1; //初始化j,右边有序序列的初始索引int t = 0; //指向temp数组的当前索引//先把左右两边有序数据按照规则填充到temp数组,直到左右两边有一边处理完毕while (i <= mid && j <= right){if (arr[i] <= arr[j]){temp[t] = arr[i];t++;i++;}else {temp[t] = arr[j];t++;j++;}}//把剩余的一方依次填充到temp数组while (i <= mid){ //左边序列还有剩余的元素temp[t++] = arr[i++];}while (j <= right){ //右边序列还有剩余的元素temp[t++] = arr[j++];}//将temp数组的元素拷贝到arr//拷贝每次小序列t = 0;int tempLeft = left;while (tempLeft <= right){arr[tempLeft++] = temp[t++];}count++;System.out.println("第" + count + "次合并: arr[] = " + Arrays.toString(arr));}

        测试结果展示:

        分析:

{10, 6, 7, 1, 3, 4, 2,9}被拆分成了{10, 6}{7, 1}{3, 4}{2, 9}:

第一次合并:{6, 10}有序
第二次合并:{1, 7}有序
第三次合并: {1, 6, 7, 10}有序
第四次合并:{3, 4}有序
第五次合并:{2, 9}有序
第六次合并: { 2,3,4,9}有序
第七次合并:{1,2,3,4,6,7,9,10}有序

5.3 特点总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

  2. 时间复杂度:O(N*logN)

  3. 空间复杂度:O(N)

  4. 稳定性:稳定

 5.4 海量数据的排序问题

        外部排序:排序过程需要在磁盘等外部存储进行的排序

        前提:内存只有 1G,需要排序的数据有 100G

        因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序:

         1. 先把文件切分成 200 份,每个 512 M

         2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以

        3. 进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

6.排序算法复杂度及稳定性分析

        详解总概图如下所示:

ps:本次的内容就到这里了,本文的相关内容吸取了博主【兴趣使然黄小黄 】的相关思想,如果大家感兴趣的话,可以去了解一下他,如果喜欢的话还请一键三连哦!!!

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

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

相关文章

vue3若依框架,在页面中点击新增按钮跳转到新的页面,不是弹框,如何实现

在router文件中的动态路由数组中新增一个路由配置&#xff0c;这个配置的就是新的页面。 注意path不要和菜单配置中的路径一样&#xff0c;会不显示内容。 在菜单配置中要写权限标识就是permissions:[]里的内容 在children里的path要写占位符info/:data 点击新增按钮&#x…

HTML+CSS高频面试题

面试题目录 前言1.讲一下盒模型&#xff0c;普通盒模型和怪异盒模型有什么区别2.CSS如何实现居中3.讲一下flex弹性盒布局4.CSS常见的选择器有哪些&#xff1f;优先级5.长度单位px 、em、rem的区别6.position属性的值有哪些7.display属性的值有哪些&#xff0c;分别有什么作用8.…

std::map

一 emplace() emplace_hint() try_emplace()区别 1. emplace template< class... Args >std::pair<iterator, bool> emplace( Args&&... args ); 若容器中没有拥有该键的元素&#xff0c;则向容器插入以给定的 args 原位构造的新元素。 细心地使用 em…

20231211-DISM++安装win10-22h2-oct

20231211-DISM安装win10-22h2-oct 一、软件环境 zh-cn_windows_10_consumer_editions_version_22h2_updated_oct_2023_x64_dvd_eb811ccc.isowepe x64 v2.3标签&#xff1a;win10 22h2 wepe dism分栏&#xff1a;WINDOWS 二、硬件环境 8G或以上的有PE功能的启动U盘一个台式机…

Python常用文件操作库详解与示例

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 文件操作是编程中常见的任务之一&#xff0c;而Python提供了丰富的文件操作库&#xff0c;使得文件的读取、写入、复制、移动等操作变得非常便捷。本文将深入介绍一些Python中常用的文件操作库&#xff0c;以及它…

原型图都可以用什么软件制作?推荐这9款

对于设计师来说&#xff0c;一个有用的原型设计工具可以大大提高他们的工作效率&#xff0c;节省很多时间。当然&#xff0c;不同的原型设计工具有一定的差异&#xff01;那么哪个原型设计工具更好呢&#xff1f;以下是一些有用的原型设计软件&#xff0c;有需要的朋友可以根据…

红队攻防实战之DEATHNOTE

难道向上攀爬的那条路&#xff0c;不是比站在顶峰更让人热血澎湃吗 渗透过程 获取ip 使用Kali中的arp-scan工具扫描探测 端口扫描 可以看到开放了22和80端口。 访问80端口&#xff0c;重定向到 修改hosts文件&#xff0c;将该域名解析到ip 如图 修改完再次访问&#xff0…

如何在pytest接口自动化框架中扩展JSON数据解析功能?

开篇 上期内容简单说到了。params类类型参数的解析方法。相较于简单。本期内容就json格式的数据解析&#xff0c;来进行阐述。 在MeterSphere中&#xff0c;有两种方式可以进行json格式的数据维护。一种是使用他们自带的JsonSchema来填写key-value表单。另一种就是手写json。…

总线一:I2C简介(介绍看这一篇就够啦)

本节主要介绍以下内容&#xff1a; I2C协议简介 STM32的I2C特性及架构 I2C初始化结构体详解 一、I2C协议简介 I2C 通讯协议(Inter&#xff0d;Integrated Circuit)是由Phiilps公司开发的&#xff0c;由于它引脚少&#xff0c;硬件实现简单&#xff0c;可扩展性强&#xff…

Java判断字符串是不是数字

描述&#xff1a;通过Java判断一个字符串&#xff0c;是不是数字。这里包括正数、负数、浮点数、科学计数法 代码&#xff1a; import java.util.regex.Pattern;public class Test {public static void main(String[] args) {System.out.println(isNumeric("12.23")…

数据结构二维数组计算题,以行为主?以列为主?

1.假设以行序为主序存储二维数组Aarray[1..100,1..100]&#xff0c;设每个数据元素占2个存储单元&#xff0c;基地址为10&#xff0c;则LOC[5,5]&#xff08; &#xff09;。 A&#xff0e;808 B&#xff0e;818 C&#xff0e;1010 D&…

【LeetCode-树】-- 109.有序链表转换二叉搜索树

109.有序链表转换二叉搜索树 方法&#xff1a;找到链表的中点&#xff0c;将其作为根节点 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNo…

python中import mysql.connector出错无模块,且是已经pip install mysql-connector情况下

已经安装了mysql-connector和mysql-connector-python&#xff0c;使用python连接数据库&#xff0c;导入import mysql.connector仍报错&#xff1a; import mysql.connector# Connect to server cnx mysql.connector.connect(host"127.0.0.1",port3306,user"a…

视频剪辑进阶指南:批量置入视频封面,增加视频吸引力

在视频剪辑的进阶阶段&#xff0c;除了掌握基本的剪辑技巧和特效处理&#xff0c;还要尝试一些创新的方法来增加视频的吸引力。批量置入视频封面就是一种有效的方式。通过置入吸引的封面&#xff0c;能吸引观众点击视频并提高观看量。下面详细介绍云炫AI智剪如何批量置入视频封…

pandas按行值筛选

之前都没有意识到这个问题&#xff0c;就是pandas取某一行的值的问题 测试代码如下 import pandas as pd import numpy as np df pd.DataFrame({A: foo bar foo bar foo bar foo foo.split(),B: one one two three two two one three.split(),C: np.arange(8), D: np.arange…

GO闭包实现原理(汇编级讲解)

go语言闭包实现原理(汇编层解析) 1.起因 今天开始学习go语言,在学到go闭包时候,原本以为go闭包的实现方式就是类似于如下cpp lambda value通过值传递,mutable修饰可以让value可以修改,但是地址不可能一样value通过引用传递,但是在其他地方调用时,这个value局部变量早就释放,…

数据结构第六课 -----排序

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

【Canvas】记录一次从0到1绘制风场空间分布图的过程

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热衷分享有趣实用的文章&#xff0c;希望大家多多支持&#xff0c;一起进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 背景 前置知识 风场数据 绘制风场 准备工作 生成二维网格 获取…

【BI】FineBI功能学习路径-20231211

FineBI功能学习路径 https://help.fanruan.com/finebi/doc-view-1757.html 编辑数据概述 1.1 调整数据结构 1.2 简化数据 2.1上下合并 2.2其他表添加列 2.3左右合并 新增分析指标 函数参考 https://help.fanruan.com/finereport/doc-view-1897.html 数值函数 日期函数 文…

【unity小技巧】FPS游戏后坐力制作思路

参考原视频链接 &#xff1a;https://www.bilibili.com/video/BV1j44y1S7fX/ 注意&#xff1a;本文为学习笔记记录&#xff0c;推荐支持原作者&#xff0c;去看原视频自己手敲代码理解更加深入 免责声明&#xff1a;向宇的博客免责声明 文章目录 前言不加后座力效果简单添加后座…