zoukankan      html  css  js  c++  java
  • 冒泡排序及其算法优化分析

    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
    ", 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
    ", 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

  • 相关阅读:
    避免scrollview内部控件输入时被键盘遮挡,监听键盘弹起,配合做滚动
    红包功能的开发总结
    App启动时间分析
    第三方动画库 Lottie嵌入记录
    加入一个新的团队需要做什么
    OC 面向对象的特性
    为什么说OC是运行时语言?什么是动态类型、动态绑定、动态加载?
    adb pull 和 adb push
    《重构:改善既有代码的设计》重构的方法整理
    《重构:改善既有代码的设计》(二) 代码的坏味道
  • 原文地址:https://www.cnblogs.com/xumenger/p/4311906.html
Copyright © 2011-2022 走看看