2、冒泡排序算法

厨子大约 5 分钟数据结构算法算法基地面试排序算法

简介

估计我们在各个算法书上介绍排序时,第一个估计都是冒泡排序。主要是这个排序算法思路最简单,也最容易理解,(也可能是它的名字好听,哈哈),学过的老哥们也一起来复习一下吧,我们一起深挖一下冒泡排序。

冒泡排序的基本思想是,两两比较相邻记录的关键字,如果是反序则交换,直到没有反序为止。冒泡一次冒泡会让至少一个元素移动到它应该在的位置,那么如果数组有 n 个元素,重复 n 次后则能完成排序。根据定义可知那么冒泡排序显然是一种比较类排序。

最简单的排序实现

我们来看一下这段代码

Java Code:

class Solution {
    public int[] sortArray(int[] nums) {
        int len = nums.length;
        for (int i = 0; i < len; ++i) {
            for (int j = i+1; j < len; ++j) {
                if (nums[i] > nums[j]) {
                    swap(nums,i,j);
                }
            }
        }
        return nums;

    }
    public void swap(int[] nums,int i,int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

Python Code:

from typing import List
class Solution:
    def sortArray(self, nums: List[int])->List[int]:
        leng = len(nums)
        for i in range(0, leng):
            for j in range(i + 1, leng):
                if nums[i] > nums[j]:
                    self.swap(nums, i, j)
        return nums

    def swap(self, nums: List[int], i: int, j: int):
        temp = nums[i]
        nums[i] = nums[j]
        nums[j] = temp

我们来思考一下上面的代码,每次让关键字 nums[i] 和 nums[j] 进行比较如果 nums[i] > nums[j] 时则进行交换,这样 nums[0] 在经过一次循环后一定为最小值。那么这段代码是冒泡排序吗?

显然不是,我们冒泡排序的思想是两两比较相邻记录的关键字,注意里面有相邻记录,所以这段代码不是我们的冒泡排序,下面我们用动图来模拟一下冒泡排序的执行过程,看完之后一定可以写出正宗的冒泡排序。

动画模拟

代码

class Solution {
    public int[] sortArray(int[] nums) {
        int len = nums.length;
        for (int i = 0; i < len; ++i) {
            for (int j = 0; j < len - i - 1; ++j) {
                if (nums[j] > nums[j+1]) {
                    swap(nums,j,j+1);
                }
            }
        }
        return nums;
    }
    public void swap(int[] nums,int i,int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

上图中的代码则为正宗的冒泡排序代码,但是我们是不是发现了这个问题

微信截图_20210119221439
微信截图_20210119221439

我们此时数组已经完全有序了,可以直接返回,但是动图中并没有返回,而是继续执行,那我们有什么办法让其完全有序时,直接返回,不继续执行吗?

我们设想一下,我们是通过 nums[j] 和 nums[j+1] 进行比较,如果大于则进行交换,那我们设想一下,如果一个完全有序的数组,我们进行冒泡排序,每次比较发现都不用进行交换。

那么如果没有交换则说明当前完全有序。那我们可不可以通过一个标志位来进行判断是否发生了交换呢?当然是可以的

我们来对冒泡排序进行改进

代码

Java Code:

class Solution {
    public int[] sortArray(int[] nums) {
        int len = nums.length;
        //标志位
        boolean flag = true;
        //注意看 for 循环条件
        for (int i = 0; i < len && flag; ++i) {
            //如果没发生交换,则依旧为false,下次就会跳出循环
            flag = false;
            for (int j = 0; j < len - i - 1; ++j) {
                if (nums[j] > nums[j+1]) {
                    swap(nums,j,j+1);
                    //发生交换,则变为true,下次继续判断
                    flag = true;
                }
            }
        }
        return nums;

    }
    public void swap(int[] nums,int i,int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

Python Code:

from typing import List
class Solution:
    def sortArray(self, nums: List[int])->List[int]:
        leng = len(nums)
        # 标志位
        flag = True
        for i in range(0, leng):
            if not flag:
                break
            flag = False
            for j in range(0, leng - i - 1):
                if nums[j] > nums[j + 1]:
                    self.swap(nums, j, j + 1)
                    # 发生交换,则变为true,下次继续判断
                    flag = True
        return nums

    def swap(self, nums: List[int], i: int, j: int):
        temp = nums[i]
        nums[i] = nums[j]
        nums[j] = temp

这样我们就避免掉了已经有序的情况下无意义的循环判断。

时间复杂度分析

最好情况,就是要排序的表完全有序的情况下,根据改进后的代码,我们只需要一次遍历即可,

只需 n -1 次比较,时间复杂度为 O(n)。最坏情况时,即待排序表逆序的情况,则需要比较(n-1) + (n-2) +.... + 2 + 1= n*(n-1)/2 ,并等量级的交换,则时间复杂度为 O(n^2)。*

平均情况下,需要 n(n-1)/4 次交换操作,比较操作大于等于交换操作,而复杂度的上限是 O(n^2),所以平均情况下的时间复杂度就是 O(n^2)。

空间复杂度分析

因为冒泡排序只是相邻元素之间的交换操作,只用到了常量级的额外空间,所以空间复杂度为 O(1)

稳定性分析

那么冒泡排序是稳定的吗?当然是稳定的,我们代码中,当 nums[j] > nums[j + 1] 时,才会进行交换,相等时不会交换,相等元素的相对位置没有改变,所以冒泡排序是稳定的。

算法名称最好时间复杂度最坏时间复杂度平均时间复杂度空间复杂度是否稳定
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定