• 排序算法——冒泡排序


    写在前面

    排序算法是算法中最基础的,学好排序算法,在实际需求中还是很有用的,这里介绍几种常见的排序算法。

    算法有性能好坏之分,这种判断的方式就是:

    • 时间复杂度(原本指一个算法执行所需的具体时间,但是在实际判断时一般指同一个语句执行次数最多的次数。)

    • 空间复杂度(原本指一个算法在内存中所占用的存储空间,但在实际判断时一般指用到的临时变量的个数,如果是固定的常量数就是 O(1),若是不固定和 n 有关,则就是 n 的函数)

    在排序算法中,还多了一个特殊的判断依据:

    • 稳定性
    1. 如果 a=b,排序前是 a 在 b 的前面,排序后 a 还是在 b 的前面,那么就称这种算法是稳定的

    2. 如果 a=b, 排序前 a 在 b 的前面,排序后可能会 b 在 a 的前面,那么就称这种算法是不稳定的

    排序算法可以可以映射到生活中。我们日常的排队时,安排从低到高的顺序排,我们人眼是一眼就可以看出来谁高些就让其往后站,谁低些就让其往前站。但计算机不能,它只能按照告诉它的逻辑去执行。

    下面就看程序员是如何告诉计算机进行操作的。

    1. 冒泡排序

    冒泡排序是最基础的排序算法,该算法的具体思路就是从第一个开始,依次往后两两比较,二者中谁高就往后站,直至比到最后一个人,此时,最后一个人就是整个队中最高的。然后再从头开始依次往后两两比较,二者中谁高谁站后面,此时因为最后一个人已经是最高的了,因此就直至比到倒数第二个,依次类推。每比一趟都会确定一个人的最终占位,一共比 n-1 趟就能确定 n-1 个人的位置了,那剩下那一个人的位置就是在第一个了。

    依据上述思路,使用 js 实现代码如下:

    function sort(arr){
        for(let i = 0; i < arr.length - 1; i++)  /*第一个for循环确保是 n-1 趟*/
            for(let j = 0; j < arr.length -1 - i; j++){  /*第二个for循环是比较的起始点和末点,起始点一直是0,末点随着 i 的变化而变化*/
                if(arr[j] > arr[j+1]){   /*因为这里是有用到 j+1, 所以上层循环的末点要到 length -1 - i,因为当 i 为 0 时,末点最远为 < length - 1,那 j+1 的位置才是 length - 1。注意不要地址越界了*/
                    let temp = arr[j]  
                    arr[j] = arr[j+1]
                    arr[j+1] = temp
                }
            }
        return arr
    }
    

    这里没有规定 i 必须从 0 开始,只要控制第一个循环为 n-1 趟就可以,第二层循环根据 i 值具体计算。只要注意 j+1 没有地址越界就可以,如下:

    function sort2(arr){
        for(let i = 1; i < arr.length; i++)
            for(let j = 0; j < arr.length - i; j++){
                if(arr[j] > arr[j+1]){
                    let temp = arr[j]
                    arr[j] = arr[j+1]
                    arr[j+1] = temp
                }
            }
        return arr
    }
    

    平均时间复杂度:O(n2),最好时间复杂度:O(n),最差时间复杂度:O(n2)

    空间复杂度:O(1)

    稳定性:稳定

    1.1 改进的冒泡排序

    在上述最基本的冒泡排序中,如果一个数组本身就是有序的,或者比较不到 n-1 趟时就已经是有序的了,那么对于上述的排序算法,会一直执行比较语句 n 次,直到比了 n-1 趟为止。

    但实际上,如果一趟下来,交换语句从没有执行过,那就说明这个数组已经是有序的数组了,没有必要再进行后续的循环比较了,因此在每趟中加个判断条件改进冒泡排序如下:

    function sort3(arr){
        for(let i = 0; i < arr.length - 1; i++){
            let flag = false
            for(let j = 0; j < arr.length - 1 -i; j++){
                if(arr[j] > arr[j+1]){
                    flag = true
                    let temp = arr[j]
                    arr[j] = arr[j+1]
                    arr[j+1] = temp
                }
            }
            if(!flag)
                break
        }
        return arr
    }
    

    改进后的冒泡算法的时间复杂度成了一个变量,根据比较数组的有序程序有不同的时间复杂度,因此,此时就不再使用时间复杂度来衡量了,而是使用平均时间复杂度,就是将最差情况的事件复杂度和最好情况的复杂度进行平均的计算。

    最好情况下,这个数组本身就是有序的,那语句都会只被执行一遍,时间复杂度为 O(1)。最坏情况下,这个数组是完全逆序的,n-1 趟一趟也不能少,此时时间复杂度为 O(n^2),因此取平均数还是 O(n^2)

    平均时间复杂度:O(n2),最好时间复杂度:O(1),最坏时间复杂度:O(n2)

    空间复杂度:O(1)

    稳定性:稳定

    后续会更新冒泡排序的其他改进方式。。。

  • 相关阅读:
    Dubbo 配置参数
    类文件结构

    shell script 编程入门
    OnePlus5刷 TWRP
    TimeUtil 工具类
    Outline 科学的上网
    HotSpot虚拟机的锁优化
    equals与hashCode
    Java中的动态代理
  • 原文地址:https://www.cnblogs.com/lovevin/p/13599197.html
Copyright © 2020-2023  润新知