冒泡排序及其算法优化分析

1.基本冒泡排序

     冒泡排序的基本思想:假设被排序的记录数组d[1...N]垂直竖立,将每个记录d[i]看作是一个气泡,那么重的气泡就会向下下沉,轻的气泡就会向上升。每次都是相邻的两个气泡d[i]和d[i+1]进行比较。如果d[i]>d[i+1],那么就交换两个气泡,然后在比较d[i+1]和d[i+2],以此类推,知道所有的气泡都有序排列。假设排序20,37,11,42,29。

第1次冒泡:20。37,11,42,29    d[0]和d[1]比较    
第2次冒泡:20,11,37,42,29    d[1]和d[2]比较
第3次冒泡:20,11,37,42,29    d[2]和d[3]比较
第4次冒泡:20,11,37,29,42    d[3]和d[4]比较

      那么就找到了最重的气泡42,接下来按同样的方法找出第二重、第三重……的气泡,直到完成排序。根据以上分析可知需要双层循环。第一层循环控制次数,第二层循环控制要排序的数据范围。如下所示

第0次比较第0个到第n-1个,下标控制0~n-2
第1次比较第0个到第n-2个,下标控制0~n-3
.
.
第i次比较第0到第n-i+1个,下标控制0到n-i-2
.
.
第n-2次比较第0到第1个,下标控制0到0

     冒泡排序算法的代码如下

int main(void)
{
    int i, j, tmp;
    int N = 5;
    int a[N] = {20,37,11,42,29};
    for( i=0; i<N; i++ ){
        for( j=0; j<N-i-1; j++ ){
            if( a[j]>a[j+1] ){
                tmp = a[j];
                a[j] = a[j+1];
                a[j+1] = tmp;
            }
        }
    }
    for(i=0; i<N; i++)
        printf("%d\n", a[i]);
    return 0;
 }

       冒泡排序的最好、最坏、平均情况下的时间复杂度都是O(n^2)。但是若在某趟排序中未发现气泡位置的交换,则说明排序的无序区中所有的气泡均满足轻者在上,重者在下的原则,即为正序,则冒泡排序过程可在此次扫描后就终止。基于这种考虑,提出第一种改进算法

2.冒泡排序算法优化一:不做每次扫描都判断是否已经排序完成

     如果在某趟循环中没有任何数据交换发生,则表明数据已经排序完成。那么剩余的循环就不需要再执行了。改进后的算法代码如下

int main(void)
{
    int i, j, tmp;
    int N = 5;
    int isSorted = 0;
    int a[5] = {20,37,11,42,29};
    for( i=0; i<N&&(!isSorted); i++ ){
    //只有在没有排序的情况下(!isSorted)才进行继续循环
        isSorted = 1;
        //设定排序标志
        for( j=0; j<N-i-1; j++ ){
            if( a[j]>a[j+1] ){
                isSorted = 0;
                //如果没有排序,就重新设定标志
                tmp = a[j];
                a[j] = a[j+1];
                a[j+1] = tmp;
            }
        }
    }
    for(i=0; i<N; i++)
        printf("%d\n", a[i]);
    return 0;
 }

 

     这种排序方法,如果数据初始状态就是正序,那么扫描一趟就可以完成。所需要的比较和数据移动的次数分别时最小值n-1和0,也就是算法最好的时间复杂度是O(n);若初始数据反序,则需要进行n-1趟排序,每趟排序进行n-i次关键字的比较,且每次比较都必须移动数据三次来达到交换数据位置,这种情况下比较次数达到最大值n(n-1)/2,移动次数也达到最大值3n(n-1)/2,所以最坏的时间复杂度还是O(n^2)。平均的时间复杂度仍为O(n^2)

2.冒泡排序改进二:记录犯罪现场

     在冒泡排序的每一趟扫描中,记住最后一次交换发生的位置lastexchange也是有所帮助的。因为该位置之前的相邻的记录已经有序,故下一趟排序开始的时候,0lastexchange已经是有序的了,lastexchangen-1是无序区,所以一趟排序可能使当前有序区扩展多个记录,即较大缩小无序区范围,而非递减1,以此减少排序趟数。

