基础排序算法(冒泡、选择、插入)

本文详细介绍了冒泡排序、选择排序和插入排序三种基本排序算法的工作原理,同时阐述了时间复杂度和空间复杂度的概念,指出这些算法的时间复杂度均为O(n^2),而空间复杂度均为O(1),并讨论了排序算法的稳定性。
摘要由CSDN通过智能技术生成

一、前言

本篇文章主要对冒泡排序、选择排序、插入排序三个基本的排序算法进行理解分析并简单讲解时间复杂度与空间复杂度的概念,且尽量做到解释通俗易懂,使读者能够产生自身对排序逻辑的理解。

二、时间复杂度与空间复杂度

为什么要学会分析时间复杂度和空间复杂度?
分析算法的时间复杂度和空间复杂度是为了评估算法的效率和性能。它们是衡量算法执行时间和所需空间资源的两个重要指标。我们可以通过比较不同算法的时间、空间复杂度来选择最优的算法来解决问题,同时也是我们对一个算法是否进行了合理有效优化的重要判断标准。

时间复杂度

时间复杂度是指算法执行所需的时间量度。分析一个算法的时间复杂度,本质上是将核心操作次数和输入规模关联起来。
对算法函数进行分析时应该遵循一定规则:算法函数中的常数可以忽略、算法函数中的最高次幂的常数因子可以忽略、算法函数中最高次幂越小算法效率越高。
算法时间复杂度的真正表示方法——大O记法:算法的时间复杂度就是算法的量度,T(n) = O(f(n)),执行次数=执行时间,f(n)是问题规模n的某个函数。
推导大O表示法有以下几个规则:用常数1取代运行时间中所有加法常数、在修改后的运行次数中只保留高阶项、若高阶项存在且常数因子不为1则去除与这个项相乘的常数。
常见时间复杂度从低到高:O(1)、O(logn)、O(nlogn)、O(n^2)

空间复杂度

空间复杂度是指算法在执行过程中所需内存空间的量度,它告诉我们算法所需的额外空间随着输入规模的增加而增长的趋势。通过分析空间复杂度,我们可以评估算法在内存使用方面的要求,避免出现内存溢出等问题,并选择适合的算法来保证系统的稳定性和性能。简单来说,算法的空间复杂度可以用来描述算法对内存的占用。
分析算法的空间复杂度需要我们了解Java中常见的内存占用(计算机访问内存的方式都是一次1个字节),如:基本数据类型对内存的占用、一个引用变量(机器地址)需要8个字节表示、创建一个对象时需要使用16个字节来保存对象的头信息、一般内存使用如果不够8个字节都会自动填充为8个字节(比如Java中数组也是被限定为对象,其需要使用16个字节保存头信息、4个字节用于保存数组长度信息,因此还会自动填充4个字节的长度)。

三、基础排序

以下算法默认从小到大排序

冒泡排序

冒泡排序的实现原理较为简单,它通过反复遍历需要进行排序的元素数组,每一次遍历都会找出当前还未完成排序的元素中的最大值(从小到大排序),并将找到的最大值,将其“上浮”到元素队列顶部,当未排序的元素只剩下一个时排序便完成了。精髓就是每次遍历时找到当前还未完成排序的元素中的最大值。
过程图示
在这里插入图片描述
算法实现

