一、核心思想
冒泡排序最基本、最核心的思想就是:每两两相邻的元素进行大小的比较。如果不满足预期的顺序,就交换两元素的位置!
二、详细原理
我们随便以一个乱序的数组为例,例如{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次比较
在类似情况下,冒泡排序的效率便得以提高。