最近看了一下冒泡排序这个经典的算法,在网上也看到了很多改进冒泡排序算法的方式,这里总结一下:
冒泡排序最简单的实现方式如下(我用PHP来实现,用其他语言也是一样的):
for($i=0;$i<$arr_count;$i++){ for($j=0;$j<$arr_count-1;$j++){ if($rand_arr[$j] > $rand_arr[$j+1]){ //进行交换 $temp = $rand_arr[$j]; $rand_arr[$j] = $rand_arr[$j+1]; $rand_arr[$j+1] = $temp; } }}复制代码
上面的代码很简单,我这里就不解释了,大家肯定都明白。
下面,写一个测试用例,用php来生成一个长度为10000的随机数组,用来进行后面的实验对比‘
$rand_arr = [];for($i=0;$i<10000;$i++){ $rand_arr[$i] = rand(0,10000);}复制代码
好了,一切准备好了,下面开始进行冒泡排序的优化,首先要分析出冒泡排序的问题:
(1):当我们对一个较有序的数组进行排序操作的时候,使用冒泡排序的排序步骤如下:
原始状态:[ 3 , 1 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 ]----针对这个数组我们按照基本冒泡排序算法,需要循环count([ 3 , 1 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 ])轮,也就是10轮。
第一轮的排序结果如下:[1,3,4,5,6,7,8,9,10,11]
第二轮的排序结果如下:[1,3,4,5,6,7,8,9,10,11]
第三轮的排序结果如下:[1,3,4,5,6,7,8,9,10,11]
第....轮的排序结果如下:[1,3,4,5,6,7,8,9,10,11]
第十轮的排序结果如下:[1,3,4,5,6,7,8,9,10,11]
通过上面的介绍,会发现,上面的数组其实在第一轮排序后就已经将数组排序好了。后面的几轮排序其实都是无用功。也就是说下面的代码白白走了9遍,一点作用都没发生。
for($j=0;$j<$arr_count-1;$j++){ if($rand_arr[$j] > $rand_arr[$j+1]){ //进行交换 $temp = $rand_arr[$j]; $rand_arr[$j] = $rand_arr[$j+1]; $rand_arr[$j+1] = $temp; $flag = false; } }复制代码上面的白白执行9遍的代码中,每一遍都能在(n-1)的数据比较,如果数组的长度n极大,这个消耗还是很严重的。所以我们首先要解决一下这个问题,怎么才能不造成无谓的数据比较的资源浪费。
优化策略:在程序中添加标识,用于记录每次外循环过程中,是否发生了数据的交换。如果没有发生数据的交换,就意味者在上一轮的外循环过程中,数据已经排序完成,不需要继续循环比较数据了,我们直接break掉就可以了。如果还存在这数据的交换,就意味着在之前的外部循环中,数据还没有排序好,这一轮排序操作或者接下来的排序操作才可以完成排序工作,我们还需要进行下一轮的排序来完成排序。
$arr_count = count($rand_arr);for($i=0;$i<$arr_count;$i++){ //设置flag变量,用来记录数据是否交换完成 $flag = true; for($j=0;$j<$arr_count-1;$j++){ if($rand_arr[$j] > $rand_arr[$j+1]){ //进行交换 $temp = $rand_arr[$j]; $rand_arr[$j] = $rand_arr[$j+1]; $rand_arr[$j+1] = $temp; $flag = false; } } if($flag){ break; }}复制代码其实,在进行数据排序的过程中,随着排序的进行,数据逐渐从无序变为有序,出现上面情况的可能性会逐步的增高的。
(2):好了,说完上面的问题,咱们再来看下面的问题。当冒泡排序的外部循环每执行一次,就会将最大值(最小值)交换到数组的最前端或者数组的最后端,直接看下面的例子。
数组:[ 4 , 6 , 7 , 2 , 8, 9 , 0 ]
经过第一轮排序以后会得到以下的结果:[ 4 , 6 , 7 , 2 , 8 , 0, 9 ] ----也就是说,我们再第一轮求出的最大值9,并且将这个值交换到数组的左部第一的位置上
经过第二轮排序以后会得到以下的结果:[ 4 , 6 , 7 , 2 , 0 , 8 , 9 ] ----也就是说,我们再第二轮求出的次最大值8,并且将这个值交换到数组的左部第二的位置上
........
依此类推,我们可以发现,每一轮循环完毕,都能求出某个位置上的应有的数据,并且完成交换。那么也就是说数组的左部(右部)逐渐变得有序,且有序部分的长度等于外部循环进行的轮数。既然数组的左部(右部)逐渐变得有序,那么每次循环过程中,我们就不需要再比较数组的左部(右部)的有序的部分了。所以下面的内部循环部分,就进行了一些无用的计算了。
//当$i=2,也就是进行第三轮循环的时候,左部两个元素是有序的,不需要参与到之后的比较过程了//下面的代码仍然将数组的长度-1作为临界条件,是多余了for($j=0;$j<$arr_count-1;$j++){ if($rand_arr[$j] > $rand_arr[$j+1]){ $temp = $rand_arr[$j]; $rand_arr[$j] = $rand_arr[$j+1]; $rand_arr[$j+1] = $temp; } }复制代码优化策略:新增一个变量来记录某次外部循环过程中最后发生的数据交换的位置。使用上面记录的位置来作为下一次外部循环中的内部循环的终止条件,这样就能减少不必要数据比较过程。代码如下:
$arr_count = count($rand_arr);$last_pos = $arr_count; //记录每一次外部循环过程中,最后进行数据交换的位置$next_pos = $arr_count; //记录每一次数据交换的位置for($i=0;$i<$arr_count;$i++){ for($j=0;$j<$last_pos-1;$j++){ if($rand_arr[$j] > $rand_arr[$j+1]){ //进行交换 $temp = $rand_arr[$j]; $rand_arr[$j] = $rand_arr[$j+1]; $rand_arr[$j+1] = $temp; $next_pos = $j+1; } } //如果该轮循环得到的最后的数据交换位置等于上一轮循环得到的数据交换的位置,就意味着数组全部有序了 if($last_pos == $next_pos){ break; }else{ //如果没有全部有序,就将last_pos设置为next_pos $last_pos = $next_pos; }}复制代码(3):解决了上面的问题,我们还能不能进一步的提高冒泡算法的排序速度嘛?答案是可以的。我们可以采用双向排序的方式,进一步加快排序的速度。看下面的实现的例子:
数组: [ 4 , 3 , 6 , 5 , 9 , 0 , 1 , 7 ]
双向排序之正向排序(从左到右排序):[ 3 , 4 , 5 , 6 , 0 , 1 , 7 , 9] ---计算出数组的最大值9,并将元素移动到数组的最左边
双向排序之逆向排序(从右到左排序):[ 0 , 3 , 4 , 5 , 6 , 1 , 7 , 9] ---计算出数组的最小值0,并将元素移动到数组的最右边
第一次循环过程,计算出最大值并将该值移动到数组最右边,计算出最小值并将该值移动到数组的最左边。第二次循环过程中,计算出第二大的值并将数据移动到数组右边第二个的位置,计算出第二小的值并将数据移动到数组左边第二个的位置。也会说在一次双向排序循环过程中,求出数组左边以及数组右边各个位置响应的元素,并且移动到响应的位置。
具体的实现代码如下:
$arr_count = count($rand_arr);$index = 0; //从左到右排序过程中起始的位置 $first_pos = 0; //从右到左排序过程中最后进行数据交换的位置$up = $arr_count-1; //从左到右排序过程中结束的位置$last_pos = $up; //从左到右排序过程中最后进行数据交换的位置while($up >= $index){ //从头开始扫描 for($i=$index;$i<$up;$i++){ if($rand_arr[$i]>$rand_arr[$i+1]){ //进行交换 $temp = $rand_arr[$i]; $rand_arr[$i] = $rand_arr[$i+1]; $rand_arr[$i+1] = $temp; $last_pos = $i; } } if($up == $last_pos){ break; } $up = $last_pos; //从尾部开始扫描 for($j=$up;$j>1;$j--){ if($rand_arr[$j-1]>$rand_arr[$j]){ //进行交换 $temp = $rand_arr[$j]; $rand_arr[$j] = $rand_arr[$j-1]; $rand_arr[$j-1] = $temp; $first_pos = $j; } } if($index == $first_pos){ break; } $index = $first_pos;}复制代码
最后为了验证我们的优化方式是逐步有效的,我们用之前准备好的测试案例(length=10000的随机数组),进行验证,我下面贴出来验证的结果:
没有进行优化,冒泡执行的时间38.826741201211第一次优化后,冒泡执行的时间36.326452970505第二次优化后,冒泡执行的时间24.916656017303第三次优化后,冒泡执行的时间20.908751010895