【算法刷题】八大排序算法总结(冒泡、选择、插入、二分插入、归并、快速、希尔、堆排序)

news2024/11/28 12:35:51

在这里插入图片描述

文章目录

  • 八大排序算法总结
  • 1.冒泡排序
  • 2.选择排序
  • 3.插入排序
  • 4.二分插入排序
  • 5.归并排序
  • 6.快速排序
  • 7.希尔排序
  • 8.堆排序

八大排序算法总结

排序排序方法平均情况最好情况最坏情况空间稳定性
1冒泡排序O(n2)O(n)O(n2)O(1)稳定
2选择排序O(n2)O(n2)O(n2)O(1)不稳定
3插入排序O(n2)O(n)O(n2)O(1)稳定
4二分插入排序O(n2)O(n)O(n2)O(1)稳定
5归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
6快速排序O(nlogn)O(nlogn)O(n2)O(logn)~O(n)不稳定
7希尔排序O(nlogn) ~ O(n2)O(n1.3)O(n2)O(1)不稳定
8堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定

1.冒泡排序

  1. 核心思想: 通过相邻元素的比较和交换来将最大(或最小)的元素逐渐“冒泡”到数组的一端

    动图

  2. 具体步骤:

    1. 从数组的第一个元素开始,依次比较相邻的两个元素。
    2. 如果前一个元素大于后一个元素,则交换它们的位置,使得较大的元素“冒泡”到后面
    3. 继续进行相邻元素的比较和交换,直到数组的末尾
    4. 重复执行上述步骤,每次都会将当前未排序部分的最大元素“冒泡”到数组的末尾
    5. 重复执行上述步骤,直到整个数组排序完成。
  3. 实现代码:

    public static void bubbleSort(int[] arr) {
        int temp = 0;
        for (int i = arr.length - 1; i > 0; i--) { 	// 每次需要排序的长度
            for (int j = 0; j < i; j++) { 			// 从第一个元素到第i个元素
                if (arr[j] > arr[j + 1]) {			// 确保较大的元素排到后面
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
    
  4. 时间和空间复杂度:

    • 平均情况:O(n2)
    • 最好情况:O(n)
    • 最坏情况:O(n2)
    • 空间复杂度:O(1)
    • 稳定性:稳定
  5. 优化: 增加标记符swap,用于确定当前一轮冒泡是否有元素进行交换,若没有则跳出循环,表示数组已经有序了

    public static void bubbleSort(int[] arr) {
        int temp = 0;
        boolean swap;
        for (int i = arr.length - 1; i > 0; i--) { // 每次需要排序的长度
            swap=false;
            for (int j = 0; j < i; j++) { // 从第一个元素到第i个元素
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    swap=true;
                }
            }//loop j
            if (swap==false){			//前面的一轮循环没有进行交换,数组已经有序
                break;
            }
        }//loop i
    }// method bubbleSort
    

2.选择排序

  1. 核心思想: 在未排序部分的数据中选择最小(或最大)的元素,然后将其放置到已排序部分的末尾

  2. 具体步骤:

    1. 遍历数组,将第一个元素作为当前最小(或最大)元素。
    2. 在未排序部分的剩余元素中,找到最小(或最大)的元素,并记录其位置。
    3. 将找到的最小(或最大)元素与未排序部分的第一个元素交换位置,将其放置到已排序部分的末尾。
    4. 重复执行上述步骤,每次都会将当前未排序部分的最小(或最大)元素放置到已排序部分的末尾。
    5. 这样,经过n-1轮的遍历和交换,整个数组就会按照升序(或降序)排列。
  3. 实现代码:

    public static void selectionSort(int[] arr) {
        int temp, min = 0;
        for (int i = 0; i < arr.length - 1; i++) {
            min = i;
            // 循环查找最小值
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[min] > arr[j]) {
                    min = j;
                }
            }
            if (min != i) {
                temp = arr[i];
                arr[i] = arr[min];
                arr[min] = temp;
            }
        }
    }
    
  4. 时间和空间复杂度:

    • 平均情况:O(n2)
    • 最好情况:O(n2)
    • 最坏情况:O(n2)
    • 空间复杂度:O(1)
    • 稳定性:不稳定
  5. 关于稳定性的探讨:

    • 稳定性:在排序过程中,相等元素的相对顺序是否会被改变。如果排序算法能够保持相等元素的相对顺序不变,则称其为稳定的排序算法;反之,则称其为不稳定的排序算法

    • 冒泡排序(升序):当相邻元素进行比较并需要交换位置时,只有当后面的元素小于前面的元素才会进行交换。因此,对于相等的元素,即使它们相邻,它们的相对顺序也不会被改变,从而保持了排序的稳定性

    • 选择排序:每次选择最小的元素并放置到已排序部分的末尾。在寻找最小(或最大)元素的过程中,可能会导致相等元素的相对顺序发生改变。例如,在一组相等的元素中,由于选择排序每次只选择一个最小(或最大)元素,所以在选择的过程中可能会交换这些相等元素的位置,从而导致排序的不稳定性

    • 举例:假设我们有以下一组待排序的元素:

      [5①, 2, 7, 5②, 1]
      
      • 冒泡排序:比较相邻的元素,并根据需要交换它们的位置。当遇到相等元素时,只有在后面的元素小于前面的元素时才会进行交换。

        第一轮冒泡排序:[2, 5①, 5②, 1, 7]
        第二轮冒泡排序:[2, 5, 1, 5, 7]
        第三轮冒泡排序:[2, 1, 5, 5, 7]
        第四轮冒泡排序:[1, 2, 5, 5, 7]
        
      • 选择排序:

        第一轮选择排序:[1, 2, 7, 5②, 5①](选择1) (两个相等的5的位置发生变化,不稳定!!!)
        第二轮选择排序:[1, 2, 5, 5, 7](选择2)
        第三轮选择排序:[1, 2, 5, 5, 7](选择5)
        第四轮选择排序:[1, 2, 5, 5, 7](选择5)
        第五轮选择排序:[1, 2, 5, 5, 7](选择7)
        

3.插入排序

  1. 核心思想:待排序的元素逐个插入到已排序部分的正确位置

    动图

  2. 具体步骤:

    1. 将数组分为已排序部分和未排序部分。一开始,已排序部分只包含第一个元素,即数组的第一个元素被认为是已排序的。
    2. 从未排序部分取出一个元素,将其插入到已排序部分的正确位置。为了找到正确的位置,可以从已排序部分的末尾开始,依次向前比较已排序元素,直到找到小于等于该元素的位置。
    3. 将该元素插入到找到的位置,并将已排序部分中的元素向后移动,为新元素腾出位置。
    4. 重复步骤2和步骤3,直到未排序部分为空。
    5. 最终,已排序部分即为排序好的数组。
  3. 实现代码:

    public static void insertionSort(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; i++) {		//从第二个元素开始,第一个元素默认有序
        	int key = arr[i];				//保存当前元素 key
            int j = i - 1;
    		
            //找到合适的位置
            while (j >= 0 && arr[j] > key) {//发现已排序元素比 key 大,则将该元素向后移动一位,直到找到 key 的正确位置
            	arr[j + 1] = arr[j];
    			j = j - 1;					//往后移
            }	
            arr[j + 1] = key;1				//找到 key 保存的位置
       	}
    }
    
    待排序数组:[5, 2, 4, 6, 1, 3]
    1:[2, 5, 4, 6, 1, 3]
    2:[2, 4, 5, 6, 1, 3]
    3:[1, 2, 4, 5, 6, 3]
    4:[1, 2, 3, 4, 5, 6]
    
  4. 时间和空间复杂度:

    • 平均情况:O(n2)
    • 最好情况:O(n)
    • 最坏情况:O(n2)
    • 空间复杂度:O(1)
    • 稳定性:稳定

