vlambda博客
学习文章列表

关于选择排序并去重的几种算法分析

说在前面

    选择排序是选考VB算法中最重要的排序算法之一(另一个是冒泡排序),它不仅出现在选择题中,还经常出现在大题目中。从备考复习的角度来说,我们不仅要求学生掌握选择排序的基本算法思想和代码结构,还要融会贯通,理解它的一些变例拓展。

选择排序并去重,是其典型变例,代码实现复杂多变,值得细细研究。本文是笔者近段时间的专题复习内容,整理归纳后分享给大家,希望能对你有所启发。由于笔者水平有限,表述难免存在不严谨之处,还请各位老师批评指教。


经典例题: 

关于选择排序并去重的几种算法分析

关于选择排序并去重的几种算法分析

关于选择排序并去重的几种算法分析


算法分析:

(一)  夯实基础

  1. 经典选择排序算法代码实现及说明

Private SubCommand1_Click()  算法1

    Dim i As Integer, j As Integer, k AsInteger, t As Integer

    For i = 1 To n - 1

        k = i 

        For j = i + 1 To n   'k记录当前最小值的位置

            If a(j) < a(k) Then k = j

        Next j

        If i <> k Then '将最小值与a(i)交换

            t = a(k): a(k) = a(i): a(i) = t

        End If

    Next i

End Sub

经典选择排序算法为长度为n的数组a排序时,总共需要排序n-1趟(只剩下一个元素时无需再排序),外层循环变量i既可以表示排序的趟数,也可以理解成待排序区间的左边界。本算法的巧妙之处在于派出了一个侦察兵k(也可以理解成变量i的替身),联合内层循环变量j,通过扫描待排序区间,找到最小元素的位置,并用k指向它;内层循环结束后,比较ik的值,若不相等,则交换a(k)a(i),即把最小元素交换到待排序区间的最左端。这样每排序一趟,最多交换次数为1,效率较高。

   2.低效选择排序算法代码实现及分析

Private SubCommand2_Click()   ‘算法2

    Dim i As Integer, j As Integer, t AsInteger

    For i = 1 To n - 1

        For j = i + 1 To n

            If a(j) < a(i) Then '将当前遇到的更小值与a(i)交换

                t = a(i): a(i) = a(j): a(j) = t

            End If

        Next j

    Next i

End Sub

与经典选择排序算法相比,低效选择排序算法省略了变量k,代码更为简短。但正是因为缺少了侦察兵k的帮助,幕后主使i只能亲自出马,直接与内层循环变量j接触,并且步步为营,只要找到一个比a(i)小的a(j),就马上交换二者的值,以确保左边界值最小。这样每排序一趟,都有可能发生多次交换,效率较低。

(二)  例题剖析

本题采用低效选择排序算法对数组a进行排序并去重工作。其中第②空所在代码段用于排序,第③空所在代码段用于去重,每当遇到a(j)a(i)相等,则删除a(j),因为无需保持元素之间的相对位置不变,程序直接使用尾元素a(bottom)来覆盖a(j),以实现删除a(j)的目的,同时让bottom减一。

需要说明的是本题中外层循环使用for语句是不够妥当的,因为bottom的值在程序运行过程中发生了变化,但是VB语言中for循环语句的终值在第一次执行的时候就已经计算出来了,之后即使bottom的值发生变化,但是for循环执行的次数是不会改变的;到了后面有可能出现i>=bottom,但是循环依然还在执行——当然由于此时内层循环条件不满足,内层循环不再执行,所以不会影响程序结果,但有可能给考生带来困惑,最好是把for循环语句改成do循环语句,可读性更强。

答案:(1)B(2) Int(Rnd*20) + 1;② a(i) > a(j);③ a(j) = a(bottom)

(三)  同类拓展

我们也可以采用经典选择排序算法对数组a进行排序并去重工作,为了增强程序的可读性,我们把外层循环写成do循环语句的形式。

Private SubCommand3_Click()   ‘算法3

    Dim i As Integer, j As Integer, k AsInteger, t As Integer

    Dim bottom As Integer

    bottom = n: i = 1

    Do While i < bottom

        k = i

        For j = bottom To i + 1 Step -1

            If a(j) < a(k) Then

                k = j

            ElseIf a(j) = a(i) Then

                a(j) = a(bottom)

                If k = bottom Then k = j

                bottom = bottom - 1

            End If

        Next j

        If k <> i Then

            t = a(k): a(k) = a(i): a(i) = t

        Else '为什么只有当a(i)恰好为最小值时才执行i=i+1

            i = i + 1

        End If

Loop

End Sub

在算法3中,我们引入了侦察兵k,让k指向最小元素的下标。当a(j)a(i)相等时,我们使用a(bottom)来覆盖a(j)的值,同时让bottom减一。需要注意的一种特殊情况是,当k 恰好指向bottom时,若用a(bottom)覆盖了a(j),则需将k指向j,确保k总是指向当前的最小值。内层循环结束后,若k <> i,则交换a(k)a(i)的值,并保持i的值不变;否则说明a(i)本来就是最小值,可以令i增一。