void BubbleSort(int array[], int len)
{
    int m = len – 1;
    int k, j;
    int tmp;
    while(m>0){
        for(k=j=0; j<m; j++){
            if(array[j] > array[j+1]){
                tmp = array[j];
                array[j] = array[j+1];
                array[j+1] = tmp;
                k=j;//记录每次交换的位置
            }
        }
        m=k;//记录最后一个交换的位置
    }
}

      这种算法可以看出,若记录的初始状态是正序(从小到大),则一趟扫描即可完成排序,所需的关键比较和记录移动的次数分别达到最小值n-10。即算法最好的时间复杂度是O(n);若初始记录是反序,则需要进行n-1趟排序,每趟排序要进行n-i次关键字的比较,且每次比较都必须移动记录三次来达到交换记录位置。这时比较次数达到最大值n(n-1)/2,移动次数也达到最大值3n(n-1)/2

     因此.这种办法的最坏时间复杂度也为O(n^2)。在平均情况下.算法较大地改变了无序区的范围,从而减少了比较的次数,但总的比较次数仍为O(n^2).所以算法的平均时间复杂度为O(n^2)。因此.算法2最好的时间复杂度为O(n)。平均,最坏时刻复杂度为O(n^2)

4.冒泡排序改进三:双向扫描

     若记录的初始状态为:只有最轻的气泡位于d[n]的位置(或者最重的气泡位于d[0]的位置),其余的气泡均已经排好序,在上述的三种算法中都要进行n-1趟扫描,实际只需要一趟扫描就可以完成排序。所以对于这种不对称的情况,可以对冒泡排序又做一次改进。在排序的过程中交替改变扫描方向,即先从下向上扫描,再从上向下扫描,来回扫描,这样就得到了双向冒泡排序

void BubbleSort(int array[], int len)
{
     int low, up, index, i;
     low = 0;
     up = len – 1;
     index = low;
     int tmp;
     while(up>low){
          for(i=low; i<up; i++){//从上向下扫描
               if(array[i]>array[i=1]){
                    tmp = array[i];
                    array[i] = array[i+1];
                    array[i+1] = tmp;
                    index = i;
               }
          }
          up = index; //记录最后一个交换的位置
          for(i=up; i>low; i--){//从最后一个交换位置处从下到上扫描
               if(array[i]<array[i-1]){
                    tmp = array[i];
                    array[i] = array[i+1];
                    array[i+1] = tmp;
                    index = i;
               }
          }
          low = index; //记录最后一个交换的位置
     }
}

      从这种算法可以看出.若记录的初始状态是正序(从小到大)的.则一趟扫描即可完成排序.所需的关键比较和记录移动的次数分别达到最小值n-10。即算法最好的时间复杂度为O(n);若初始记录是反序(从大到小)的.则需要进行[n/2]趟排序。如果只有最重的气泡在最上面(或者最轻的气泡在最下面),其余的有序,这时候就只需要比较1趟。但是在最坏的情况下,算法的复杂度也为O(n^2)。因此.算法最好的时间复杂度为O(n),最坏时刻复杂度为O(n^2)

5.实际测试

     分别用随机数生成器生成500500020000个随机的整数,然后排序。每种排序算法跑10次,取平均时间,可以在下表中看到运行的时间。单位为毫秒。

  500随机整数 5000随机整数 20000随机整数
基本排序方法 1.5625
145.3125
2428.125
改进方法一 1.5625 139.0625 2279.6875
改进方法二 1.5625
134.375
2153.125
改进方法三 1.5625
104.6875
1651.5625

 

6.结论

     从上面的表格可以看出,在数据量比较小的时候,这几种算法基本没有任何区别。当数据量比较大的时候,双向扫描冒泡排序会有更好的效率。但是效率并没有根本的提升。因此冒泡排序确实不是我们排序的首选。在数据量比较大的时候,快速排序等会有非常明显的优势。但是在数据量很小的时候,各种排序算法的效率差别并不是很大。那么冒泡排序也会有自己的用武之地。因此,最重要的是熟悉各种算法的性能表现并且根据数据的数量,以及当前运行的环境,开发的进度选择最合适的算法。

 

借鉴 http://blog.chinaunix.net/uid-22744029-id-1770037.html

posted @ 2015-03-03 21:00  xumenger  阅读(573)  评论(0编辑  收藏  举报

业精于勤而荒于嬉,行成于思而毁于随

十万小时的反复练习