4.二分插入排序

  1. 核心思想:利用二分查找来确定待插入元素在已排序部分中的位置,以减少比较次数

  2. 实现步骤:

    1. 初始状态:将数组的第一个元素视为已排序部分,其余元素视为待排序部分。
    2. 插入过程:从第二个元素开始遍历待排序部分,对于每个元素,使用二分查找确定其在已排序部分中的插入位置。
    3. 二分查找:在已排序部分中,使用二分查找找到第一个大于待插入元素的位置,这个位置及之后的元素都需要向后移动一个位置来腾出空间。
    4. 插入操作:将待插入元素插入到找到的位置,完成一轮插入操作。
    5. 重复步骤2~4,直到待排序部分中的所有元素都被插入到已排序部分中,整个数组就被排序完成了。
  3. 实现代码:

    public static void binaryInsertionSort(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; i++) {
            int key = arr[i];
            int left = 0;
            int right = i - 1;
    
            // 二分查找确定插入位置
            int insertIndex = binarySearch(arr, left, right, key);
    
            // 将大于 key 的元素向后移动一位
            for (int j = i - 1; j >= insertIndex; j--) {
                arr[j + 1] = arr[j];
            }
    
            // 插入 key
            arr[insertIndex] = key;
        }
    }
    
    // 二分查找
    private static int binarySearch(int[] arr, int left, int right, int key) {
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] == key) {
                return mid; // key 在已排序部分的位置
            } else if (arr[mid] < key) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return left; // 返回待插入位置
    }
    
  4. 时间和空间复杂度:

    • 平均情况:O(n2)
    • 最好情况:O(n)
    • 最坏情况:O(n2)
    • 空间复杂度:O(1)
    • 稳定性:稳定

