冒泡排序的基本实现及优化



一、前言

本文不是创新性文章,本文旨在对"冒泡排序"的基本逻辑、基本实现,以及已有的优化实现进行简单的学习性总结
本文所有算法均以C++实现
如有谬误、更多的想法等等,还请留言




二、基本概念

1、算法特征

O(n2) In-place 稳定

2、算法逻辑

类似于金鱼冒泡的物理现象。金鱼吐出的气泡(待排元素) 受到 上浮过程中(每次冒泡循环)水压(升序/降序) 影响而逐渐变大,冒出水面时气泡大小最大升序一次循环后最大元素排在最后


具体来说:
每次冒泡循环中:相邻的元素两两比较
当一个元素大于右侧相邻元素时,交换它们的位置
当一个元素小于或等于右侧相邻元素时,位置不变
在这里插入图片描述

此时,最大的数字9就排到了队尾,此时称为一次冒泡。
重复冒泡过程,进行(n -1)次冒泡,序列必然有序

在这里插入图片描述

冒泡排序动态示意图

3、算法复杂度

3.1、时间复杂度

因为每一次冒泡过程将遍历所有的N个元素,且将进行N-1冒泡过程,所以:
平均时间复杂度:O(N2)
最好情况:O(N)
最坏情况:O(N2)

3.2、空间复杂度

因为仅仅进行了交换,所以:
空间复杂度O(1)


三、代码实现

1、一般实现

template<typename T>
void BubbleSort(vector<T> &nums) {
    int length = nums.size();
    for (int i = 0; i < length - 1; ++i) {
        for (int j = 0; j < length - 1 - i; ++j) {
            if (nums[j] > nums[j + 1]) {
                T temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }//升序
        }//每一次冒泡的最大值最后一定放在length - 1 - i的位置
    }//冒泡过程
}



2、算法优化

2.1、提前结束算法

情景: 冒泡排序中时常会遇到未进行完N-1次排序,序列已经有序,但算法依然继续执行,浪费了大量的时间。
解决方案: 当某次冒泡过程中没有进行交换时,就说明序列已有序
代码实现:

template<typename T>
void BubbleSort(vector<T> &nums) {
    int length = nums.size();
    for (int i = 0; i < length - 1; ++i) {
        bool done = true;//完成标记
        for (int j = 0; j < length - 1 - i; ++j) {
            if (nums[j] > nums[j + 1]) {
                T temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
                done = false;//未完成,继续
            }//升序
        }//每一次冒泡的最大值最后一定放在length - 1 - i的位置
        if (done) return;//排序完成,提前结束
    }//冒泡过程
}



2.2、跳过有序区

情景: 序列中本身具有部分有序的片段,但算法依旧扫描过去,造成大量时间浪费。例如:[8, 0, 7, 3, 4, 5, 6, 1, 2, 9], 其中3,4,5,6已经处于正确位置无需再遍历。
解决方案: 标记最后一次进行交换的位置,该位置之后均为有序
代码实现:

template<typename T>
void BubbleSort(vector<T> &nums) {
    int length = nums.size();
    int border = length - 1;//标记有序区边界
    int lastExchange = 0;//记录最后一次交换的位置
    for (int i = 0; i < length - 1; ++i) {
        count++;
        bool done = true;//完成标记
        for (int j = 0; j < border; ++j) {
            if (nums[j] > nums[j + 1]) {
                T temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
                done = false;//未完成,继续
                lastExchange = j;//更新最后交换位置
            }//升序
        }//每一次冒泡的最大值最后一定放在length - 1 - i的位置
        if (done) return;//排序完成,提前结束
        border = lastExchange;//更新边界
    }//冒泡过程
}



2.3、双向冒泡排序——鸡尾酒排序

情景: 序列中某些值位于最后,需要排在最前。(逆序)例如:[3, 4, 5, 6, 7, 1],一般的冒泡排序需要执行完N-1次,如果可以逆向冒泡,则只需要1次即可。
题外话:为什么叫做"鸡尾酒排序"? 个人理解:双向冒泡排序像是"泡泡"在来回翻滚,如同调酒师调鸡尾酒一样。可以看看鸡尾酒排序的算法可视化视频,很有意思。空降视频04:19s
解决方案: 按正向、逆向交替的冒泡方式进行双向冒泡
代码实现:

template<typename T>
void DoubleBubbleSort(vector<T> &nums) {
    int length = nums.size();
    
    for (int i = 0; i < length / 2; ++i) {        
        for (int j = 0; j < length - 1 -i; ++j) {
            if (nums[j] > nums[j + 1]) {
                T temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }//使正序列升序
        }//奇数轮,左到右
        
        for (int j = length - 1 - i; j > i; --j) {
            if (nums[j] < nums[j - 1]) {
                T temp = nums[j];
                nums[j] = nums[j - 1];
                nums[j - 1] = temp;
            }//使正序列升序
        }//偶数轮,右到左
    }//冒泡过程
}



2.4、优化后的鸡尾酒排序

情景: 在一般的冒泡排序中可以优化使得算法及时结束,跳过无需考虑的部分。这些优化措施在鸡尾酒排序中也有相当的优化作用。
解决方案: 将上述优化"提前结束算法"和"跳过有序区"整合入鸡尾酒排序中
代码实现:

template<typename T>
void DoubleBubbleSort(vector<T> &nums) {
    int length = nums.size();
    int border = length - 1;//标记有序区边界
    int lastExchange = 0;//记录最后一次交换的位置
    for (int i = 0; i < length / 2; ++i) {
        bool done = true;//完成标记
        for (int j = 0; j < border; ++j) {
            if (nums[j] > nums[j + 1]) {
                T temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
                done = false;//未完成,继续
                lastExchange = j;//更新最后交换位置
            }//使正序列升序
        }//奇数轮,左到右
        if (done) return;//排序完成,提前结束
        border = lastExchange;//更新边界

        done = true;//重置标记

        for (int j = border; j > i; --j) {
            if (nums[j] < nums[j - 1]) {
                T temp = nums[j];
                nums[j] = nums[j - 1];
                nums[j - 1] = temp;
                done = false;//未完成,继续
                lastExchange = j - 1;//更新最后交换位置
            }//使正序列升序
        }//偶数轮,右到左
        if (done) return;//排序完成,提前结束
        border = lastExchange;//更新边界
    }//冒泡过程
}



四、总结

冒泡排序是最为简单的一种排序,其实用性低,但逻辑简单易快速构造。处理较少的数据量时,可以考虑使用。

双向冒泡排序(鸡尾酒排序),是基于冒泡排序的变种版本。再附加上优化改进,虽然在使其有更高的效率、兼容性,但其平均时间复杂度依旧为O(N2)



五、参考源

算法可视化:
        https://github.com/ZQPei/Sorting_Visualization
        https://github.com/zamhown/sorting-visualizer
算法优化:
        https://leetcode-cn.com/leetbook/read/journey-of-algorithm/5rxj8i/
        https://blog.csdn.net/zgcqflqinhao/article/details/83537017
        https://blog.csdn.net/lemon_tree12138/article/details/50591859



感谢阅读!

有疑问或者认为有错误请留言,谢谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值