public class BubblingSort {
    public static void main(String[] args) {

        int[] arr = {33, 3, 12, 44, 3, 22, 100, 50};
        
        for (int i = arr.length - 1; i > 0; i--) { //控制遍历次数
            for (int j = 0; j < i; j++) { //控制每次遍历时的比较次数
                if (arr[j] > arr[j + 1]) { //从小到大
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

冒泡排序的时间复杂度
一般情况下我们应该计算一个算法在最坏情况下的时间复杂度,在排序算法中就是将一个逆序的元素列排序为正序的元素列。从算法实现中我们可以看出,在核心的逻辑中,若有n个元素则会执行n-1次遍历操作,且每次遍历操作都会进行n-1-j次元素之间的比较,由此我们可以通过计算得到总的比较和交换次数为(n-1) + (n-2) + … + 2 + 1,即n(n-1)/2次,根据大O表示法的规则得到其时间复杂度为O(n^2)。
冒泡排序的空间复杂度
从算法实现中我们可以看出,在冒泡排序算法中,只需要创建一个额外的变量来进行元素交换,不需要额外的数据结构或数组来存储待排序元素。因此,无论待排序元素的数量是多少,冒泡排序始终只需要固定的常数级空间,其空间复杂度就为O(1)。

选择排序

选择排序的实现原理与冒泡排序较为类似,同样是通过反复遍历未排序的元素数组,但每次取出的最小(或最大)元素是放在已排序元素列的末尾。一种较为简单的方式就是从0下标开始,依次为每个位置找出当前未排序元素中的最小(从小到大)元素。
过程图示
在这里插入图片描述
算法实现

public class SelectSort {
    public static void main(String[] args) {

        int[] arr = {33, 3, 12, 44, 3, 22, 100, 50};

        for (int i = 0; i < arr.length - 1; i++) { //控制遍历的次数,且i表示每次遍历结束后找出的元素的存放位置
            for (int j = i + 1; j < arr.length; j++) { //控制每次遍历的次数
                if (arr[i] > arr[j]) {
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

选择排序的时间复杂度
同冒泡排序一样,当待排序数组的元素个数为n时,选择排序的外层循环会执行n-1次,外层循环每执行一次,内存循环会进行n-1-i次比较,根据计算以及大O表示法的规则得到其时间复杂度为O(n^2)。
选择排序的空间复杂度
同冒泡排序一样,从算法实现中我们可以看出,在选择排序算法中,只需要创建一个额外的变量来进行元素交换,不需要额外的数据结构或数组来存储待排序元素。因此,无论待排序元素的数量是多少,选择排序排序始终只需要固定的常数级空间,其空间复杂度就为O(1)。

插入排序

插入排序相比与冒泡排序和选择排序来讲,其实现原理更加有意思。其原理是将每个未排序元素依次插入已排序元素列中的合适位置,我们可以将待排序元素的首个元素视为已排序元素,这很好理解,因为首个元素只有一个,一个单独的元素当然可以视为已排序好的元素列,然后我们通过不断的遍历比较最终完成排序。一句话讲,插排每次是从有序列的末尾开始遍历,将未排序元素与已经排序的有序列依次进行进行比较排序。(就跟我们平时打牌时一样排好扑克牌)
上面所说的将元素放入已排元素列的合适位置是这样理解的,若我们是从小到大排序,那么已排序元素列应是已经正确按照从大到小的顺序排列的,因此我们每次将一个未排序元素与已排序元素列中的元素依次进行比较时,都能找出一个位置使得该未排序元素的左侧全是小于它的元素或不存在元素(该元素移动到了首位)、右侧全是大于它的元素或不存在元素(该元素作为了已排序元素的最后一个元素)
过程图示
在这里插入图片描述
算法实现

public class InsertSort {
    public static void main(String[] args) {

        int[] arr = {33, 3, 12, 44, 3, 22, 100, 50};

        for (int i = 1; i < arr.length; i++) { //控制遍历的次数,i从1开始,因为下标为0的元素视为初始时的已排序元素列
            for (int j = i; j > 0; j--) { //控制每次未排序元素的比较次数
                if (arr[j - 1] > arr[j]) {
                    int temp = arr[j - 1];
                    arr[j - 1] = arr[j];
                    arr[j] = temp;
                } else { //当元素在已排序元素中间找到合适位置时直接结束本次循环
                    break;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

插入排序的时间复杂度
插入排序在最坏的情况下是每个未排序元素与已排序元素列进行比较时都是比较完整个数列的。插排的外层循环次数为n-1次,内层循环次数为i次,根据计算以及大O表示法的规则得到其时间复杂度为O(n^2)。
插入排序的空间复杂度分析
同前两种算法一样,插排在插入的过程中,只需要使用一个额外的变量来暂存当前要插入的元素,而不需要额外的数据结构或数组来存储待排序元素。

四、补充:排序算法的稳定性

什么是排序算法的稳定性?
算法的稳定性是指算法在排序等操作时,相同元素在排序前后顺序不会改变的特性,若相同元素在排序后调换了顺序那么就称该排序算法是不稳定的排序算法。
从以上每个算法的实现中我们不难发现:
冒泡排序是稳定的排序算法,因为每次进行元素比较的时候,只有当被排序元素大于了当前被比较元素时才交换元素位置且每次比较时都是相邻的元素进行比较,所以当两个相同的元素进行比较时是不会交换位置的。
在这里插入图片描述
选择排序是不稳定的排序算法,因为在排序过程中,元素的位置交换是有跨度的,如果有相同大小的元素,它们可能会在排序的过程中交换位置,导致排序后顺序改变。比如,对于序列 5 5 2 7 8,首先选择最小的元素 1 和第一个元素 3 交换位置,得到序列 2 5 5 7 8,这种情况下原本的第一个5变成了第二个5。
插入排序是稳定的排序算法,因为同冒泡排序一样,其本质上也是相邻元素比较大小,满足大于或小于的条件才会交换元素之间的顺序。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值