5.归并排序

  1. 核心思想: 采用分治法,将已有序的子序列合并,得到完全有序的序列;

  2. 步骤:

    1. 分解(Divide):将原始数组分解为较小的子数组,直到每个子数组只有一个元素为止。这可以通过递归的方式实现,将数组不断二分直到每个子数组的长度为1。
    2. 解决(Conquer):对每个子数组进行排序。对于只有一个元素的子数组来说,可以认为它们已经是有序的。
    3. 合并(Merge):合并相邻的子数组,形成一个更大的有序数组。合并过程中,需要按照大小顺序逐个比较两个子数组中的元素,并将较小(或较大)的元素依次放入一个临时数组中,直到合并完成。
    4. 递归合并(Recursively Merge):重复以上步骤,直到所有子数组都被合并成一个大的有序数组为止。

    动图

  3. 实现代码:

    public static void mergeSort(int[] arr){
        int[] temp =new int[arr.length];
        internalMergeSort(arr, temp, 0, arr.length-1);
    }
    private static void internalMergeSort(int[] arr, int[] temp, int left, int right){
        //当left==right的时,已经不需要再划分了
        if (left<right){
            int middle = (left+right)/2;
            internalMergeSort(arr, temp, left, middle);          //左子数组
            internalMergeSort(arr, temp, middle+1, right);       //右子数组
            mergeSortedArray(arr, temp, left, middle, right);    //合并两个子数组
        }
    }
    // 合并两个有序子序列
    private static void mergeSortedArray(int arr[], int temp[], int left, int middle, int right){
        int i=left;      
        int j=middle+1;
        int k=0;
        while (i<=middle && j<=right){
            temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }
        while (i <=middle){
            temp[k++] = arr[i++];
        }
        while ( j<=right){
            temp[k++] = arr[j++];
        }
        //把数据复制回原数组
        for (i=0; i<k; ++i){
            arr[left+i] = temp[i];
        }
    }
    
  4. 时间和空间复杂度:

    • 平均情况:O(nlogn)
    • 最好情况:O(nlogn)
    • 最坏情况:O(nlogn)
    • 空间复杂度:O(n)
    • 稳定性:稳定
  5. 适用场景: 归并排序在数据量比较大的时候也有较为出色的表现(效率上),但是,其空间复杂度O(n)使得在数据量特别大的时候(例如,1千万数据)几乎不可接受。而且,考虑到有的机器内存本身就比较小,因此,采用归并排序一定要注意。

6.快速排序

  1. 核心思想: 通过选取基准元素,将数组划分为两个子数组,并对子数组递归排序,实现整个数组的快速排序

  2. 实现步骤:

    1. 从数列中挑出一个元素,称为 “基准”(pivot),
    2. 重新排序数列,所有比基准值小的元素摆放在基准前面所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
    3. 递归地(recursively)把小于基准值元素的子数列大于基准值元素的子数列排序。
  3. 实现代码:

    public static void quickSort(int[] arr){
        qsort(arr, 0, arr.length-1);
    }
    
    private static void qsort(int[] arr, int low, int high){
        if (low >= high)
            return;
        int pivot = partition(arr, low, high);        //将数组分为两部分
        qsort(arr, low, pivot-1);                   //递归排序左子数组
        qsort(arr, pivot+1, high);                  //递归排序右子数组
    }
    
    private static int partition(int[] arr, int low, int high){
        int pivot = arr[low];     //基准
        while (low < high){
            
            while (low < high && arr[high] >= pivot){
                --high;
            } 
            
            arr[low]=arr[high];             //比基准小的元素会被移动到基准的左边
            
            while (low < high && arr[low] <= pivot){
                ++low;
            }
            
            arr[high] = arr[low];           //比基准大的元素会被移动到基准的右边
        }
        //扫描完成,基准到位
        arr[low] = pivot;
        //返回的是基准的位置
        return low;
    }
    
  4. 举例:

    初始:[7, 2, 1, 6, 8, 5, 3, 4]	# 7为基准
    	 
    第一:[4, 2, 1, 6, 8, 5, 3, 4] # 4移到左边
    	 [4, 2, 1, 6, 8, 5, 3, 8] # 8移到右边
    	 [4, 2, 1, 6, 3, 5, 3, 8] # 3移到左边
    	 [4, 2, 1, 6, 3, 5, 7, 8] # 基准7插入到low位置
    	 
    划分为两个数组:[4, 2, 1, 6, 3, 5]和[8]
    第二: [4, 2, 1, 6, 3, 5] # 以4为基准
    	  [3, 2, 1, 6, 3, 5] # 3移到左边
    	  [3, 2, 1, 6, 6, 5] # 6移到右边
    	  [3, 2, 1, 4, 6, 5] # 基准4插入到low位置
    	 
    以此类推......
    
  5. 时间和空间复杂度:

    • 平均情况:O(nlogn)
    • 最好情况:O(nlogn)
    • 最坏情况:O(n2)
    • 空间复杂度:O(logn)~O(n)
    • 稳定性:不稳定

7.希尔排序

  • 明天总结

8.堆排序

  • 明天总结

在这里插入图片描述

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

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

相关文章

生活中的数学 --- 等额本息贷款和等额本金贷款的月供应该怎么算?

等额本息贷款和等额本金贷款的月供应该怎么算&#xff1f; 从一个例子开始&#xff0c;假设我要从银行贷款36万(即&#xff0c;本金)&#xff0c;银行给出的贷款年利率是12%(月利率为年利率除以12)&#xff0c;贷款半年(6个月)&#xff0c;按月还款&#xff0c;分6期还完。 问分…

go websocket

WebSocket 是一种网络协议&#xff0c;建立在 HTTP 协议之上&#xff0c;允许双向通信。WebSocket 协议允许服务器发送数据到客户端&#xff0c;同时也可以让客户端向服务器发送数据。WebSocket 使用 HTTP 协议的升级请求和响应来建立连接。WebSocket 的主要优点在于它可以通过…

【Node.js】短链接

原文链接&#xff1a;Nodejs 第六十二章&#xff08;短链接&#xff09; - 掘金 (juejin.cn) 短链接是一种缩短长网址的方法&#xff0c;将原始的长网址转换为更短的形式。短链接的主要用途之一是在社交媒体平台进行链接分享。由于这些平台对字符数量有限制&#xff0c;长网址可…

Lua热更新(AssetBundle)

AssetBundle 新版本导入ab包报错,则删除其中的Tests文件夹。 给资源分组 打包设置:平台、路径、重复打包清空文件夹、复制到streaming文件夹 建议勾选 建议使用LZ4压缩方式 用来观察文件中的包大小,不常用 参数总结: 这六个只做了解,重要的是上面的

kubesphere部署(apple m1 m2 m3)

背景&#xff1a;使用一个命令kk(KubeKey)同时快速安装 Kubernetes 和 KubeSphere的集成环境&#xff0c;提高效率&#xff0c;减少部署时所花费的精力。这里环境为apple m2 一、KubeSphere简介 KubeSphere 是在 Kubernetes 之上构建的面向云原生应用的分布式操作系统&#x…

YOLOv7全网独家改进: 卷积魔改 | 变形条状卷积,魔改DCNv3二次创新

💡💡💡本文独家改进: 变形条状卷积,DCNv3改进版本,不降低精度的前提下相比较DCNv3大幅度运算速度 💡💡💡强烈推荐:先到先得,paper级创新,直接使用; 💡💡💡创新点:1)去掉DCNv3中的Mask;2)空间域上的双线性插值转改为轴上的线性插值; 💡💡💡…

人工智能——深度学习

4. 深度学习 4.1. 概念 深度学习是一种机器学习的分支&#xff0c;旨在通过构建和训练多层神经网络模型来实现数据的高级特征表达和复杂模式识别。与传统机器学习算法相比&#xff0c;深度学习具有以下特点&#xff1a; 多层表示学习&#xff1a;深度学习使用深层神经网络&a…

智过网:报考中级注册安全工程师需要什么条件?

随着社会的快速发展和科技的日新月异&#xff0c;安全生产问题越来越受到人们的关注。中级注册安全工程师作为专业安全管理人才&#xff0c;其职责与角色日益凸显。那么&#xff0c;想要报考中级注册安全工程师&#xff0c;需要满足哪些条件呢&#xff1f; 首先&#xff0c;报考…

lanqiao.125卡片换位(2016年蓝桥杯C/C++省赛C组)

