排序算法——冒泡排序

一、核心思想

冒泡排序最基本、最核心的思想就是:每两两相邻的元素进行大小的比较。如果不满足预期的顺序,就交换两元素的位置!

二、详细原理

我们随便以一个乱序的数组为例,例如{4,5,2,1,3}。现在我们需要通过冒泡排序使该数组变为升序。

上文说到冒泡排序的核心就是两两比较,那不妨从该数组的第一个元素 “ 4 ” 与 “ 5 ” 进行比较,发现 4 < 5 符合升序,则不交换位置; 接着将 “ 5 ” 与 “ 2 ”进行比较,发现 5 > 2 不符合升序,则交换位置!以此类推,可以得到以下过程:

为方便起见,我们将上述整个过程称为一次“冒泡”,而这一次“冒泡”仅仅找出了序列中的最值并将其排序。换句话说,现在的序列中只有“5”这一个元素是有序的,剩下的{4,2,1,3}仍需要进行排序。

在一次的“冒泡”过程中,不难发现有5个元素的序列,两两比较共进行了4次比较,那么类比可得,对于一个有 n 个元素的序列,一次“冒泡”共需 (n - 1) 次比较。

而又因为每进行一次“冒泡”,剩下未排序的序列就会少一个元素(含有 n - 1 个元素),所以在第二次的“冒泡”中,就只需要 (n - 2) 次比较。以此类推,如果对于一个已经进行了 i 次冒泡排序的序列,剩下的序列进行一次“冒泡”共需要 (n - 1 - i) 次比较。

说完了每一次“冒泡”比较的次数,那再来说说对于一个含有 n 个元素的乱序序列,共需要多少次“冒泡”。 在上述含有 5 个元素的序列,如果接着往下推一下,我们不难发现一共需要 4 次“冒泡”过程,就能实现对该序列的排序。那么类比一下,对于一个含有 n 个元素的乱序序列,那就需要  (n - 1)次“冒泡”。

所以对于代码而言,我们就需要一个两层的嵌套for循环来实现整个“冒泡”的过程。外层的for循环用以控制总共需要“冒泡”的次数,即 (n - 1)次,而内层的for循环则用以控制每一次“冒泡”过程中需要比较的次数,即(n - 1 - i)次。

三、代码构建(C语言实现)

void bubble_sort(int arr[], int n)
{
    //定义一个用以冒泡排序的函数
    //其中 arr 用于接收主函数中需要排序的数组
    //而变量 n 则是该数组中元素的个数
    for(int i = 0; i < n-1; i++) // 排序总共需要进行的“冒泡”的次数:(n - 1)次
    {
        for(int j = 0; j < n-1-i; j++)//每一次“冒泡”需要进行几次比较? (n - 1 - i)次
        {
            if(arr[j] > arr[j+1])//用于判断相邻两元素是否符合升序要求
            {
                int temp = 0;        //将两元素进行交换
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

算法优化

        首先不妨先思考一下,冒泡排序可以优化的空间在哪?

        从冒泡排序的原理来看,该算法的效率之所以低,是因为对于任意含有 n 个元素的待排序的数组而言,冒泡排序判断是否需要交换元素的次数是固定的,我们通过上述代码可以计算一下,对于一个含有 n 个元素的待排序的数组,总共需要进行 (n - 1) +(n - 2) + ··· + 1 = n*(n-1)/2 次。

        那现在假设有这么一个数列 {9,0,1,2,3,4,5} ,可以看出,理论上只要进行一次“冒泡”,将 9 排序到该数组的最后,就可以完成该数组的排序了。而对于优化前的代码而言,在排完 9 之后,又会傻傻地进入下一次“冒泡”需要的排序中,而这接下来的两两比较中,显然没有任何元素需要再交换。那这时便产生了“浪费”,多余的比较需要消耗多余的时间,效率就低了!

        于是我们不妨从这个角度对算法进行优化,假设能够在每一次“冒泡”后,对该数组进行一次检测,如果发现该数组已经符合预期(升序或降序),那么便直接中止接下来的“冒泡”过程,是不是就可以减少接下来的“浪费”,从而达到优化的效果。

        那么如何对数组进行检验呢?如果一个数组已经符合预期,会出现什么效果?那便是在下一次的“冒泡”过程中,元素一次都没有交换。反之,如果出现了哪怕一次交换的过程,就说明这个数组还不符合预期。

        那不妨对上述程序进行优化改写:

void bubble_sort(int arr[], int n)
{
   
    
    for(int i = 0; i < n-1; i++) 
    {
        int flag = 1; //flag 作为一个标记变量,1代表该数组已经有序(符合预期)
        for(int j = 0; j < n-1-i; j++)
        {
            if(arr[j] > arr[j+1])//用于判断相邻两元素是否符合升序要求
            {
                int temp = 0;        //将两元素进行交换
                arr[j] = arr[j+1];
                arr[j+1] = temp;
                flag = 0; //将flag 改为 0 说明该数组还不符合预期,仍需“冒泡”
            }
        }
        if(flag == 1) //若该数组已经有序,则不再进行“冒泡”循环
           {
                break;
           }
    }
}

我们以{9,0,1,2,3,4,5,6,7,8}这个数组为例,来看看优化前后的效果,来统计一下优化前后需要进行比较的次数。

优化前:进行了整整45次比较

优化后:只进行了17次比较

在类似情况下,冒泡排序的效率便得以提高。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值