前言
本章主要为我们介绍了简单的算法相关概念,对算法是什么有初步的认识,同时了解算法的相关评判参数,体会一种算法从无到有的实现过程,最终完成例题。
一、算法是什么?
算法并没有一种固定的定义,我将它看作一种“解决问题的方法”,换言之,对输入的数据进行特定的处理,从而得到我们需要的结果,这便是算法的作用。
但是,并不是所有解决方法都能称为算法,算法一般都有极强的“类属性”,即对特定类型的问题有着以不变应万变的解决方式,它更像是一种思路。
我们用最常见的例子来帮助大家理解什么是算法。
比方说,我们现在要求解从1到100的所有自然数的和,我们该如何计算呢?最简单的方法当然是老老实实的从1开始加,利用循环,逐个相加,即可得到答案,但显然,这个方法虽然思路简单,但并不是很简洁。
代码实现如下,此处需要注意的是,range()函数生成的可遍历范围为左开右闭区间,因此我们右端点需要选择在101。
# Alkaid#3529
sum_a = 0
for i in range(1, 101):
sum_a += i
print(sum_a)
计算结果为5050,结果正确,但是需要我们思考的是,有没有更简洁的算法呢?
聪明的高斯想出了一个巧妙的解法,将原求和数列复制一份后反转,与原数列相加,再求和,最终得到的结果即为原数列和的两倍,除以2即可得到答案,代码实现如下:
# Alkaid#3529
sum_a = (1 + 100) * 100 / 2
print(sum_a)
这里我们不禁赞叹高斯的聪慧过人,相比较而言,第二种算法更为精巧,无需循环,一次计算即可得到结果。

接下来,我们来看书上的例子:九九乘法表。在各类算法中,九九乘法表应当是我们最开始接触到的几个例子之一,详细的思路我就不再赘述了,代码实现如下:
# Alkaid#3529
print("This is multiplication table")
def multiplication_table():
for i in range(0, 10):
for j in range(1, i + 1):
if i < 5 and i * j < 10 and j > 1:
print(f'{j} * {i} = {i * j}', end=' ')
else:
print(f'{j} * {i} = {i * j}', end=' ')
print('')
multiplication_table()
我们来看一下打印效果:

增加一个对结果的判断可以使得打印效果更为整洁,如果对九九乘法表的思路有疑问的话可以移步我的另一篇文章乘法表输出思路详解。
二、简单算法分析
1.时间复杂度分析
算法的时间复杂度是从时间角度衡量算法的指标,表示算法执行对时间的消耗度,时间复杂度越低,则算法运行所需的时间越短,是衡量算法优劣的重要指标之一。
我们可以通过1—100求和的例子来比较两种算法的时间复杂度,为显著观察两种算法的时间差异,我们不妨把输入规模扩大至10000000,通过导入time模块利用其中的函数记录计算时间。
代码如下(示例):
# Alkaid#3529
import time
def natural_sum(n):
"""
普通方法求和
:param n: 求和的末项
:return: 返回求和结果
"""
ans = 0
while n > 0:
ans += n
n -= 1
return ans
def gaussian_sum(n):
"""
高斯算法求和
:param n: 求和的末项
:return: 返回求和结果
"""
ans = (1 + n) * n / 2
return ans
def test():
t1 = time.process_time()
print(natural_sum(10000000))
t2 = time.process_time()
print(f'natural_sum needs time : {t2 - t1}')
t1 = time.process_time()
print(gaussian_sum(10000000))
t2 = time.process_time()
print(f'natural_sum needs time : {t2 - t1}')
test()
我们可以明显观察到两者所需时间的差异。

2.空间复杂度分析
空间复杂度也是算法必须考虑的因素之一,它是衡量算法所需额外空间大小的指标,表示该算法需要占据的内存大小,空间复杂度越低,表明该算法所需额外空间越少。
书中给出了一个例子,从一副扑克牌中随机抽取五张牌。最直观感受的方法当然是,将一副牌共计54张牌全部存储在一个列表当中,随机生成五个数字,返回这五个数字的索引对应的牌即可。这个方法虽然思路简单,实现容易,但是我们仔细思考可以发现,这种算法需要存储54张牌,所需空间暂且认为较大(在实际应用中这种存储量并不算大,此处为对比分析,认为其较大),我们是否可以通过优化算法,从而减小所需的额外空间呢?
这里,笔者与书中的想法产生了分歧,笔者认为书中的做法较为繁琐,我们不妨简化一下抽取思路,方便起见,此处以随机抽取一张牌为例子,思路大致如下:
首先随机生成1-5中的某个数字,其中,1-4对应扑克牌的四种花色,5对应两张王牌,其次随机生成1-13中的一个数字,如果第一轮生成的数字在1-4范围内,在第二轮生成的数字对应每种花色的相应牌面,;如果第一轮生成的数字为5,则第二轮生成的数字1-7表示小王,8-13表示大王即。这样,我们通过两轮随机数生成,即可随机获得一张扑克牌。但是缺点显而易见,王牌与普通点数牌生成的概率是不同的,但题目中并无硬性要求,暂可忽略。
代码实现就比较简单了,如下:
# Alkaid#3529
import random
print("This is random poker")
poker_suit = ['Spade', 'Heart', 'Club', 'Diamond', 'Joker']
poker_num = ['Jack', 'Queen', 'King']
suit = random.randint(0, 4)
num = random.randint(1, 13)
if suit < 4:
if num < 11:
print(f'{poker_suit[suit]} {num + 1}')
else:
print(f'{poker_suit[suit]} {poker_num[num % 10 - 1]}')
else:
print(poker_suit[suit])
这样相比于第一种方法,显而易见的缩小了额外空间,降低了空间复杂度,但也导致了时间复杂度升高。
三、例题实现
我们来看一下本章的实验题。
实验题一:2020年的新型冠状病毒给全人类带来了灾难。对疫情传播模型进行简单推测,从而预知病毒的扩散速度。其假设如下:假设病毒平均每5天1个人即传染给2个人,如果不加人为控制,预测第31天、61天的感染人数。
我们对题目进行分析,不难得出感染人数与天数的关系,即每过五天,新增感染人数将翻一番,将之转化为代码求解即可。此处笔者与参考答案发生了些许分歧,笔者慎重考虑后选择了自己的解法。
代码如下:
# Alkaid#3529
print("This is viral infection model")
def predictive_model(day):
"""
预测感染人数
:param day: 经过天数
:return: 感染人数
"""
# num = 0 # 计数器:感染人数
# i = 0
# for day in range(0, day, 5): # 间隔为5天
# num += 3 ** i # 指数公式
# i += 1
num = 3 ** int(day / 5)
return num
print(predictive_model(31))
print(predictive_model(61))
总结
本章整体难度不大,旨在带大家对算法有着入门了解,接下来,我们便逐渐进入算法与数据结构的正式学习当中,相信只要我们坚持努力,长风破浪会有时,直挂云帆济沧海!
本文介绍了算法的基本概念,通过求和问题和九九乘法表展示了算法的实现,并分析了时间复杂度和空间复杂度。高斯求和算法在时间和空间上优于传统方法。此外,还探讨了如何减少空间复杂度,例如通过优化扑克牌抽取算法。最后,提供了一个疫情传播模型的简单预测算法。
1121

被折叠的 条评论
为什么被折叠?