题目&#xff1a; 语法点&#xff1a; 1. unordered_map<string,int> dist; //存储图的不同状态及不同状态对应的步数 2. unordered_map的相关操作&#xff0c;详细见C中的unordered_map用法详解-CSDN博客 dist.count(x) //来寻找x出现的次数 dist.find(x) //来…

STM32学习和实践笔记(6):自己进行时钟配置的思路

在《STM32学习和实践笔记&#xff08;4&#xff09;: 分析和理解GPIO_InitTypeDef GPIO_InitStructure (d)-CSDN博客》 中&#xff0c;我了解到&#xff0c;在程序执行我们写的main函数之前&#xff0c;实际上先执行了一个汇编语言所写的启动文件&#xff0c;以完成相应的初始…

51单片机学习笔记15 LCD12864(带字库)显示屏使用

51单片机学习笔记15 LCD12864&#xff08;带字库&#xff09;显示屏使用 一、LCD12864简介二、管脚定义三、命令1. 功能能设定2. 清屏指令&#xff08;0x01&#xff09;3. 地址归位4. 进入设定点5. 显示状态开关6. 设定CGRAM地址7. 设定DDRAM地址8. 写资料到RAM9. 读出RAM 四、…

Flink WordCount实践

目录 前提条件 基本准备 批处理API实现WordCount 流处理API实现WordCount 数据源是文件 数据源是socket文本流 打包 提交到集群运行 命令行提交作业 Web UI提交作业 上传代码到gitee 前提条件 Windows安装好jdk8、Maven3、IDEA Linux安装好Flink集群&#xff0c;可…

37-代码测试(下):Go语言其他测试类型及IAM测试介绍

。 Go中的两类测试&#xff1a;单元测试和性能测试。 我就来介绍下Go 语言中的其他测试类型&#xff1a;示例测试、TestMain函数、Mock测试、Fake测试等&#xff0c; 示例测试 示例测试以Example开头&#xff0c;没有输入和返回参数&#xff0c;通常保存在example_test.go…

Verilog实现手表计时

实现手表的计时功能&#xff1a; 1.具有start启动信号、pause暂停信号&#xff0c;可以自定义其触发机制。 2.具有时间更改接口&#xff0c;可以更改时、分、秒。 3.输出时、分、秒。 Verilog设计 模块端口定义&#xff1a; module watch1(input wire clk …

mp4转flv怎么转?电脑怎么把视频转成flv?

MP4&#xff08;MPEG-4 Part 14&#xff09;是一种多媒体容器格式&#xff0c;广泛用于包含视频、音频、字幕等多种数据流。MP4因其高度灵活性、压缩效率和兼容性成为视频领域的主流格式&#xff0c;支持范围涵盖从在线视频到移动设备的各类应用场景。 FLV文件格式的多个优点 …

Linux虚拟内存简介

Linux&#xff0c;像多数现代内核一样&#xff0c;采用了虚拟内存管理技术。该技术利用了大多数程序的一个典型特征&#xff0c;即访问局部性&#xff08;locality of reference&#xff09;&#xff0c;以求高效使用CPU和RAM&#xff08;物理内存&#xff09;资源。大多数程序…

使用MongoDB 构建AI:轻松应对从预测式AI到生成式AI

毫无疑问&#xff0c;如今从生成式AI (GenAI )中获益最大的&#xff0c;是那些早已运用预测式AI (Predictive AI )的组织。 2023年6月&#xff0c;麦肯锡在2023年6月发布的《生成式人工智能的经济潜力》研究中也得出了与此相同的结论。 原因主要有以下几点&#xff1a; 内部文…

SCI一区 | Matlab实现OOA-TCN-BiGRU-Attention鱼鹰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现OOA-TCN-BiGRU-Attention鱼鹰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现OOA-TCN-BiGRU-Attention鱼鹰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述程序…

HarmonyOS 开发-MpChart运动健康场景实践案例

介绍 MpChart是一个包含各种类型图表的图表库&#xff0c;主要用于业务数据汇总&#xff0c;例如销售数据走势图&#xff0c;股价走势图等场景中使用&#xff0c;方便开发者快速实现图表UI&#xff0c;MpChart主要包括线形图、柱状图、饼状图、蜡烛图、气泡图、雷达图、瀑布图…

218基于matlab的有限差分法求解泊松方程

基于matlab的有限差分法求解泊松方程&#xff0c;采用SOR超松弛迭代法。模型采用方形区域&#xff0c;划分网格数为100*100&#xff0c;网格数可以很方便的更改。程序已调通&#xff0c;可直接运行。 218有限差分法 泊松方程 SOR超松弛迭代法 - 小红书 (xiaohongshu.com)