场景编程集锦 - 寻找最优化方案

时间:2023-01-31 18:00:18

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. 场景扩展

  在本案例中,我们使用三种不同的算法编写寻找最大数的程序,同理我们也可以编程寻找最小数的程序,有兴趣的读者可以自行编写,它对于训练和提升编程动手能力大有裨益。