冒泡排序入门到精通策略探析

发表时间:2021/6/18   来源:《中国教师》2021年第18卷2月6期   作者:胡锋
[导读] 冒泡排序是高三信息技术选考中常见的排序算法,是浙教版《算法与程序设计》的重点内容之一。

        胡锋
        绍兴市高级中学  312000
        【摘要】冒泡排序是高三信息技术选考中常见的排序算法,是浙教版《算法与程序设计》的重点内容之一。针对学生容易入门,但很难熟练应用的弊端。本文通过对冒泡排序算法的特点、冒泡算法的核心代码、冒泡排序的优化、各种冒泡排序变式四个方面对冒泡排序算法进行由浅入深地分析,探讨解题策略,增强学生灵活应变的计算思维能力。
【关键字】冒泡排序  核心代码  优化  变式

一、冒泡排序算法的特点
        冒泡排序从字面上看有冒泡二字,寓意就是水泡从底下逐渐往上冒,最终冒到水面。对于数据排序来讲,每次冒泡,就是将一个最值从底部冒到头部。如果总共N个元素,第1次冒泡产生第1个最值,第2次冒泡产生第2个最值(该最值次于第1个最值)……显然N-1次冒泡后,只剩下一个元素,当剩余一个元素时,就不需要比较和排序了。因此冒泡排序共需要进行N-1次冒泡。当某次冒泡时,先从底部开始比较相邻元素,如果两者顺序符合要求则不交换,否则交换,继续向前推进,直到头部为止。每次冒泡产生一个最值,这个最值不需要参加到下一轮冒泡过程中,因此每次冒泡长度都比前一次短1个长度。因此第1次冒泡比较N-1次,第2次冒泡比较N-2次……。而交换次数必定小于等于比较次数,因为两两比较时不一定满足交换条件。最差请况下,交换次数等于比较次数,如一个完全降序的序列要变成完成升序的序列。

二、冒泡算法的核心代码
        结合冒泡排序算法的特点,冒泡排序共需要进行N-1次,每次冒泡长度比前一次减1。冒泡方向可以从前往后,也可以从后往前。当从前往后冒最大值时排升序;当从前往后冒最小值时排降序;当从后往前冒最大值时排降序;当从后往前冒最小值时排升序;
        将上述文字描述变为VB核心代码(范例中数据存放在数组A(1)至A(N)中):
        【范例1】假设从前往后冒泡排升序
        For  i=1  To  N-1   ‘冒泡排序共需要进行N-1次
          For j=1 To N-i  ‘从前往后冒泡,一般从第1个开始冒泡
                If  A(j)>A(j+1)  Then    ‘当前数比后一个数大
                    C=A(j)               ‘交换A(j)和A(j+1)
                    A(j)=A(j+1)     
                    A(j+1)=C
                End If
            Next  j
        Next  i
        【范例2】假设从前往后冒泡排降序
        只要将范例1中的比较语句A(j)>A(j+1)改为A(j)<A(j+1)即可。
        【范例3】假设从后往前冒泡排升序
        For  i=1  To  N-1   ‘冒泡排序共需要进行N-1次
          For j=N To i+1 Step -1 ‘从前往后冒泡,一般从第N个开始冒泡
                If  A(j)<A(j-1)  Then    ‘当前数比后一个数大
                    C=A(j)               ‘交换A(j)和A(j-1)
                    A(j)=A(j-1)     
                    A(j-1)=C
                End If
            Next  j
        Next  i
        【范例4】假设从前往后冒泡排降序
        只要将范例3中的比较语句A(j)<A(j-1)改为A(j)>A(j-1)即可。
        核心代码的重点:确定每次冒泡的起止位置,相邻元素的比较和交换。
        核心代码的难点:确定每次冒泡的起止位置。
        我们用范例1来说明如何确定每次冒泡的起止位置。
        在这个例子中我们用A(j)和A(j+1)作为相邻元素,那么从前往后冒泡时,每一轮开始肯定是A(1)和A(2)进行比较,显然j应该从1开始。而结束位置每一次都不同,该如何确定呢?不妨列个表观察一下:
        观察发现,每次冒泡的起止位置都与当前的次数有关,即第i次冒泡排序的起止位置是1至N-i。
        还是假设从前往后冒泡排升序,如果我们用A(j)和A(j-1)作为相邻元素,相当于当前位置与前一个位置比较,那么第i次冒泡排序的起止位置是2至N-i+1。
        用相同的方法可以推导出从后往前冒泡时,如果是A(j)和A(j-1)作为相邻元素,则j的起止位置是N至2;如果是A(j)和A(j+1)作为相邻元素,则j的起止位置是N-1至1。