当然我们也可以从左向右扫描待排序区间进行排序并去重工作,这样保证k<=j,则在用a(bottom)覆盖a(j)时,不用考虑更新k的值;但由于需要重新处理a(j),故必须保持j原地不动。

Private SubCommand4_Click()  算法4

    Dim i As Integer, j As Integer, k AsInteger, t As Integer

    Dim bottom As Integer

    bottom = n: i = 1

    Do While i < bottom

        k = i: j = i + 1

        Do While j <= bottom

            If a(j) < a(k) Then

                k = j

            ElseIf a(j) = a(i) Then

                a(j) = a(bottom)

                bottom = bottom - 1

                j = j - 1 '想想为什么?

            End If

            j = j + 1

        Loop

        If k <> i Then

            t = a(k): a(k) = a(i): a(i) = t

        Else 

            i = i + 1

        End If

    Loop

End Sub

算法3和算法4目的都是删除与a(i)相等的元素,共同点是只有当a(i)恰好为最小值时才执行i=i+1;不同点是扫描方向不同,需要考虑的问题也不一样。

除了删除与a(i)相等的元素,我们也可以删除与a(k)相等的元素,这样确保每趟扫描过后,最小元素都只剩下一个,将其交换到最左端以后,可以放心地让i增一。

同样的,我们在内层循环有向左扫描和向右扫描两种方式,代码分别如下:

Private SubCommand5_Click() 算法5

    Dim i As Integer, j As Integer, k AsInteger, t As Integer

    Dim bottom As Integer

    bottom = n: i = 1

    Do While i < bottom

        k = i

        For j = bottom To i + 1 Step -1

            If a(j) < a(k) Then

                k = j

            ElseIf a(j) = a(k) Then

                a(j) = a(bottom)

                If k = bottom Then k = j

                bottom = bottom - 1

            End If

        Next j

        If k <> i Then t = a(k): a(k) =a(i): a(i) = t

        i = i + 1

    Loop

End Sub

Private SubCommand6_Click()  算法6

    Dim i As Integer, j As Integer, k AsInteger, t As Integer

    Dim bottom As Integer

    bottom = n: i = 1

    Do While i < bottom

        k = i: j = i + 1

        Do While j <= bottom

            If a(j) < a(k) Then

                k = j

            ElseIf a(j) = a(k) Then

                a(j) = a(bottom)

                bottom = bottom - 1

                j = j - 1

            End If

            j = j + 1

        Loop

        If k <> i Then t = a(k): a(k) =a(i): a(i) = t

        i = i + 1

    Loop

End Sub

综合比较上述选择排序并去重的算法实现,例题所用的算法在排序方面效率较低,但去重效率高(一趟扫描下来能将所有小于a(i)的元素全部去重);算法3和算法4采用经典选择排序算法,排序效率较高,但每次只能将与a(i)相等的元素去重,去重效率较低;算法5和算法6是笔者推荐的算法,它们的排序效率和去重效率均高于前者,综合得分最高。

(四)  拓展思考

除了选择排序,冒泡排序也是选考VB算法中要求熟练掌握的算法,那么冒泡排序并去重的代码该怎么写呢?

请模仿上述代码,根据经典冒泡排序算法的代码结构,写出一段冒泡排序并去重的代码实现。

(五)  拓展思考答案

Private SubCommand7_Click() 算法7

    Dim i As Integer, j As Integer, t AsInteger

    Dim k As Integer, bottom As Integer

    bottom = n: i = 1

    Do While i <= bottom - 1

        For j = bottom To i + 1 Step -1

            If a(j) < a(j - 1) Then

                t = a(j): a(j) = a(j - 1): a(j- 1) = t

            ElseIf a(j) = a(j - 1) Then

                a(j) = a(bottom)

                bottom = bottom - 1

            End If

        Next j

        i = i + 1

Loop

End Sub


为了保证解析的原创性和思维的独特性,我都是独立解题后,先不看答案(除非题目不会做),直接把解析写好,再去看答案。

当然,如果发现参考答案有更好的思路,我还是很乐于学习和借鉴的。同时,由于本人水平有限,解析中难免出现疏漏甚至错误之处,敬请谅解。

无论是赞同还是反对我的看法,都请你给我留言。如果你有新的想法,千万不要憋在心里,请发出来大家一起讨论。让我们相互学习,共同进步!


需要本文word版的,可以加入“选考VB算法解析”知识星球参与讨论和下载文件,“选考VB算法解析”知识星球汇集了数量众多的同好,更多有趣的话题在这里讨论,更多有用的资料在这里分享。

我们专注选考VB算法,感兴趣就一起来!



相关优秀文章: