冒泡排序 快速排序 归并排序 其他排序

书接上回..

目录

2.3 交换排序

2.3.1冒泡排序

2.3.2 快速排序

快速排序的优化:

快速排序非递归

2.4 归并排序

基本思想

归并排序非递归

海量数据的排序问题

 排序算法时间空间复杂度和稳定性总结

四. 其他非基于比较排序 (了解)


2.3 交换排序

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

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

2.3.1冒泡排序

遍历数组, 将最大值移动到最后, 再次遍历, 将第二大值移动到倒数第二个位置, 以此类推

思路:

  1. 一共需要遍历arr.length-1趟, 才能将所有的元素变有序
  2. 第1趟在遍历时, 需要交换arr.length-1次, 第2趟在编历时, 需要交换arr.length-1-1次...
  3. 我们可以优化一下代码, 即只需要交换arr.length-1-i次即可, i从0开始
  4. 进一步优化, 如果一趟遍历下来, .没有任何的交换进行, 则说明数组已经有序, 则不需再遍历, 直接return即可
代码:
 public static void bubbleSort(int[] arr){
        for (int i = 0; i < arr.length-1; i++) {
            int flag = 0;
            for (int j = 0; j < arr.length-1-i; j++) {
                if(arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                    flag = 1;
                }
            }
            if(flag == 0){
                return;
            }
        }
    }

冒泡排序的特性总结

  • 冒泡排序是一种非常容易理解的排序
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定

2.3.2 快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

思路:

  1. 对[left,right]区间进行排序
  2. partition()方法的作用: 找到一个基准值pivot, 并将小于基准值的数都放在其左边, 大于基准值的数都放在其右边, 即将划分成以pivot边界的[left,pivot-1],[pivot+1,right]两部分
  3. 递归基准值的左边, 即[left,pivot-1]再次进行partition, 直到left>= right
  4. 递归基准值的右边, 即[pivot+1,right]再次进行partition, 直到left>= right
  5. 全部完成后数组就会变有序

代码:(非最终)

 public static void quickSort(int[] arr){
        quick(arr,0,arr.length-1);
    }
    private static void quick(int[] arr,int left,int right){
        if(left >= right){
            return ;
        }
        int pivot = partition(arr,left,right);
        quick(arr,left,pivot-1);
        quick(arr,pivot+1,right);
    }

将区间按照基准值划分为左右两半部分的常见方式有: 

1.  Hoare法

假设数组的第一个数就是基准值, 运用交换的思想

思路:

  1. 将left赋给pivot, 创建变量i = left; j = right,用来遍历数组
  2. i从前面遍历, 找到比arr[pivot]大的数, 停下来, j从后面遍历,找到比arr[pivot]小的数, 停下来, 交换这两个下标的值, 这样就把比arr[pivot]小的数放前面, 比arr[pivot]大的数放后面
  3. 继续遍历, 直到i>=j, 停止
  4. 此时交换i和pivot的值,返回i下标, 即基准值的下标

代码:

private static int partition(int[] arr,int left,int right){
        int i = left;
        int j = right;
        int pivot = left;
        while(i < j){
            while(i < j && arr[j] >= arr[pivot]){
                j--;
            }
            while(i < j && arr[i] <= arr[pivot]){
                i++;
            }
            swap(arr,i,j);
        }
        swap(arr,i,pivot);
        return i;
    }

思考:

1. 上述里层while循环, 判断条件一定是>=  <= 吗?不取等行不行?

答案: 不行, 因为如果不加等号, 左右两边都是相同的数字时, 进不去里层循环, 外层循环死循环

2. 以左边作为基准时, 为什么一定要从右边先开始找, 而不是左边?

答案: 如果从左边开始找, 那么左右相遇的地方可能是比基准值大的数字, 那么进行交换后, 就不满足基准值左边的数都小于右边, 反之, 如果从右边开始找, 那么左右相遇的地方一定是比基准值小的数字, 这时与左边的基准值进行交换, 小的数换到左边, 满足基准值左边的数都小于右边

2. 挖坑法

假设数组的第一个数就是基准值, 运用覆盖的思想

