冒泡排序
1.排序思想
冒泡排序也是非常简单的排序算法,易于理解。
要点:(1)也把数组看作有序和无序部分,初始时将整个数组视为无序;
(2)每次遍历数组中的无序部分,且两两比较,并将两者中较大的元素置于后面一位,则一趟遍历完成后,最大元素自然“沉到”无序部分的最后一位;
(3)减小无序部分的长度,循环第二步,直到数组有序。
其思想与选择排序类似,所不同的是每一趟通过两两比较与交换得到最大值,同时交换到最后位置。
2.代码实现
//冒泡排序
void bubbleSort(int arr[], int len)
{
for (int i=0; i<len-1; i++) //外层n-1次循环,len-i代表了无序部分的最后一位元素,每循环一次,无序部分个数减1
{
for (int j=1; j<len-i; j++) //内层n-i次循环,目的是将无序部分的最大值交换到len-i位置
{
if (arr[j-1] > arr[j]) //两两比较和交换
{
swap(arr[j-1], arr[j]);
}
}
}
}
从代码实现中可以看出外层循环中,len-i实际代表的是无序部分的最后一个元素的位置,外层循环每执行一次就找到无序部分的最大值并同时被交换到len-i的位置上。这样,无序部分元素减少一个,而有序部分个数增加一个。所以len-1趟之后,全部len个数据就拍好序了。
但是如果数据是有序的或者后半部分有序,冒泡排序的效率将非常低,它依然会一趟一趟不折不扣的执行比较,而造成效率低下。所以,可以针对这种情况对冒泡排序进行改进,那就是引入一个空跑标志(将一趟遍历没有发生数据交换称之为空跑),用来代表该趟遍历是否有数据交换,如果没有,说明后面的数据已经有序(有序才不需要交换),则可以停止比较和交换了;如果有数据交换,则说明数据无序。
//冒泡排序的改进(引入空跑标志)
void bubbleSortImp(int arr[], int len)
{
int flag = 0; //空跑标志,初始为0 0-未空跑 1-空跑
int i = 0;
while(!flag && i<len-1) //加入上一趟是否空跑判断
{
flag = 1; //假设本趟空跑
for (int j=1; j<len-i; j++) //内层n-i次循环
{
if (arr[j-1] > arr[j]) //两两比较和交换
{
swap(arr[j-1], arr[j]);
flag = 0; //如果有交换则必然未空跑,否则空跑
}
}
i++;
}
}
上述的改进算法主要针对的是数据后半部分有序时候的改进,如果前半部分的数据有序,而后半部分无序又该如何改进呢?这个问题留给读者思考,在文章的最后,我会给出参考链接。
3.性能分析
时间复杂度:O(n^2)。
由代码可以看出,相对于选择排序,它的数据移动次数明显增多,而比较次数相同,所以根据选择排序的性能分析,可以得知:无论最好情况、最坏情况和平均情况,比较次数都不变,所以选择排序的时间复杂度O(n^2)。
空间复杂度:O(1)。
特点:稳定的就地排序(稳定性与代码有关)。
适用性:小规模数组排序。
总结:在某本算法书中提到,冒泡排序的效率是非常糟糕的,如果不是因为它有一个形象易记的名字,可能根本不会有人注意它。
关于上述文章提到的冒泡排序的另一种改进方法,参考链接如下: