冒泡排序及其优化

本文深入讲解冒泡排序算法,包括其基本思想、运行过程、算法实现及优化,同时对比鸡尾酒排序,分析时间与空间复杂度,以及算法的稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

冒泡排序

  • 冒泡排序(Bubble Sort),一种交换排序,两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

基本思想

  • 基本思想:从无序序列头部开始,进行两两比较,根据大小交换位置,直到最后将最大(小)的数据元素交换到了无序队列的队尾,从而成为有序序列的一部分;下一次继续这个过程,直到所有数据元素都排好序。
  • 算法核心:每次通过两两比较交换位置,选出剩余无序序列里最大(小)的数据元素放到队尾。
  • 运行过程:
    1. 比较相邻元元素,第一个比第二个大(小),交换。
    2. 从序列头到尾做同样的工作,这样序列中最大(小)的元素被交换到最后一位。
    3. 除去已经选出的元素,对剩下的元素重复以上的步骤。
    4. 持续对每次越来越少的元素(无序)重复上面的步骤,直到没有任何一对数字需要比较,则序列最终有序。
  • 示例

算法实现(核心代码)

代码部分使用了Utils类,定义在这篇文章中有说明。
核心代码:

//C++实现
void bubbleSort(int* A, int n){
    if(A==NULL || n==0){
        return;
    }
    for(int i=n-1; i>=0; i--){
        for(int j=0; j<i; j++){
            if(A[j]>A[j+1]){
                Utils::swap(A, j, j+1);
            }
        }
    }
}

算法优化和变种

  • 设置交换标志变量flag

试想一下,如果待排序的序列是[1,2,3,4,5,6,7,9,8],也就是倒数第一和倒数第二个关键字需要交换,别的都已经是正常顺序,当i=1时,交换了8和9,序列已经有序,但是算法仍然将i=2~9以及每个循环中的j循环都执行一遍,虽然没有交换数据,但这种大量的比较也是多余的,优化的方法是当一趟冒泡中并没有发生交换时,说明序列已经时有序的了,这时候便可以停止运行,所以改进代码,增加一个flag变量用来标记是否有交换的发生。C++实现如下:

void bubbleSort(int* A, int n){
    if(A==NULL || n==0){
        return;
    }
    bool flag = true;  //定义标志变量flag
    for(int i=n-1; i>=0 && flag; i--){    
        flag = false;  //循环开始置为false
        for(int j=0; j<i; j++){
            if(A[j]>A[j+1]){
                Utils::swap(A, j, j+1);
                flag = true;  //发生交换置为true
            }
        }
    }
}
  • 鸡尾酒排序

鸡尾酒排序又叫定向冒泡排序,搅拌排序、来回排序等,是冒泡排序的一种变形。此算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。

鸡尾酒排序在于排序过程是先从低到高,然后从高到低;而冒泡排序则仅从低到高去比较序列里的每个元素。它可以得到比冒泡排序稍微好一点的效能,原因是冒泡排序只从一个方向进行比对(由低到高),每次循环只移动一个项目。

以序列(2,3,4,5,1)为例,鸡尾酒排序只需要从低到高,然后从高到低就可以完成排序,但如果使用冒泡排序则需要四次。

但是在乱数序列的状态下,鸡尾酒排序与冒泡排序的效率都很差劲。

//C++实现
void cocktailSort(int* A, int n){
    int j, left = 0, right = n-1;
    while(left<right){
        for(j=left; j<right; j++){
            if(A[j]>A[j+1]){
                Utils::swap(A, j, j+1);
            }
        }
        right--;
        for(j=right; j>left; j--){
            if(A[j-1]>A[j]){
                Utils::swap(A, j-1, j);
            }
        }
        left++;
    }
}

性能分析

  1. 时间复杂度

    在设置标志变量之后:

    当原始序列“正序”排列时,冒泡排序总的比较次数为n-1,移动次数为0,也就是说冒泡排序在最好情况下的时间复杂度为O(n);

    当原始序列“逆序”排序时,冒泡排序总的比较次数为n(n-1)/2,移动次数为3n(n-1)/2次,所以冒泡排序在最坏情况下的时间复杂度为O(n^2);

    当原始序列杂乱无序时,冒泡排序的平均时间复杂度为O(n^2)。

  2. 空间复杂度

    冒泡排序排序过程中需要一个临时变量进行两两交换,所需要的额外空间为1,因此空间复杂度为O(1)。

  3. 稳定性

    冒泡排序在排序过程中,元素两两交换时,相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。

附:完整实现和测试

#include <iostream>
#include "Utils.h"
using namespace std;

class BubbleSort{
    public:
        void bubbleSort(int* A, int n){
            if(A==NULL || n==0){
                return;
            }
            bool flag = true;
            for(int i=n-1; i>=0 && flag; i--){
                flag = false;
                for(int j=0; j<i; j++){
                    if(A[j]>A[j+1]){
                        Utils::swap(A, j, j+1);
                        flag = true;
                    }
                }
            }
        }

        void cocktailSort(int* A, int n){
            int j, left = 0, right = n-1;
            while(left<right){
                for(j=left; j<right; j++){
                    if(A[j]>A[j+1]){
                        Utils::swap(A, j, j+1);
                    }
                }
                right--;
                for(j=right; j>left; j--){
                    if(A[j-1]>A[j]){
                        Utils::swap(A, j-1, j);
                    }
                }
                left++;
            }
        }
};


int main(int argc, char const *argv[])
{
    int n = 10;
    int range = 100;
    int *arr = Utils::generateArray(n,range);
    Utils::printArray(arr, n);
    BubbleSort bubble = BubbleSort();
    // bubble.bubbleSort(arr, n);
    bubble.cocktailSort(arr, n);
    Utils::printArray(arr, n);
    return 0;
}


参考链接:

https://blog.youkuaiyun.com/guoweimelon/article/details/50902597

https://zh.wikipedia.org/zh/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值