思路:

  1. 将数组第一个下标的值存起来给pivot,挖坑pivot的位置, 创建变量i = left; j = right,用来遍历数组
  2. i从前面遍历, 找到比arr[pivot]大的数, 停下来, 将arr[j]放在arr[i]的位置, 此时i = 0, 在arr[j]的位置挖坑,j从后面遍历,找到比arr[pivot]小的数, 停下来,将arr[i]放在arr[j]的位置,在arr[i]的位置挖坑
  3. 继续遍历, 直到i >= j, 停止
  4. 最后放pivot的值放在arr[i]这个坑中即可, 返回基准值所在的下标i

代码:

 private static int partition(int[] arr,int left,int right){
        int i = left;
        int j = right;
        int pivot = arr[left];
        while(i < j){
            while(i<j && arr[j] >= pivot){
                j--;
            }
            arr[i] = arr[j];
            while(i<j && arr[i] <= pivot){
                i++;
            }
            arr[j] = arr[i];
        }
        arr[i] = pivot;
        return i;
    }

3. 前后指针(了解)

private static int partition(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;
}
快速排序的优化:

1. 三数取中法选key

拿到left = 0, right = arr.length-1, 和mid为中间下标, 可以表示为mid = left + ((right-left)>>1), 比较这三个数的大小, 将中间大小的数字作为基准值, 这样可以保证基准值的左右两边都有数据, 减小时间复杂度

private static int middleNum(int[] arr,int left,int right){
        int mid = left + ((right-left)>>1);
        if (arr[left] < arr[right]){
            if(arr[mid] < arr[left]){
                return left;
            }else if(arr[mid] > arr[right]){
                return right;
            }else{
                return mid;
            }
        }else{
            if(arr[mid] < arr[right]){
                return right;
            }else if(arr[mid] > arr[left]){
                return left;
            }else{
                return mid;
            }

        }
    }

 private static void quick(int[] arr,int left,int right){
        if(left >= right){
            return ;
        }
        //优化1
        int index = middleNum(arr,left,right);
        swap(arr,index,left);

        int pivot = partition(arr,left,right);
        quick(arr,left,pivot-1);
        quick(arr,pivot+1,right);
    }

2. 递归到小的子区间时,可以考虑使用插入排序

当区间变小时, 再使用递归的方法进行排序, 会浪费时间, 所以当区间小于一个数(假设10)时, 就采用直接插入排序, 提高效率

 private static void quick(int[] arr,int left,int right){
        if(left >= right){
            return ;
        }
        //优化1
        int index = middleNum(arr,left,right);
        swap(arr,index,left);
        
        //优化2
        if(right-left+1 <= 10 ){
            insertSort2(arr,left,right);
            return;
        }

        int pivot = partition(arr,left,right);
        quick(arr,left,pivot-1);
        quick(arr,pivot+1,right);
    }
    public static void insertSort2(int[] arr,int left,int right){
        for (int i = 1 + left; i <=right ; i++) {
            int tmp = arr[i];
            int j = i-1;
            for ( ; j >= 0; j--) {
                if(arr[j] > tmp){
                    arr[j+1] = arr[j];
                }else{
                    //arr[j+1] = tmp;
                    break;
                }
            }
            arr[j+1] = tmp;
        }
    }
快速排序非递归

思路:

  1. 使用partition方法找到基准值的下标
  2. 判断如果基准值的左边不止有一个数据, 即pivot-1 > left, 则需要继续划分, 则将这一划分的左右坐标分别压入栈中, 注意在出栈时先右后左

代码:

public static void quickSortNorR(int[] arr){
        int left = 0;
        int right = arr.length-1;
        int pivot = partition(arr,left,right);
        Stack<Integer> stack = new Stack<>();
        if(pivot - 1 > left){
            stack.push(left);
            stack.push(pivot - 1);
        }
        if(pivot + 1 < right){
            stack.push(pivot+1);
            stack.push(right);
        }
        while(!stack.isEmpty()){
            right = stack.pop();
            left = stack.pop();
            pivot = partition(arr,left,right);
            if(pivot - 1 > left){
                stack.push(left);
                stack.push(pivot - 1);
            }
            if(pivot + 1 < right){
                stack.push(pivot+1);
                stack.push(right);
            }
        }
    }

