`
finalbone
  • 浏览: 56683 次
  • 性别: Icon_minigender_1
  • 来自: 海边
社区版块
存档分类
最新评论

排序算法-冒泡

阅读更多

1 我要冒泡

   冒泡排序这个名字对于我们来说实在是过于熟悉了。作为一个程序员,如果敢说出自己不会冒泡排序,结局肯定是会被鄙视到火星上去。许多公司到学校去招聘应届毕业生的时候,都会要求写一个冒泡排序。毫无疑问的,冒泡排序就是算法世界里面的HelloWorld。我选择了一个弱智的开始,不外乎想告诫自己不要以非常弱智的方式结束自己的算法学习之旅。为了不使得自己的文章过于直白和缺乏技术含量,因此我加入了一些冒泡排序的改进方法,并且给出了一个非常初级的效率评比方案。这一切的一切,不过是自我安慰而已,一点也不会改变本文菜鸟的本色。但是我还是老老实实地写了这篇文章,希望一个好的开始,会带来一个好的结束,而不是无行浪子的始乱终弃。

2 基本冒泡排序

   冒泡排序的基木思想:设想被排序的记录数组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。

第一次冒泡:20 37 11 42 29
第二次冒泡:20 11 37 42 29
第三次冒泡:20 11 37 42 29
第四次冒泡:20 11 37 29 42

那么就找到了最重的气泡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。

算法如下:

        /// <summary>
        
///     冒泡排序的过程很简单,首先将第一个记录的关键字与第
        
///     二个记录的关键字进行比较,若按升序排序,则当第一个记录的
        
///     关键字大于第二个记录的关键字时,将两个记录交换。然后再比
        
///     较第二个记录和第三个记录的关键字。依次类推,直至第n-1个
        
///     记录和第n个记录的关键字进行比较为止。通过这样的一趟冒
        
///     泡排序,结果使得关键字最大的记录被安置在最后一个记录的位
        
///     置上,即它的最终位置。接着进行第二趟冒泡排序,对前n-1个
        
///     记录进行同样的操作,其结果是使关键字次大的记录被安置到
        
///     倒数第二个位置上。这样,通过n一1趟冒泡排序,就将n-1个记
        
///     录安置到相应的最终位置上,剩下的关键字最小的记录就放在
        
///     第一个位置,从而实现了升序排序。
        
/// </summary>
        
/// <param name="myArray"></param>
        public static void BasicBubble(int [] myArray)
        {
            
for(int i = 0; i < myArray.Length - 1; i++)          //循环的趟数
            {
                
for(int j = 0; j < myArray.Length - 1 - i; j++)//每趟循环的次数
                {
                    
if( myArray[j] > myArray[j+1] )
                    {
                        Swap(
ref myArray[i], ref myArray[i+1]);
                    }
                }
            }
        }

交换数据是一个非常简单的函数。在这儿写出来也只是考虑本文的完整性而已。

        private static void Swap(ref int left, ref int right)
        {
            
int temp;

            temp 
= left;
            left 
= right;
            right 
=temp;
        }


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

3 第一种改进:不做无用功

如果在某一趟循环中没有任何数据交换发生,则表明数据已经排序完毕。那么剩余的循环就不需要再执行了。比如我们的数据按照从小到大排列,那么在第一趟比较就不会有任何数据交换发生。这种改进算法如下:

        /// <summary>
        
/// 设置一个标志位,当没有交换的时候这个标志位不会变化,那么说明数据已经
        
/// 排序好了,就不需要再进行剩余的循环。只有在标志位被重新设置的情况下才会
        
/// 进行剩余的循环。
        
/// </summary>
        
/// <param name="myArray"></param>
        public static void ImproveBubble1(int [] myArray)
        {
            
bool isSorted = false

            
for(int i = 0; i < myArray.Length - 1 && !isSorted; i++)//只有在没有排序的情况下才继续循环
            {
                isSorted 
= true;                                                      //设定排序标志
                for(int j = 0; j < myArray.Length - 1 - i; j++)
                {
                    
if( myArray[j] > myArray[j+1] )
                    {
                        isSorted 
= false;                                             //如果是没有排序,就重新设定标志
                        Swap(ref myArray[i], ref myArray[i+1]);
                    }
                }
            }
        }

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

比较次数:Cmax= n(n-1)/2                           移动次数: Mmax=3n(n-1)/2

因此这种改进方法的最坏时间复杂度也为O(n^2)。在平均情况下.算法可能在中间的某一趟排序完后就终止,但总的比较次数仍为O(n^2).所以算法的平均时间复杂度为O(n^2)。因此,这种算法最好的时间复杂度为O(n)。平均,最坏时刻复杂度为O(n^2)。

4 第二种改进:记录犯罪现场

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

        /// <summary>
        
///     在冒泡排序中,每趟排序实现了将最大(升序)或
        
///     最小(降序)的记录安置到未排序部分的最后位置,即最终位置。
        
///     通过进一步观察研究,由于每趟排序过程中,通过和邻记录关键字两两
        
///     比较,大(升序)或小(降序)的记录在不断地往下沉或往后靠,
        
///     小(升序)或大(降序)的记录在不断往上冒或往前靠。
        
///     每经过一趟排序,在最后次交换位置后而的记录都已经排好序。根据
        
///     上面的思路,对n个记录进行第k趟排序,首先需在第k-1趟排
        
///     序时记下最后交换的位置。然后在第k趟排序时,将第一个记
        
///     录的关键字与第二个记录的关键字进行比较,符合交换条件时,
        
///     进行交换。再比较第二个记录和第三个记录的关键字,依次类
        
///     推,直至第m-1个记录和第m个记录的关键字进行比较,而不
        
///     需要比较至n-k-1个记录。在大部分排序中,m都小于n-k-1
        
///     从而减少了比较趟数和每趟的比较次数。由于在第一趟排序时,
        
///     没有上一趟排序的m值。因此,还要设置m的初始值为n-1。
        
/// </summary>
        
/// <param name="myArray"></param>
        public static void ImproveBubble2(int[] myArray)
        {
            
int m = myArray.Length - 1;
            
int k, j;

            
while( m > 0 )
            {
                
for( k=j=0; j<m; j++)
                {
                    
if( myArray[j] > myArray[j+1])
                    {
                        Swap(
ref myArray[j], ref myArray[j+1]);
                        k 
= j; //记录每次交换的位置
                    }
                }
                m 
= k;        //记录最后一个交换的位置
            }
        }

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

  比较次数:Cmax= n(n-1)/2                         移动次数 Mmax=3n(n-1)/2

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

5 第三种改进:双向扫描,一网打尽

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

        /// <summary>
        
///  对n个记录进行排序时,设up记录了从前面向后面依次进行扫描时最后的交换位置,
        
///  low记录了从后面向前面依次进行扫描时最前的交换位置。
        
///  由上个改进的冒泡排序的原理可知,up后面的记录和low前面的记录都已有序。
        
///  每趟排序都由两次不同方向的比较、交换组成。第一次是从未排好序的第一个记录开始,
        
///  即从low记录开始,向后依次两两比较,如果不符合条件,则交换之,
        
///  直至比较到未排好序的最后一个记录,即up记录为止。
        
///  同时记下最后一次交换的位置,并存于up。第二次是从未排好序的最后一个记录开始,
        
///  即从up记录开始,向前依次两两比较,如果不符合条件,则交换之,
        
///  直至比较到未排好序的第一个记录,即low记录为止。同时记下最后次交换的位置,
        
///  并存于low. 这样,就完成了一趟排序。
        
///  每趟排序都实现了将未排好序部分的关键字大的记录往后移(升序),
        
///  关键字小的记录往前移(升序),从而使两端已排好序(如果是降序,记录移动的方向则相反)。
        
///  未排好序部分的记录的首尾位置分别由low和up指明。
        
///  不断按上面的方法进行排序,使两端已排好序的记录不断增多,
        
///  未排好序部分的记录逐渐减少。即low和up的值不断接近,当low>=up时,
        
///  表明已没有未排好序的记录,排序就完成了。由于在第一趟排序时,
        
///  没有上趟排序的low和up值。因此,还要设置low和up的初始值分别为0和n-1。
        
/// </summary>
        
/// <param name="myArray"></param>
        public static void ImproveBubble3(int [] myArray)
        {
            
int low, up, index, i;
            low 
= 0;
            up 
= myArray.Length - 1;
            index 
= low;

            
while( up > low)
            {
                
for( i=low; i<up; i++)        //从上向下扫描
                {
                    
if(myArray[i]>myArray[i+1])
                    {
                        Swap(
ref myArray[i], ref myArray[i+1]);
                        index 
= i; 
                    }
                }

                up 
= index;                     //记录最后一个交换的位置
                for(i=up; i>low; i--)             //从最后一个交换位置处从下向上扫描
                {
                    
if(myArray[i]<myArray[i-1])
                    {
                        Swap(
ref myArray[i], ref myArray[i-1]);
                        index 
= i;
                    }
                }
                low 
= index;                    //记录最后一个交换的位置
            }
        }

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

6 实际测试

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

Random ran = new Random();
int
 number = 500;
int [] myArray = new int[number];
for(int i=0; i<myArray.Length; i++)
      myArray[i] 
= ran.Next(0, number);

  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

7 结论

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

/******************************************************************************************
 *【Author】:flyingbread
 *【Date】:2007年1月26日
 *【Notice】:
 *1、本文为原创技术文章,首发博客园个人站点(http://flyingbread.cnblogs.com/),转载和引用请注明作者及出处。
 *2、本文必须全文转载和引用,任何组织和个人未授权不能修改任何内容,并且未授权不可用于商业。
 *3、本声明为文章一部分,转载和引用必须包括在原文中。
 ******************************************************************************************/

分享到:
评论

相关推荐

    c++排序算法-冒泡排序

    冒泡排序是一种基础且经典的计算机科学排序算法,尤其在C++编程中常见。它通过不断地比较相邻元素并根据需要进行交换,逐步将较大的元素“冒泡”到序列的末尾,从而实现升序排列。这一过程可以理解为一个逐层推进的...

    详解Java常用排序算法-冒泡排序

    Java排序算法之冒泡排序详解 冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就交换位置。这个过程持续对数列的末尾进行,直到整个数列都排序完成...

    python的排序算法-冒泡排序

    冒泡排序是一种简单的排序算法,通过重复遍历列表,比较每对相邻元素,并在需要时交换它们的位置。这个过程会重复进行,直到没有更多的交换需要进行。 代码的执行流程如下: 1.l0 是一个包含整数的列表 [10, 50, ...

    基于python的排序算法-冒泡排序Bubble Sort

    冒泡排序(Bubble Sort)是一种简单的排序算法,其工作原理是通过重复地遍历待排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列...

    C语言排序算法---冒泡排序法

    冒泡排序是一种简单直观的排序算法,它重复地遍历待排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的...

    排序算法 -- 冒泡排序

    冒泡排序是一种基础且经典的排序算法,它的基本思想是通过不断地交换相邻的逆序元素,使得每一轮排序后,最大的元素“浮”到数组的末尾。这个过程就像水底下的气泡逐渐升至水面一样,因此得名“冒泡排序”。 在Java...

    经典算法的C#源码实现

    经典排序算法 - 冒泡排序Bubble sort 经典排序算法 - 选择排序Selection sort 经典排序算法 - 鸡尾酒排序Cocktail sort 经典排序算法 - 希尔排序Shell sort 经典排序算法 - 堆排序Heap sort序 经典排序算法 - ...

    TIA博途-冒泡排序SCL算法-全局FC库文件-V15版本.zip

    冒泡排序是一种简单的排序算法,它重复地遍历待排序的列表,比较每对相邻元素,如果它们的顺序错误就把它们交换过来。遍历列表的工作是重复进行直到没有再需要交换,也就是说该列表已经排序完成。这个算法的名字由来...

    Java后端算法-冒泡排序和选择排序对比

    本文将深入探讨两种基础且常见的排序算法:冒泡排序和选择排序。这两种算法都是简单直观的排序方法,但它们在性能和适用场景上有所不同。 **冒泡排序**: 冒泡排序是一种交换排序,通过不断比较相邻元素并交换位置...

    最快的排序算法 计算机最快的算法-史上14个最快速算法:孩子的计算能力爆表!大脑堪比计算机!...,排序算法数据结构

    冒泡排序算法是一种简单的排序算法,它的工作原理是通过不断地比较相邻元素,并交换它们以达到排序的目的。冒泡排序算法的时间复杂度为O(n^2),因此它适合小规模的数据排序。 2.选择排序算法 选择排序算法也是一种...

    算法设计与分析-1排序算法性能分析-冒泡/选择/插入/合并/快速排序-pre ppt

    本篇文章将详细讨论几种常见的排序算法:选择排序、冒泡排序、插入排序、合并排序以及快速排序,分析它们的算法原理、时间效率,并通过经验分析验证理论分析的准确性。 **1. 选择排序(Selection Sort)** 选择排序...

    快速排序算法和冒泡排序效率对比

    快速排序和冒泡排序是两种常见的排序算法,它们在计算机科学中扮演着重要的角色,特别是在数据处理和优化程序性能方面。本篇文章将深入探讨这两种排序算法的原理、效率以及它们在C#编程语言中的实现。 首先,让我们...

    VC++多线程实现三种排序算法比较----冒泡排序、快速排序、归并排序

    本篇文章主要探讨了如何在VC++环境中利用多线程技术来实现三种经典的排序算法:冒泡排序、快速排序和归并排序,并对它们的性能进行了比较。 首先,冒泡排序是一种简单的排序算法,它重复地遍历待排序的数列,一次...

    Python算法之---冒泡,选择,插入排序算法.py

    Python算法之---冒泡,选择,插入排序算法.py

    Bubble Sort-冒泡排序算法-少儿编程scratch项目源代码文件案例素材.zip

    冒泡排序(Bubble Sort)是一种基础的排序算法,适合初学者和少儿编程教学。它通过重复遍历待排序的数列,依次比较相邻元素并交换位置,使较大的元素逐渐“冒”到数列的顶端,从而实现排序。在这个项目中,我们将...

    算法-数据结构和算法-9-冒泡排序.rar

    冒泡排序是一种基础且经典的排序算法,它的基本思想是通过重复遍历待排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序...

    冒泡排序 算法(冒泡,选择,插入,数组排序)

    冒泡排序是一种简单的排序算法,它的基本思想是通过重复遍历待排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这...

    c语言排序方式2-冒泡排序算法

    一种简单的的排序方式---冒泡排序,本资源为其源代码

    java排序算法-大全.rar

    1. **冒泡排序**:冒泡排序是一种简单直观的排序算法,通过不断交换相邻的错误顺序元素来逐步完成排序。它重复地走访过要排序的元素,依次比较每一对相邻元素,如果它们的顺序错误就把它们交换过来。走访元素的工作...

    C#-基于C#实现的冒泡排序算法-Bubble-Sort.zip

    冒泡排序是一种基础且历史悠久的排序算法,它通过重复遍历待排序的序列,比较相邻元素并根据需要交换它们的位置,来逐步将序列中的大值“冒”到序列的末尾。在C#中实现冒泡排序,可以深入了解C#的基础语法、控制流...

Global site tag (gtag.js) - Google Analytics