对分查找算法是分治策略的一个典型应用,在历届选考真题卷中都能看到它的身影,也是各种联考和模拟卷的“宠儿”。由于考得太多,常规的对分查找题目已经不能“考倒”学生,一些披着“对分查找”外衣的复杂算法如雨后春笋般冒出来,疯狂地磨砺学生的思维,摧残老师的头发。
“2018学年第一学期慈溪市高三技术期末卷16题”曾经让众多师生摸不着头脑,“2019年11月三地市教学质量检测试卷第12题”更是让人做得云里雾里。
2019年11月三地市教学质量检测试卷第12题解析
一 题目 二 考查知识点对分查找、数组元素求和。要求学生熟悉数组的的基本操作,深刻理解对分查找算法思想,能够利用对分查找算法寻找最优解。
三解析本题是用对分查找算法求k段元素最大和的最小值问题。因为题目提供的代码有些难以读懂,我们先自己编写代码来解决此问题。
为了更好的理解问题,我们把代码模块化处理,可以模仿“2018学年第一学期慈溪市高三技术期末卷16题”的做法,自定义函数check(n, k, ans)来判断把长度为n的数组最多分成k段以后,各段和是否都不大于ans,若是返回真,否则返回假。
函数返回真,说明ans不小于k段元素最大和,是满足条件的解。我们采用线性查找的方法,从小到大逐个猜测ans的值,很明显第一个满足条件的解就是最优解。代码如下:
Private Function check(n As Integer, k As Integer, ans As Integer) As Boolean
t = 0: s = 1 '默认至少分成1段
For i = 1 To n
If t + a(i) <= ans Then
t = t + a(i)
Else '第s段元素和大于ans,则另起一段
s = s + 1
t = a(i)
End If
Next i
check = (s <= k) '若ans比较大,分段数量也可能小于k
End Function
Private Sub Command3_Click()
Dim n As Integer, k As Integer, i As Integer
Dim L As Integer, R As Integer, m As Integer
a(1) = 1: a(2) = 2: a(3) = 3: a(4) = 3: a(5) = 1
n = 5: k = 3: L = 3: R = 10
'从小到大依次查找k段元素最大和,首次满足条件的解即最优解(最小值)
For m = L To R
If check(n, k, m) Then Exit For
Next m
Label1.Caption = "最小值:" & m
End Sub
当然,我们可以采用效率更高的对分查找算法,对分枚举m的值,根据check()函数的反馈来调整m的值。若m太小,不能得到解,则增大m的值;若m较大,可以得到一个解,则将其存储到ans中,并继续减小m,直到获得最优解。相关代码如下:
Private Sub Command4_Click()
Dim n As Integer, k As Integer, ans As Integer
Dim L As Integer, R As Integer, m As Integer
a(1) = 1: a(2) = 2: a(3) = 3: a(4) = 3: a(5) = 1
n = 5: k = 3: L = 3: R = 10: ans = R
Do While L <= R
m = (L + R) \ 2 '对分枚举m的值
If check(n, k, m) Then
ans = m '找到一个解,但不一定是最优解
R = m - 1 '减小m的值
Else
L = m + 1 '增大m的值
End If
Loop
Label1.Caption = "最小值:" & ans
End Sub
上述代码模仿了“2018学年第一学期慈溪市高三技术期末卷16题”的做法,虽然思路比较清晰,但是代码不够简洁。由于程序本身需要实现的功能不多,完全可以省略自定义函数check(),把所有的代码都集中在一个函数中。改进后的代码如下:
Private Sub Command5_Click()
Dim n As Integer, k As Integer, i As Integer
Dim L As Integer, R As Integer, m As Integer
Dim s As Integer, t As Integer
a(1) = 1: a(2) = 2: a(3) = 3: a(4) = 3: a(5) = 1
n = 5: k = 3
L = 3: R = 10
Do While L < R
m = (L + R) \ 2 '因为最优解是满足条件的最小值,为避免死循环,m左偏
t = 0: s = 1 '默认至少分成1段
For i = 1 To n
If t + a(i) <= m Then
t = t + a(i)
Else '第s段元素和大于ans,则另起一段
s = s + 1
t = a(i)
End If
Next i
If s <= k Then'm满足条件,继续寻找比m更小的最优解
R = m '最优解在[L,m]之间
Else
L = m + 1 '最优解在[m+1,R]之间
End If
Loop
'循环结束后L=R
Label1.Caption = "最小值:" & L
End Sub
以上是笔者能想到的相对直观的代码。但是这些代码都与题目提供的代码不一样!那么如何来理解题目中的代码呢?
说实话,真的很难!笔者费了九牛二虎之力总算稍微理顺了思路(但是仍不敢说真正领会了出题者的意图),下面尝试通过添加注释的方式对原题目代码做一些分析:
上述代码最令人费解的地方就是没有直接把m作为第s段元素和的最大值,而是把(m-1)作为和的最大值来处理了,所以它的do while循环条件和内层循环的if条件都不合常理。此处故意挖坑,人为增加了阅读障碍(当然也可能是出题者另有深意,而我未能领会)。
当然,如果仅仅是为了找到正确选项,我们无需完全理解每一句代码的含义,可以采用排除法和特殊值法等技巧来寻找答案。
在本题中,虽然m的含义不好理解,但是s和k的含义还是很明确的,当s<=k时,m是满足条件的解,我们可以进一步减小右边界,反之则增大左边界,故可以排除B、C和D。
为了确保万无一失,我们还可以把数据代入,模拟运行一遍程序。
把选项A的代码代入程序,运行过程如下:
L | R | m | s | k |
3 | 10 | 6 | 3 | 3 |
3 | 6 | 4 | 4 | 3 |
4 | 6 | 5 | 3 | 3 |
4 | 5 | 循环结束,输出最优解L |
A
五拓展思考解析中涉及到的2道题目和普通对分查找算法的区别在于:普通对分查找算法都是试图用平均值m去指向待查找的元素(满足条件的解),一旦找到该元素则马上跳出循环;而我们今天研究的对分查找变例不仅仅要找到满足条件的解,而且要找到最优解,因此不能中途跳出循环,而是要等到循环正常结束后才能确认找到的解为最优解。
虽然对分查找算法的基本框架是固定的,但是实现同一功能的代码有多种写法,只要稍微修改一下循环条件,或对平均值m采取不同的取整方式,就可以写出不同的代码。所以当题目给出的代码与我们预想的不一致时,要尽量站到出题者的角度去理解代码。实在不能理解的时候,可以通过代入具体的数据来模拟运行程序,找出正确答案。
我在这里再给出3道题目,希望大家通过相关练习能增强对此类题目的理解。
第1题:最小距离最大值问题。数组a(n)是一个长度为n+1的递增正整数序列(其中a(0)=0),求从数组中删除m个元素以后(不能删除a(0)),剩下的元素中最小距离的最大值。距离是指当前元素减去前一个元素的差。
例如:当a = (0,2,11,14,17,21,25),m = 2时,返回4。
分析过程:如果删除元素“2”和“11”,剩下的元素为(0,14,17,21,25),最小距离为3,即17-14;如果删除元素“2”和“14”,剩下的元素为(0,11,17,21,25),最小距离为4,即21-17 或者 25-21。
依次分析删除2个数的所有可能情况,可知最小距离的最大值为4。
下面的代码使用对分查找算法求最小距离的最大值,请把缺失的代码补充完整:
Private Sub Command1_Click()
Dim left As Integer, right As Integer, mid2 As Integer
Dim cnt As Integer, i As Integer, k As Integer
left = 1: right = a(n)
Do While left < right
'因为最优解是满足条件的最大值,为避免死循环,mid右偏
mid = (left + right + 1) \ 2
cnt = 0: k = 0
For i = 1 To n '当前距离小于mid,删除元素a[i]
If a(i) - a(k) >= mid Then k = i Else ①
Next i
If cnt <= m Then 'mid满足条件,继续寻找比mid更大的最优解
left = ② '最优解在[mid,right]之间
Else
right = ③ '最优解在[left,mid-1]之间
End If
Loop
List1.AddItem Str(m) + ":" + Str(right)
End Sub
第2题:给定n(n为奇数且小于1000)个整数,整数的范围在[1,m]之间,请使用二分法求这n个整数的中位数。所谓中位数,是指将这n个数排序之后,排在正中间的数。
例如:当a = (8,6,6,3,4,8,1)时,中位数为6。
下面的代码使用对分查找算法求中位数,请把缺失的代码补充完整:
Private Sub Command2_Click()
Dim a(1 To 1000) As Integer
Dim n As Integer, m As Integer, i As Integer
Dim left As Integer, right As Integer
Dim count As Integer, mid As Integer
n = Val(Text1.Text): m = Val(Text2.Text)
For i = 1 To n
a(i) = ① '生成范围在[1,m]之间的随机整数
Next i
left = 1: right = m
Do While left <= right
mid = (left + right) \ 2
count = 0
For i = 1 To n '记录不小于mid的整数数量
If a(i) >= mid Then count = count + 1
Next i
If count > n \ 2 Then
left = ②
Else
right = ③
End If
Loop
Label1.Caption = "中位数:" & right
End Sub
第3题:对分查找插入排序算法。
Private Sub Command7_Click()
Dim n As Integer, i As Integer, L As Integer, R As Integer, key As Integer
n = 10
For i = 1 To n '产生n个随机整数赋值给数组a并显示在list1中
a(i) = Int(Rnd * 10) + 1
List1.AddItem Str(a(i))
Next i
For i = 2 To n
key = a(i)
L = 1: R = i - 1
Do While L <= R
m = (L + R) \ 2
If a(m) <= key Then L = ① Else R = ②
Loop
For j = i - 1 To L Step -1
a(j + 1) = a(j)
Next j
a(L) = ③
Next i
For i = 1 To n '排序结果显示在list2中
List2.AddItem Str(a(i))
Next i
End Sub
六拓展思考答案(1) ① cnt = cnt + 1
② mid
③ mid – 1
(2) ① Int(Rnd * m + 1)
② mid + 1
③ mid – 1
(3) ① m + 1
② m - 1
③ key
写在后面为了保证解析的原创性和思维的独特性,我都是独立解题后,先不看答案(除非题目不会做),直接把解析写好,再去看答案。
当然,如果发现参考答案有更好的思路,我还是很乐于学习和借鉴的。同时,由于本人水平有限,解析中难免出现疏漏甚至错误之处,敬请谅解。
无论是赞同还是反对我的看法,都请你给我留言。如果你有新的想法,千万不要憋在心里,请发出来大家一起讨论。让我们相互学习,共同进步!
需要本文word版的,可以加入“选考VB算法解析”知识星球参与讨论和下载文件,“选考VB算法解析”知识星球汇集了数量众多的同好,更多有趣的话题在这里讨论,更多有用的资料在这里分享。
我们专注选考VB算法,感兴趣就一起来!
相关优秀文章:
阅读代码和写更好的代码
最有效的学习方式
选考VB算法解析之2018年11月高考真题卷第17题
选考VB算法解析之2017年11月高考真题卷第17题
最小距离最大值问题
插入排序算法及其变例分析