快速排序总结

  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(logN)
  • 稳定性:不稳定
  • 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2.4 归并排序

基本思想

归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法( Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
思路:
  1. 归并排序分为两部分, 分解和合并, 分解我们运用到的思路是递归, 合并我运用到的思路是用新数组存储有序序列, 再赋值给原数组
  2. 分解: 找到数组的中间下标mid, 左下标left, 右下标right, 将数组分解成[left, mid] 和[mid+1,right], 递归分解, 直到只剩下一个元素
  3. 合并:将两组数据的头和尾分别定义成s1e1 和s2e2, s1和s2依次进行比较, 小的就存放在新数组里, 并++,直到有一个数组的s1 > s2, 结束比较, 直接将另一组数据剩下的加到新数组的后面, 最后将新数组赋值给原数组, 注意: 新数组和原数组的对应关系是:arr[i+left] = tmpArr[i]
代码:
  public static void mergeSort(int[] arr){
        mergeFunc(arr,0,arr.length-1);
    }
    private static void mergeFunc(int[] arr,int left,int right){
        if(left >= right){
            return ;
        }
        int mid = left+((right-left)>>1);
        mergeFunc(arr,left,mid);
        mergeFunc(arr,mid+1,right);

        merge(arr,left,mid,right);
    }
    private static void merge(int[] arr, int left,int mid,int right){
        int s1 = left;
        int s2 = mid +1;
        int e1 = mid;
        int e2 = right;
        int[] tmpArr = new int[right-left+1];
        int k = 0;
        while(s1 <= e1 && s2 <= e2){
            if(arr[s1] > arr[s2]){
                tmpArr[k++] = arr[s2++];
            }else{
                tmpArr[k++] = arr[s1++];
            }
        }
        while(s1 <= e1){
            tmpArr[k++] = arr[s1++];
        }
        while(s2 <= e2){
            tmpArr[k++] = arr[s2++];
        }

        for (int i = 0; i < k; i++) {
            arr[i+left] = tmpArr[i];

        }
    }

归并排序非递归

思路:

  1. 设gap, 表示几个元素为一半, 归并是将一组分成两半进行比较的,比较的事交给merge方法, 我们只需要知道怎么分组, 并找好下一组的位置, 每次gap*=2, 当gap >arr.length时, 说明已经全部有序了
  2. 用 i 来遍历数组找位置, i=0 时, 那么第一组的left就是0, mid就是left+gap-1, right就是mid+gap, 注意:为了防止mid和right越界, 当mid right>= arr.length时, 说明超过了数组的大小, 只需将他们设置成最后一个元素即可
  3. i在找下一组时, i应该等于i+2*gap, 因为一组的长度为2*gap

代码:
 

public static void mergeSortNorR(int[] arr){
        int gap = 1;
        while(gap < arr.length){
            for (int i = 0; i < arr.length; i=i+2*gap) {
                int left = i;
                int mid = left + gap -1;
                if(mid >= arr.length){
                    mid = arr.length-1;
                }
                int right = mid +gap;
                if(right >= arr.length){
                    right = arr.length-1;
                }
                merge(arr,left,mid,right);
            }
            gap *= 2;
        }
    }

归并排序总结

  • 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(N)
  • 稳定性:稳定

海量数据的排序问题

外部排序:排序过程需要在磁盘等外部存储进行的排序
前提:内存只有 1G ,需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序
1. 先把文件切分 200 份,每个 512 M
2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
3. 进行 2 路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

 排序算法时间空间复杂度和稳定性总结

四. 其他非基于比较排序 (了解)

1. 计数排序

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
操作步骤:
  1.  统计相同元素出现次数
  2.  根据统计的结果将序列回收到原来的序列中
计数排序的特性总结
  • 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  • 时间复杂度:O(MAX(N,范围))
  • 空间复杂度:O(范围)
  • 稳定性:稳定

2. 基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

步骤:

  1. 先比较个位数字, 依次放在有顺序的10个队列中, 分别代表数字0~9, 然后从第一个队列开始出队
  2. 再按照十位数字比较, 继续放入, 再出队, 循环最大数字的位数次结束

 3. 桶排序

思想:划分多个范围相同的区间,每个子区间自排序,最后合并。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GIS、CAD数据为基础进行城市排水系统水力建模方法

佳文推荐 城市内涝水文水动力模型介绍 在城市排水防涝规划过程中&#xff0c;水文水动力耦合模型已经成为一种不可或缺的分析工具。在模型建立、城市内涝风险评估、排水系统性能诊断以及海绵城市规划等方面&#xff0c;内涝耦合模型提供了相应的模拟及分析工具&#xff1a; …

C语言结构体之位段

位段&#xff08;节约内存&#xff09;&#xff0c;和王者段位联想记忆 位段是为了节约内存的。刚好和结构体相反。 那么什么是位段呢&#xff1f;我们现引入情景&#xff1a;我么如果要记录一个人是男是女&#xff0c;用数字0 1表示。我们发现只要一个bit内存就可以完成我们想…

Chrome浏览器修改网页内容

方法一&#xff1a;使用开发者工具 在Chrome浏览器中打开要修改的网页。按下F12键打开开发者工具。在开发者工具窗口中&#xff0c;找到“Elements”标签页。在“Elements”标签页中&#xff0c;找到要修改的网页元素。双击要修改的网页元素&#xff0c;即可进行编辑。 方法二…

C++ 之LeetCode刷题记录(四十)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅。 目标&#xff1a;执行用时击败90%以上使用 C 的用户。 27. 移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值…

【码云Git提交】Windows

一、第一次提交 1.登录码云创仓库 2.观察创建后的提示&#xff0c;就有步骤命令了 3.我们在系统中打开一个测试文件夹窗口打开GitBash PS&#xff1a;&#xff08;你需要提前装一个Node&#xff0c;本章不介绍&#xff09; 我们打开一个创建的test测试文件夹窗口&#xff0c;…

gif格式动图怎么制作?分享一种制作gif的方法

制作gif动图是非常有趣的&#xff0c;通过自制gif表情包能够丰富你的图片库&#xff0c;让你在社交平台上轻松的与朋友互动。那么&#xff0c;如何自己制作gif动画呢&#xff1f;很简单&#xff0c;通过使用gif图片制作&#xff08;https://www.gif.cn/&#xff09;工具-GIF中文…

【PLC】PROFIBUS(一):介绍

1、简介 PROFIBUS (Process Fieldbus)&#xff0c;德国SIEMENS和其它机构联合开发&#xff1b; 1999年&#xff0c;PROFIBUS成为国际工业现场总线协议标准IEC61158的组成部分&#xff1b; PROFIBUS 由三部分组成&#xff1a;PROFIBUS-DP、PROFIBUS-PA 和 PROFIBUS-FMS&#xf…

038—pandas 重采样线性插补

前言 在数据处理时&#xff0c;由于采集数据量有限&#xff0c;或者采集数据粒度过小&#xff0c;经常需要对数据重采样。在本例中&#xff0c;我们将实现一个类型超分辨率的操作。 思路&#xff1a; 首先将原始数据长度扩展为 3 倍&#xff0c;可以使用 loc[] 方法对索引扩…

Power Query 中转换时区

当我们的数据库在国内&#xff0c;使用报表的是国外的人时&#xff0c;通常数据库的刷新时间都会设置为UTC&#xff0c;这时我们就学院根据不同国家的时区来设置相对应的时间。我们就要用到时区的转换。 具体的步骤如下&#xff1a; 1&#xff0c;把时间转换为UTC的时区 添加…

眼观百遍,不如手敲一遍

眼观百遍&#xff0c;不如手敲一遍 Repetitive Viewing Cannot Surpass Hands-on Typing 在现代教育体系中&#xff0c;编程已成为一项基础而关键的技能。伴随着各种便捷的工具和在线资源的普及&#xff0c;获取并复制代码变得前所未有地容易。然而&#xff0c;在这种趋势下&am…

【MD】激光驱动原子动力学的全尺寸从头算模拟

Zeng Q, Chen B, Zhang S, et al. Full-scale ab initio simulations of laser-driven atomistic dynamics[J]. npj Computational Materials, 2023, 9(1): 213.核心研究内容&#xff1a; 本文研究了激光驱动的原子动力学的全尺度从头算模拟。研究的重点是探讨在极端条件下材料…

使用Docker本地搭建蚂蚁笔记并实现无公网IP远程访问

文章目录 1. 安装Docker2. Docker本地部署Leanote蚂蚁笔记3. 安装cpolar内网穿透4. 固定Leanote蚂蚁笔记公网地址 本篇文章介绍如何使用Docker部署Leanote蚂蚁笔记&#xff0c;并且结合cpolar内网穿透实现公网远程访问本地笔记编辑并制作个人博客等。 Leanote 蚂蚁笔记是一款云…

力扣_203_移除链表元素(c语言)

解题方法&#xff1a; struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode* newhead,*newtail;newheadnewtailNULL;struct ListNode*pcurhead;while(pcur){if(pcur->val!val){if(newheadNULL)newheadnewtailpcur;else{newtail->nextpcu…

【Pt】马灯贴图绘制过程 01-制作基础色

目录 一、导入模型并烘焙 二、制作基础底漆 &#xff08;1&#xff09;底漆层 &#xff08;2&#xff09;水痕层 &#xff08;3&#xff09;指纹层 一、导入模型并烘焙 1. 导入模型&#xff0c;马灯模型如下所示 2. 在纹理集设置中点击“烘焙模型贴图” 设置输出大小为…

Kali开启远程服务

一&#xff0c;先切换root账户 二、kali开启远程服务 1&#xff0c;修改远程登录的配置文件 vim /etc/ssh/sshd_config &#xff08;用文本编辑器打开此文件) 在文件的普通模式下&#xff0c;使用/PermitRootLogin&#xff0c;回车&#xff0c;查找到该行&#xff0c;i&#…

海外媒体软文发稿:谷歌关键词优化细分人群成功案例,突破海外市场!

海外媒体软文发稿&#xff1a;谷歌关键词优化细分人群成功案例&#xff0c;突破海外市场&#xff01; 引言 在全球化的时代&#xff0c;海外市场对于企业的发展至关重要。而在海外市场中&#xff0c;互联网媒体的作用不可忽视。本篇教程将介绍如何通过谷歌关键词优化细分人群…

视频素材app有哪些?视频素材网址推荐

在这个视觉传达愈发重要的时代&#xff0c;拥有一款好的无水印短视频素材网站就如同握有一把打开创意之门的钥匙&#xff0c;选择合适的短视频素材平台至关重要&#xff0c;这会让你的视频制作更加轻松而高效。 1&#xff0c;蛙学府 以其广泛的优质视频素材库而闻名&#xff0…

桉木芯清水模板,大显身手之作 — 能强优品木业助力工程建设

在现代建筑施工中,清水建筑模板的选择对于工程质量和效率至关重要。贵港市能强优品木业有限公司凭借25年的专业经验,成为了广西清水建筑模板领域的佼佼者。公司生产的桉木芯清水模板材质优良、镜面效果出众,尤其适合大型工程施工。 该公司的清水建筑模板采用优质桉木作为芯材,木…

记录些LangChain相关的知识

RAG的输出准确率 RAG的输出准确率 向量信息保留率 * 语义搜索准确率 * LLM准确率RAG的输出准确率由三个因素共同决定&#xff1a;向量信息保留率、语义搜索准确率以及LLM准确率。这三个因素是依次作用的&#xff0c;因此准确率实际上是它们的乘积。这意味着&#xff0c;任何一…

小学科学期刊投稿邮箱论文发表

《小学科学》是由国家新闻出版总署批准的教育理论类半月刊&#xff0c;由长春出版传媒集团有限责任公司主管主办&#xff0c;旨在为广大一线科学教师、教研员和其他教育工作者提供一个展示传播、交流、研讨科学教育及教研成果的平台&#xff0c;促进小学科学教育工作者的沟通与…