三、冒泡排序的优化
        【优化范例1】当某次冒泡排序时没有发生数据交换,说明数据已经完全有序了,不用继续冒泡过程,从而减少排序次数。
原始数据: 3  5  6  1  9  12
第1次冒泡: 1  3  5  6  9  12    有数据交换
第2次冒泡: 1  3  5  6  9  12    没有数据交换
第3次冒泡: 1  3  5  6  9  12
第4次冒泡: 1  3  5  6  9  12
第5次冒泡: 1  3  5  6  9  12
  显然按照正常冒泡排序,6个元素要进行5次冒泡过程,但第2次冒泡后发现没有数据交换,说明数据已经有序,则第3次、第4次、第5次冒泡可跳过。
        For  i=1  To  N-1   
            Flag=True       ’设定标记
                For j=N To i+1  Step -1
                If  A(j)<A(j-1)  Then    
                    C=A(j)               
                    A(j)=A(j-1)     
                    A(j-1)=C
                    Flag=False           ‘修改标记
                End If
            Next  j
            If Flag Then Exit For        ‘没有交换则退出排序
        Next  i
        修改标记语句“Flag=False”不能用“Flag=Not Flag”。因为当某次冒泡过程中,交换次数为偶数时Flag又变回True。所以做标记的语句不能用来回变换的值的代码。
【优化范例2】当数据的后一段已经有序,我们可以大幅减少比较次数。
    显然某次排序最后发生交换的位置为pos,那么pos后面的序列一定有序。例如100个元素排序,采用从前往后标准冒泡,第1次排序比较99次,第2次排序比较98次。但如果第1次排序时发现最后发生交换的位置是8,则第2次冒泡排序只要从位置1到位置8即可,大大缩短了冒泡长度。
Last=n-1              ‘默认第1次比较n-1次
For  i=1 To n-1
     pos=0
     For  j=1  To  Last           
           If  A(j)>A(j+1)  Then
                c=A(j)
                A(j)=A(j+1)   
                A(j+1)=c
                pos=j     ‘记录发生交换的位置
            End If
     Next j
     Last=pos    ‘最后发生交换的位置作为下次排序的终点
Next i
        程序中“pos=0”不能去掉,因为如果某次排序时没有数据交换,则pos就保留了前一次排序时最后发生交换的位置。从而后面每一次排序的结束位置都被固定。而实际上我们知道没有数据交换就是有序了。有了“pos=0”,那么没有数据交换时,pos的值就是0,那么后面每一次排序都将直接跳过。因为“For  j=1  To  0”的循环一次也不进行。
        
四、各种冒泡排序变式
        第1类:隔项冒泡排序,即奇数位和偶数位上的元素分别排序。一次排序后,奇数位产生一个最值,偶数位产生一个最值,即一次排序产生两个最值,故排序次数为n \ 2。
假设奇数位和偶数位分别排升序
For  i=1 To  n\2
   For  j=1  To  n-2*i
       If  A(j)>A(j+2)  Then
            ‘交换A(j)和A(j+2),代码略
         End If
   Next j
Next i
    如果希望奇数位和偶数位的升降序不同,可以巧妙的利用正负1来控制升降序,可以省下奇数位和偶数位的判断,相应语句修改如下:
        If  f*A(j)>f*A(j+2)  Then
             ‘交换A(j)和A(j+2),代码略
            f=-f
        End If
        第2类:双向冒泡排序,即从前往后冒泡和从后往前冒泡都进行。相当于一次双向冒泡排序中包含两个不同方向的冒泡排序,分别在两端产生一个最大值和一个最小值,共两个最值,故排序次数为n \ 2。
假设排升序
For  i=1 To  n\2
   ‘第1次从第1位开始,    第2次从第2位开始,   …   
‘第1次到第n-1位结束,  第2次到第n-2位结束, …
        For  j=i  To  n-i                      
        If  A(j)>A(j+1)  Then       
           ‘交换A(j)和A(j+1),代码略
          End If
    Next j
