1. 场景描述
最近由神仙姐姐刘亦菲主演的电视剧《去有风的地方》带火了一方旅游,这个地方就是云南大理沙溪,被喻为“心灵疗伤的圣地”。那里是风光秀丽,美不胜收。有湖光山色、落日余晖,有清雅的民宿、还有淳朴的民风等等。如果你要自驾前往沙溪镇,导航APP会为你提供多种路线方案供你选择最优方案。例如:你可以选择用时最短的路线;也可选择高速公路最多的线路;还可以选择收费最少的路线等等,总之你可以从容挑选优的出游行车路线。在现实世界中筛选最优化方案的例子不胜枚举。例如:在众多的投资方案中,风险可控的前提下,筛选出投资回报率最高的理财方案;在一揽子的避税方案中,符合法规的前提下,挑选出上税金额最少的报税方案。 趋利避害,选择最优化方案是我们日常生活中经常要做的选择题。我们可以把所有这类问题抽象成为一个数学问题就是:找寻3个数中的最大数或者最小数。你可能会认为这是一个伪命题,因为你能够一目了然地看出3个数中的最大和最小数,无需使用计算机来解决这个简单而无趣的问题。其实不然,如果计算机能够找出3个数中的最大数和最少数,那么它就可以瞬间找出100个数中的最大数和最小数,人工完全做不到。只有充分使用计算机帮助我们筛选出最优化方案,才能真正解放我们的大脑和双手。总之,看似简单的问题,也有不凡的应用场景。 现在我们言归正传,使用Python语言,编写3种不同的算法:枚举法、决策树和比较法实现找出3个数中的最大数,最后我们还要评价三种算法的优劣,从中感悟:大道至简是一种美,那就是算法的朴素之美。
2. 编程思路
众所周知,在计算机软件编程中,同样一个需求问题,可能有不同的解决方法,我们简单地称之为算法。算法的优劣取决于:算法效率、算法复杂度、算法弹性或可延伸性、算法可读性等因素。下面我们分别介绍三种方法:枚举法、决策树和比较法。
2.1 枚举法
所谓的枚举法,就是要利用穷尽的方式,列出x, y ,z 三个数分别成为最大值的所有情况下: x >= y >= z时,x 就是最大值; y >= z >= x时,y就是最大值; z >= y >= x时,z就是最大值。
2.2 决策树
以下是决策树算法寻找最大数的流程图。
2.3 比较法
以下是比较法寻找最大数的流程图。
3. 代码实现
下面是3个不同版本的程序。
3.1 枚举法
程序模块名find_max1.py
"""
find_max1.py : 枚举法求3个数中最大值
"""
def get_max(x, y, z):
"""
枚举法:找出3个数中的最大值
"""
max_val = x # 赋初值
if x >= y >= z:
max_val = x
elif y >= z >= x:
max_val = y
elif z >= y >= x:
max_val = z
return max_val
def main():
# 输入3个数,以逗号分隔
x, y, z = input('Enter x,y,z : ').split(',')
x, y, z = float(x), float(y), float(z)
max_value = get_max(x, y, z)
print('max value = {0}'.format(max_value))
if __name__ == '__main__':
main()
我们可以通过Python交互式解释器进行函数功能测试:
D:\cases\筛选最优化方案>python
Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> import find_max1 as max1 # ①
>>> max1.get_max(1,2,3) # ②
3
>>> max1.get_max(1,3,2)
3
>>> max1.get_max(3,2,1)
3
>>> max1.get_max(2,3,1) # ③
2
>>> quit()
D:\cases\筛选最优化方案>
其中语句①导入模块find_max1.py,并重新命名为max1; 语句②调用模块max1中的函数get_max(),计算最大值; 语句③调用函数get_max(2,3,1),这里没有正确地找出最大值。 究其程序逻辑出错的根本原因是:程序没有穷尽出所有条件或情形,才导致了函数执行的错误结果。具体来说这种穷尽各种情况的方法,出现了遗漏,即遗漏了 y >= x >= z的情况。为了解决这个问题的一种办法增加条件判断,我们按照如下方法,重新修改函数get_max()。
def get_max(x, y, z):
"""
枚举法:找出3个数中的最大值
"""
max_val = x # 赋初值
if x >= y >= z:
max_val = x
elif y >= z >= x:
max_val = y
elif z >= y >= x:
max_val = z
elif y >=x >=z: # ①
max_val = y # ②
return max_val
在以上代码片段中,语句①和语句②就是新增的程序语句。我们再次启动Python交互式解释器,执行以下代码片段:
>>> import find_max1 as max1
>>> max1.get_max(2,3,1)
3
>>>
从代码的执行结果表明,之前出现的问题已经解决。 如果我们对寻找最大值的需求作出一点点延伸:希望从10个数中找出最大数,我们应该怎么做?显然,这种穷尽各种判断条件的方法恐怕是难以胜任了。下面我们将介绍另一种方法,即决策树法。
3.2 决策树
程序模块名find_max2.py。
"""
find_max2.py : 决策树方法找出3个数中最大值
"""
def get_max(x, y, z):
"""
按决策树方法找出最大数
"""
if x >= y:
if x >= z:
max_val = x
else:
max_val = z
else:
if y >= z:
max_val = y
else:
max_val = z
return max_val
def main():
# 输入3个数,以逗号分隔
numbers = input('Enter x,y,z : ').split(',')
x, y, z = map(float, numbers)
max_number = get_max(x, y, z)
print('max_number = {0}'.format(max_number))
if __name__ == '__main__':
main()
同样的是我们可以在Python交互式解释器中调试程序片段。
D:\cases\筛选最优化方案>python
Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import find_max2 as max
>>> max.get_max(1,2,3)
3
>>> max.get_max(2,3,1)
3
>>> max.get_max(3,2,1)
3
>>> quit()
D:\cases\筛选最优化方案>
当然你也可以在Windows命令行窗口中执行程序。
D:\cases\筛选最优化方案>python find_max2.py
Enter x,y,z : 1,3,2
max_number = 3.0
D:\cases\筛选最优化方案>
从以上代码执行情况表明,按照决策树方法实现的程序,正确地找出了最大数。程序代码易读,逻辑清晰。与枚举法相似的情况,决策树法要找出10个数中的最大数,算法复杂度立马飙升,代码量成几何方式增长。按照决策树算法来扩展函数get_max()的功能,我真的是要崩溃了。因此我们需要寻求其他的解决办法。下面我们来介绍比较法。
3.3 比较法
程序模块名find_max3.py
"""
find_max3.py : 比较法求3个数中最大值
"""
def get_max(x, y, z):
"""
比较法找出3个数中的最大数
"""
max_number = x # 假定x是最大值
# 按x,y,z 顺序与最大值比较
if y > max_number:
max_number = y
if z > max_number: # ①
max_number = z # ②
return max_number
def main():
# 输入3个数,以逗号分隔
while True:
try:
x, y, z = map(float, input('Enter x,y,z = ').split(','))
break
except ValueError:
print('data error, try again!')
max_value = get_max(x, y, z)
print('max_number = {0}'.format(max_value))
if __name__ == '__main__':
main()
这版编程可以方便找出3个数中的最大数。代码干净简洁,逻辑简单。如果需要找出10个数中的最大数,我们只需把语句①和语句②复制7遍,做出必要修改即可。 下面我们对上述比较法进行适当修改,使它可以从任意多个数中找出最大数,我们暂且把新版程序取名为:find_max.py。代码内容如下:
"""
find_max.py : 比较法通用版本
"""
def get_max(numbers):
"""
功能:获取最大数
参数:numbers 数字列表
"""
max_val = numbers[0] # ①
for x in numbers[1:]: # ②
if x > max_val:
max_val = x
return max_val
def main():
# 输入n个以逗号分隔的数字
while True:
try:
numbers = map(float, input('Enter x1,x2,...,n = ').split(',')) # ③
numbers = list(numbers)
break
except ValueError:
print('Data error, try again!')
max_number = get_max(numbers)
print('max_number = {0}'.format(max_number))
if __name__ == '__main__':
main()
这一版程序中,函数main()中的输入数据输入,提供的容错机制,防止因数据输入的错误而终止程序执行的异常处理。下面是对几条重要语句的解释。 语句①假定第1个元素是最大值; 语句②从列表的第二个元素开始迭代循环; 语句③从键盘上输入n个数字,期间用逗号相隔;并将输入的数字转换为浮点数。相等与执行了2条语句: numbers = input('Enter x1,x2,...,n = ').split(',') numbers = map(float, numbers) 我们可以启动Python交互式解释器,逐条执行语句。
D:\cases\筛选最优化方案>python
Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> numbers = input('Enter x1,x2,...,n = ').split(',') # ①
Enter x1,x2,...,n = 1,2,3
>>> print(numbers)
['1', '2', '3']
>>> numbers = map(float, numbers) # ②
>>> type(numbers)
<class 'map'>
>>>
>>> numbers = list(numbers) # ③
>>> print(numbers)
[1.0, 2.0, 3.0]
>>>
语句①从键盘上输入n个由逗号相隔的数字; 语句②列表numbers中的元素转换成浮点数,生成一个map对象。这条语句可以用下面的列表推导式替代: numbers = [ float(x) for x in numbers ] 上面语句生成的变量numbers是一个列表。 语句③将map对象转换成列表numbers。
4. 运行效果
综上所述,我们最终的程序版本是find_max.py。下面我们了执行这个程序:
D:\cases\筛选最优化方案>python find_max.py
Enter x1,x2,...,n = 1,2,3,4,5
max_number = 5.0
D:\cases\筛选最优化方案>python find_max.py
Enter x1,x2,...,n = 1,b,2,4
Data error, try again!
Enter x1,x2,...,n = 1,7,101,20
max_number = 101.0
D:\cases\筛选最优化方案>python find_max.py
Enter x1,x2,...,n = 8
max_number = 8.0
D:\cases\筛选最优化方案>
经过测试表明,find_max.py满足业务需求,可以在任意多的数字中,找出最大数。
5. 算法评价
到此为止,要找出3个数中的最大值,我们介绍了3种不同的算法。 枚举法的关键是要穷尽各种条件,可以找出最大值。这种是要防止遗漏穷尽的条件,尤其是从更多的数字(例如10个数字)中找出最大值,其难度极大,几乎不具有可操作性。 决策树算法具有逻辑清析,直观易懂,不易遗漏比较条件的优点。但同样存在问题是,要从更多的数字中找出最大值,算法的复杂度将会显著增加。 比较法是最简单的算法,简单朴素,容易理解,易于扩展。尤其是可以非常方便地扩展成为通用版本。 综上所述,3种算法差异还是很大。对照Python之禅,顺序比较法是最好的。Python之禅推崇的编程哲学是:简单的算法胜于复杂的实现方法;如果一个方法容易被解释清楚,它就是一个好方法。find_max3.py 、find_max.py就是具备这样的特点,它充分地体现了算法的朴素之美,它也是对大道至简的最好诠释。
6. 不重复造*
事实上,Python中有一个内建函数max( ),可以解决在多个数中找出最大值的问题。请看以下程序片段:
D:\cases\筛选最优化方案>python
Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> numbers = [1,2,4,8,2]
>>> max(numbers)
8
>>> quit()
D:\cases\筛选最优化方案>
正如你看到的,程序能实现我们期待的功能。在Python程序开发中推崇这样一种理念,不重复造*。我们把Python或者第三方库中提供的模块和函数称为*。我们在开发项目时,首先要看Python、第三方库中有无现存的“*”,如果有现存的,我们应该优先使用,以此提高程序的开发效率。如果没有现存的*可以利用,这才需要我们自行编写。 结论:结合本案例,解决寻找最大值的问题,使用Python的内建方法是最好的选择。但是这并不意味着在案例中实现的3种算法毫无意义,恰恰相反,它揭示了算法在编程中的核心地位。它给我们的启示就是: 解决同一问题,可以用不同的算法。算法的选择尤其重要,不同的算法,其执行效率、复杂度、可读性和健壮性完全不一样,最终的效果或许是相差甚远。
7. 场景扩展
在本案例中,我们使用三种不同的算法编写寻找最大数的程序,同理我们也可以编程寻找最小数的程序,有兴趣的读者可以自行编写,它对于训练和提升编程动手能力大有裨益。