说在前面
索引排序是近年来各类模拟考试中的新宠,很多老师都花了很多力气给学生做各种练习,希望学生能够掌握相关算法,但是由于排序算法本身就变例颇多,再引入索引数组,理解难度进一步增大,让很多学生不知所措。
事实上我们可以把“索引”理解成“数组下标”的别称,就好比超市中存储柜的编号,通过这个编号就能找到相应的存储空间。“索引排序”是和“物理排序”相对应的概念,所谓物理排序就是改变记录(数组元素)存放的物理位置,即直接对数组进行排序;而索引排序通过增加一个索引数组来存储各数组元素的下标,在排序时不需要对原数组进行排序,只需对索引数组排序即可。这样可以大大提高排序的效率,也可以避免因修改原数组的内容而造成的数据混乱。
在本文中,笔者打算使用2个经典的案例,由浅入深地介绍引入索引数组的原因,和利用索引数组将原始数组进行有序输出的原理,进而搞清楚索引排序的本质特征。由于笔者水平有限,表述难免存在不严谨之处,还请各位老师批评指教。
例1. 如下图所示,List1显示了排序前学生的姓名和成绩,点击按钮1或按钮2都能按照成绩从高到低将学生的姓名和成绩显示到List2中。按钮1和按钮2分别采用了不同的方法来实现排序功能,其中Command1_Click()使用选择排序算法对数组score和pname都进行了排序,再输出排序后的姓名和成绩;但是Command2_Click()并没有对数组score和pname进行排序,而是引入了一个辅助数组q,数组q按顺序存储了各学生的序号,q(i)=k表示排名第i的人其序号为k。我们根据学生成绩对数组q进行排序,并按顺序输出学生姓名和成绩。
下面的代码实现了上述功能,请将缺失的代码补充完整:
Const n = 6
Dim score(1 Ton) As Integer '存储学生的成绩
Dim pname(1 Ton) As String '存储学生的姓名
Dim i AsInteger, j As Integer, k As Integer
Private SubForm_Load()
‘将学生成绩和姓名分别存储到数组score和pname,并显示在列表框List1中,代码略
End Sub
Private SubCommand1_Click()
Dim tscore As Integer, tname As String
For i = 1 To n - 1
k = i
For j = i + 1 To n
If ① Then k = j
Next j
If k <> i Then
tscore = score(i): score(i) =score(k): score(k) = tscore
tname = pname(i): pname(i) = pname(k):pname(k) = tname
End If
Next i
For i = 1 To n
List2.AddItem pname(i) + Str(score(i))
Next
End Sub
Private SubCommand2_Click()
Dim q(1 To n) As Integer, tpos As Integer
For i = 1 To n
q(i) = I '初始化排名为i的人序号也是i
Next
For i = 1 To n - 1
k = i
For j = i + 1 To n
If ② Then k = j
Next j
If k <> i Then
tpos = q(i): q(i) = q(k): q(k) =tpos
End If
Next i
For i = 1 To n '按照排名输出学生的姓名和分数
List2.AddItem ③
Next
End Sub
例1.【答案】
① score(j) > score(k)
② score(q(j)) > score(q(k))
③ pname(q(i)) + Str(score(q(i)))
【解析】
“索引排序”是和“物理排序”相对应的概念,所谓物理排序就是改变记录(数组元素)存放的物理位置,即直接对数组进行排序;而索引排序通过增加一个索引数组来存储各数组元素的下标,在排序时不需要对原数组进行排序,只需对索引数组排序即可。Command1_Click()以成绩为关键字,使用选择排序算法对数组score和pname都进行了排序,每趟排序过程中,将score数组中最大值的下标存储在变量k中,故第①空答案为score(j)> score(k);Command2_Click()并没有对数组score和pname进行排序,而是根据学生成绩对索引数组q进行排序,每趟排序过程中,将score数组中最大值的下标存储在q(k)中,故第②空答案为score(q(j))> score(q(k))。
最后要按顺序输出学生姓名和成绩,因为Command2_Click()并没有对数组score和pname进行排序,故不能直接输出pname(i)和Str(score(i)),而是要输出pname(q(i))和Str(score(q(i)))。
例2. 阿福编写VB程序演示索引排序算法,功能如下:先单击“生成”按钮Command1,在数组a中生成n个两位整数,并显示在标签Label1中;再单击“排序”按钮Command2,则对索引数组b排序,将数组a中的元素按升序存储到数组c中,分别将排序后的索引数组b和有序数组c显示在标签Label2和Label3中,并清空原始数组a;最后单击“还原”按钮Command3,将排序前的原始数据存储到数组d,并显示在标签Label4中。运行界面如图所示,请回答下列问题:
(1)当n=6时,若数组元素a(1)到a(6)的值依次为30,50,60,10,20,40,则单击“排序”按钮后,数组元素b(1)到b(6)的值依次为 。
(2)实现上述功能的VB程序如下,请将缺失的代码补充完整。
(3)程序加框处代码有错,请改正。
Const n = 6
Dim a(1 To n) As Integer, b(1 To n) AsInteger
Dim c(1 To n) As Integer, d(1 To n) AsInteger
Private Sub Command1_Click()
Dim i As Integer
For i = 1 To n
a(i) = Int(Rnd * 90) + 10
b(i) = i
Label1.Caption = Label1.Caption + Str(a(i))
Next i
End Sub
Private Sub Command2_Click()
Dim i As Integer, j As Integer, k As Integer, t As Integer
For i = 1 To n - 1
For j = n To i + 1 Step -1
If ① Then
t = b(j): b(j) = b(j - 1): b(j- 1) = t
End If
Next j
Next i
For i = 1 To n
c(i) = ②
Label2.Caption = Label2.Caption + Str(b(i))
Label3.Caption = Label3.Caption + Str(c(i))
Next i
For i = 1 To n
a(i) = 0
Next i
End Sub
Private Sub Command3_Click()
Dim i As Integer
For i = 1 To n
d(i) = c(b(i))
Next i
For i = 1 To n
Label4.Caption = Label4.Caption + Str(d(i))
Next i
End Sub
例2.【答案】
(1)4,5,1,6,2,3
(2)① a(b(j)) < a(b(j - 1))
② a(b(i))
(3)d(b(i)) = c(i)
【解析】
索引排序最本质的特征是把原始数组a的元素下标存储到索引数组b中,因此无论用哪种算法对数组b排序,在排序过程中,都要用b(i)来表示原始数组中第i个元素的下标。
当n=6,数组元素a(1)到a(6)的值依次为30,50,60,10,20,40时,我们把原数组a、索引数组b、有序数组c和还原数组d各个元素的值依次显示如下表所示:
程序使用冒泡排序算法对索引数组b排序,将数组a中的元素按升序存储到数组c中,因为已经将数组a的元素下标存储到了索引数组b中,所以两个相邻元素的下标不再是j和j-1,而是b(j)和b(j - 1),故第①空答案为a(b(j))< a(b(j - 1));排序后数组b存储的是对a中元素按升序排列的下标,若b(i)=k,则表示第i个元素(即排名为i)的下标为k,因此可以找到数组a、b、c之间的关系:c(i) = a(b(i)),从而得到对应数组c的值。
根据数组a、b、c之间的关系:c(i) =a(b(i)),我们既可以由a和b求出c,也可以由b和c求出a,即a(b(i)) = c(i),把数组a改成d,即得到第3题答案d(b(i)) = c(i)。
拓展思考:
在例题1和例题2中,我们分别用选择排序和冒泡排序算法对索引数组进行了排序,其实索引排序的本质并不在于用哪种算法来排序,而是要理解索引数组b中存储的是原始数组的元素下标,因此无论用哪种算法对数组b排序,在排序过程中,都要用b(i)来表示原始数组中第i个元素的下标。
为了帮助大家进一步理解索引排序,我给出了两段分别用插入排序和计数排序实现例题1功能的代码,请认真阅读代码,并将缺失的代码补充完整:
Private Sub Command3_Click() '插入排序
Dim q(1 To n) AsInteger, tpos As Integer
For i = 1 To n
q(i) = i '初始化排名为i的人序号也是i
Next
For i = 2 To n
tpos = ①
For j = i - 1 To 1 Step -1
If score(q(j)) <= score(tpos) Then Exit For
②
Next j
q(j + 1) = tpos
Next i
For i = 1 To n '按照排名输出学生的姓名和分数
List2.AddItem ③
Next
End Sub
Private Sub Command4_Click() '计数排序
Dim q(1 To n) As Integer, tpos As Integer
Dim tong(0 To 100) As Single
For i = 0 To 100 '把桶清空
tong(i) = 0
Next i
For i = 1 To n '将数据装入桶中
tong(score(i)) = ④
Next i
For i = 1 To 100 '从第2个桶开始依次求出每个桶的前缀和
tong(i) = tong(i) + tong(i - 1)
Next i
For i = n To 1 Step -1 '反向填充目标数组(索引数组)
q(⑤ ) = i
tong(score(i)) = tong(score(i)) - 1
Next i
For i = 1 To n '按照排名输出学生的姓名和分数
List2.AddItem ⑥
Next
End Sub
【答案】
① q(i)
② q(j + 1) = q(j)
③ pname(q(i)) + Str(score(q(i)))
④ tong(score(i)) + 1
⑤ tong(score(i))
⑥ pname(q(i)) + Str(score(q(i)))
为了保证解析的原创性和思维的独特性,我都是独立解题后,先不看答案(除非题目不会做),直接把解析写好,再去看答案。
当然,如果发现参考答案有更好的思路,我还是很乐于学习和借鉴的。同时,由于本人水平有限,解析中难免出现疏漏甚至错误之处,敬请谅解。
无论是赞同还是反对我的看法,都请你给我留言。如果你有新的想法,千万不要憋在心里,请发出来大家一起讨论。让我们相互学习,共同进步!
需要本文word版的,可以加入“选考VB算法解析”知识星球参与讨论和下载文件,“选考VB算法解析”知识星球汇集了数量众多的同好,更多有趣的话题在这里讨论,更多有用的资料在这里分享。
我们专注选考VB算法,感兴趣就一起来!
相关优秀文章:
阅读代码和写更好的代码
最有效的学习方式