‘第1次从第n-1位开始,  第2次从第n-2位开始,…
‘第1次到第2位结束,    第2次到第3位结束,  …
    For  j=n-i  To  i+1  Step -1    
        If  A(j)<A(j-1)  Then        
          ‘交换A(j)和A(j-1),代码略
          End If
    Next j
Next i
    双向冒泡的关键是无论从前往后冒泡还是从后往前冒泡,其冒泡的起止位置是不断变化,相互靠近。
        第3类:特异规则的冒泡排序。如将元素中的奇数偶数分开要求奇数在前并升序,又如将元素中的质数和非质数分开,等等。这种冒泡排序其实和标准冒泡排序最接近,只是控制交换的条件不是简单的比大小,而要根据规则进行相应的改变。
假设将元素中的奇数和偶数分开,奇数在前并升序
        For  i=1  To  N-1   
          For j=1 To N-i  
                If  A(j+1) Mod 2=1 And A(j) Mod 2=0   Then  
                    C=A(j)               
                    A(j)=A(j+1)     
                    A(j+1)=C
                ElseIf  A(j+1) Mod 2=1 And A(j) Mod 2=1 And A(j+1)<A(j) Then
                    C=A(j)               
                    A(j)=A(j+1)     
                    A(j+1)=C
                  End If
            Next  j
        Next  i
        当后面是奇数前面是偶数,则无论大小关系都交换;当前后都是奇数,则后面的更小再交换。
        第4类:调整步长的冒泡排序,标准冒泡排序可以看成步长为1,隔项冒泡排序可以看成步长为2,当然还可以是其他的步长。
        步长为1时所有元素为一组,步长为2时所有元素分为2组。显然步长为m时所有元素分为m组。由此可见只有步长为1时才能将所有元素完成排序,步长为其他数时,只能每组分别排序。为了使每组中的元素大于等于2,因此最大步长一般取元素个数的一半。同时如果步长不变只能每组分别排序,故一次排序后需要缩小步长进行下一次排序,直到步长为1,常见的步长缩小方式为m=m\2。
        既然最后步长要变为1,那调整步长的意义何在?为什么不直接采用步长为1的冒泡排序呢?
        这样做的意义在与可以减少数据交换的次数!
        例如:9  8  7  6  5  4  3  2  1 要排升序
        如果采用标准冒泡,显然9需要和后面的8个元素各交换一次才能冒到最后,一共需要交换8次。
        如果先采用步长为4的冒泡,那么9只要与5和1分别交换一次,一共只需要交换2次。
        假设排升序
        m = n \ 2
        Do While m > 0         ‘步长不为0时,继续排序
           For i = 1 To m      ‘步长m时共有m组数据
              ‘i为起始位置,m为步长,共需进行(n - i) \ m次排序
               For j = 1 To (n - i) \ m  
                 ‘For k = i+m To n Step m  比较A(k)和A(k - m)
                   For k = i To n - m Step m
                       If A(k) > A(k + m) Then
                          C = A(k)
                          A(k) = A(k + m)
                          A(k + m) = C
                       End If
                   Next k
               Next j
           Next i
           m = m \ 2           ‘调整步长
        Loop

五、结语
        冒泡排序是一种比较容易理解的排序方法,一般用于元素不是很多的情况。学习冒泡排序有利于学生对循环结构的理解和应用能力的提升,有利于高三学生
在选考算法中对冒泡排序本质的理解和兴趣的培养。同时冒泡排序也是信息技术选考中的一个高频高点,通常进行适当的变形,有一定难度。要熟练掌握冒泡排序算法,就要熟悉它的工作原理,熟识它的核心代码,有利于提高学生善于分析问题和了解一般规律的能力。
        
        
参考文献:
【期刊】:中国信息技术教育
【题目】:不一样的冒泡排序
【作者】:陈凯
【年(卷),期】:2019年05期
投稿 打印文章 转寄朋友 留言编辑 收藏文章
  期刊推荐
1/1
转寄给朋友
朋友的昵称:
朋友的邮件地址:
您的昵称:
您的邮件地址:
邮件主题:
推荐理由:

写信给编辑
标题:
内容:
您的昵称:
您的邮件地址: