Python每日一练 10——for循环
文章目录
- Python每日一练 10——for循环
- 一、for循环介绍
- 二、实例一:等差数列前n项和
- 三、实例二:计算阶乘
- 四、实例三:拉马努金法计算圆周率
- 五、实例四:百钱买百鸡
一、for循环介绍
for循环一般用于循环次数可确定的情况下,一般也被称为遍历循环。
for 语句可以依据可迭代对象中的子项,按他们的顺序进行迭代。这些可迭代对象包括:range、字符串、列表、集合、字典和文件对象等可迭代数据类型。
for循环基本结构如下:
for 循环变量 in 可迭代对象:
语句块
需要注意,for开头的语句末尾一定是冒号结尾,其后面至少有一行语句需要在缩进的语句块中。缩进语句块中的语句重复执行多次,直到for语句遍历完可迭代对象。
程序执行时,从可迭代对象中逐一提取元素,赋值给循环变量,每提取一个元素执行一次语句块中的所有语句,总的执行次数由可迭代对象中元素(项)的个数确定。
二、实例一:等差数列前n项和
输入一下正整数,计算从1到这个正整数(包括该数本身)的所有整数的和。
range(1,n+1) 可生成1,2,……,n的序列,所以等差数列前n 项和的问题,可以用range实现:
算法描述:
- 先设置一个初值为0的变量
- 遍历由range()产生的整数序列,每得到一个整数就加到变量上
- 输出变量的值,即为所有整数的和
n = int(input()) # 将输入的字符转成整型,例如输入 100
sum_of_i = 0 # 设累加容器初值为0
for i in range(1, n+1): # 遍历1,2,...n的数列
sum_of_i = sum_of_i + i # 将当前项加到累加容器上,注意此处要缩进,表示循环执行
print(sum_of_i) # 输出累加总和,5050
运行的结果是:
100
5050
这个问题也可以直接用sum()函数结合range()函数来实现。将range()函数直接作为sum()函数的参数,可以直接获得range()函数所生成序列中全部元素的和。例如计算从1到n的加和可以写为以下程序:
n = int(input()) # 将输入的字符转成整型,例如输入 100
print(sum(range(1, n + 1))) # sum()函数可返回序列参数的元素的和,输出 5050
还可以简写成一段代码:
print(sum(range(1, int(input()) + 1))) # sum()函数可返回序列参数的元素的和,输出 5050
改变range()函数中start、stop、step的值,便可以计算不同等差数列中连续n项和了,例如:
print(sum(range(1, 100, 2))) # 100以内奇数的和2500
print(sum(range(0, 101, 2))) # 不超过100的偶数的和2550
print(sum(range(0, 101, 5))) # 不超过100的偶数5的整数倍的数的和1050
print(sum(range(50, 80, 5))) # 序列50 55 60 65 70 75的和为375
运行的结果是:
2500
2550
1050
375
三、实例二:计算阶乘
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1,自然数n的阶乘写作n!
range(1,n+1) 可生成1,2,……,n的序列,阶乘是数列前n 项积的问题,可以用range实现:
算法描述:
- 先设置一个初值为1的变量
- 遍历由range()产生的整数序列,每得到一个整数就乘到变量上
- 输出变量的值,即为所有整数的积,也就是阶乘值
n = int(input()) # 将输入的字符转成整型,例如输入6
fact = 1 # 设阶乘初值为1,n为0时不进入循环,0的阶乘为1
for i in range(1, n + 1): # 遍历1,2,...n的数列
fact = fact * i # 将当前项加到累加容器上
print(fact) # 输出累加总和,720
运行的结果是:
6
720
math库中有一个用于计算阶乘的函数factorial(n),可以调用函数简化程序设计。
import math
print(math.factorial(6)) # factorial() 为计算阶乘的函数
结果跟上面的一样。
四、实例三:拉马努金法计算圆周率
之前我们学习过的Leibniz公式计算圆周率的方法存在收敛慢的问题。拉马努金曾经提出过很多收敛速度极快的求π的公式,比如这个拉马努金在1914年发布的以他自己名字命名的著名公式可用于计算圆周率:
公式中的希腊字母∑,英文译音是Sigma, 表示数学中的求和号,主要用于求多项数之和,公式展开可以表示为k从0到n时各项的累加,可以用循环实现。这个公式收敛速度极快,累加3次时,就可以达到math.pi相同的精度。
from math import factorial # 导入math库中的factorial函数
n = int(input()) # 输入正整数表示累加项数
result = 0 # 累加初值为0
for k in range(n): # 重复执行n次累加
result = result + 2 * 2 ** 0.5 / 9801 * factorial(4 * k) * (1103 + 26390 * k) / (factorial(k) ** 4 * 396 ** (4 * k))
pi = 1 / result # 累加结果取倒数为圆周率值
print(pi) # 输出圆周率值
运行的结果是:
3
3.141592653589793
for循环可以多重嵌套使用,每增加一层循环多一层缩进,最内层循环体内的语句执行的次数为各重循环次数相乘。如果循环语句的循环体中又出现循环语句,就构成多重循环结构。for和while都支持多重循环,且可以混用。一般常用的有二重循环和三重循环。循环层数越多,运行时间越长,程序越复杂,最内层程序语句执行的次数是各层循环次数的乘积。
五、实例四:百钱买百鸡
我国古代数学家张丘建在《算经》一书中提出的“百钱买百鸡”的数学问题,题目意思是1只公鸡5文钱、1只母鸡3文钱、3只小鸡1文钱。计算并输出有几组可能的解?
若用数学方法求解,3个未知量,只能列2个方程,不能直接求解。
在计算机领域,这个问题可以用枚举算法求解,枚举算法的思想是:将问题的所有可能的答案一一列举,然后根据条件判断此答案是否合适,保留合适的,舍弃不合适的。
基本思路如下:
(1)确定枚举对象、范围和判定条件。
(2)逐一枚举可能的解并验证每个解是否是问题的解。
算法步骤:
(1)确定解题的可能范围,不能遗漏任何一个真正解,同时避免重复。
(2)判定是否是真正解的方法。
(3)为了提高解决问题的效率,使可能解的范围将至最小
我们先用一个规模较小的问题为例。日常用的皮箱的经常是三位的密码锁,当忘记密码时,可以采用逐位猜测的方法试出来密码,我们知道,3个数字的全部组合只有1000个,所以我们试1000次一定可以找到密码。
具体操作方法:
-
1.先固定第1位为0
- b. 第2位设0
- ⅰ. 第3位从0试到9
- c. 重复b并依次设1-9
- b. 第2位设0
-
2.重复1并依次设第1位为1-9
算法:
1.第1位数遍历从0到9的数字
2.第2位数遍历从0到9的数字
3.第3位数遍历从0到9的数字
4.比较当前的三位数是否与密码相同,若相同
5.输出当前三位数
import random # 导入随机数模块
keys = random.randint(100, 999) # 随机产生一个三位整数
print(keys) # 查看当前生成的随机数,每次结果不同
for i in range(10): # 猜测百位上的数字
for j in range(10): # 猜测十位上的数字
for k in range(10): # 猜测个位上的数字
# 三个数字拼接为一个三位整数,若此三位数与随机产生的相同,则找到密码
if i * 100 + j * 10 + k == keys:
print(f'密码是{i}{j}{k}')
运行的结果是:
947
密码是947
参考找回密码的程序,将问题规模扩大,遍历公鸡、母鸡和小鸡的数量都从1到100,一定可以找到所有正确的解。
for cock in range(1, 101): # 公鸡数量不为0且小于或等于100
for hen in range(1, 101): # 母鸡数量不为0且小于或等于100
for chicken in range(1, 101): # 小鸡数量大于0小于等于100且是3的倍数
# 鸡的总数为100,钱的总数为100
if cock + hen + chicken == 100 and 5 * cock + 3 * hen + chicken // 3 == 100 and chicken % 3 ==0 :
print(f'公鸡{cock}只,母鸡{hen}只,小鸡{chicken}只')
运行的结果是:
公鸡4只,母鸡18只,小鸡78只
公鸡8只,母鸡11只,小鸡81只
公鸡12只,母鸡4只,小鸡84只
这是一个三重循环,最内层循环中的if语句的执行次数约为:100 * 100 * 100 = 100万次,这是一个很大的数字,计算时间开销也很大,算法的效率不高。我们可以用“剪枝”这个方法,剪枝的思想就是剪掉不可能出现的情况,避免计算机多余的运算。
我们可以用range(3, 101, 3)产生3的整数倍的数列,小鸡数量是3的倍数,所以可取的值一定在这个数列中。
for cock in range(1, 101): # 公鸡数量不为0且小于或等于100
for hen in range(1, 101): # 母鸡数量不为0且小于或等于100
for chicken in range(3, 101, 3): # 小鸡数量大于0小于等于100且是3的倍数
# 鸡的总数为100,钱的总数为100
if cock + hen + chicken == 100 and 5 * cock + 3 * hen + chicken // 3 == 100:
print(f'公鸡{cock}只,母鸡{hen}只,小鸡{chicken}只')
以上程序执行次数约为:100 * 100 * 33 = 33万次,相比之前减少了,但数字仍然很大。
实际上当公鸡和母鸡数量x, y 确定的情况下,小鸡的数量 z 可由100 – x - y计算,并不需要用循环进行遍历,可将其用两重循环实现求解。
for cock in range(1, 101): # 公鸡数量不为0且小于或等于100
for hen in range(1, 101): # 母鸡数量不为0且小于或等于100
chicken = 100 - cock - hen # 小鸡数量可由公鸡和母鸡数量计算得到
if chicken % 3 == 0 and 5 * cock + 3 * hen + chicken // 3 == 100:
print(f'公鸡{cock}只,母鸡{hen}只,小鸡{chicken}只')
以上程序执行次数约为:100 * 100 = 1万次,效率提高了33倍,我们还可以继续减少执行次数。
由于要求每种鸡数量都不能为0,所以公鸡最多只能买19只,母鸡最多只能买32只,这样继续裁剪,减少外面两层循环的次数:
for cock in range(1, 20): # 公鸡数量不超过20
for hen in range(1, 33): # 母鸡数量不超过33
chicken = 100 - cock - hen # 小鸡数量可由公鸡和母鸡数量计算得到
if chicken % 3 == 0 and 5 * cock + 3 * hen + chicken // 3 == 100:
print(f'公鸡{cock}只,母鸡{hen}只,小鸡{chicken}只')
以上程序执行次数约为:19 * 32 = 608次,相比之前的几次次数减少了很多很多。
从结果中可以发现这样的一个规律:
公鸡是4的倍数,母鸡是7的递减率,小鸡是3的递增率,为了确认这一规律,数学上推导一下这个不定方程:
x + y + z = 100
5x + 3y + z/3 = 100
消去z可得:7x + 4y = 100
由此可得:
y = 25 - (7/4)x
z = 75 + (3/4)x
因为0 < y < 100,且是自然数,则可得知 x 必为4的整数倍的正整数才能保证y 和 z 都是整数,x 最大值必小于16 才能保证y 值为正数,所以x值只能取4、8、12,这样只循环3次就可以找到所有可能的解,所有我们可以继续优化代码提高效率:
for cock in range(4, 16, 4): # cock 初值为4,小于16,步长为4
hen = 25 - cock // 4 * 7 # 整除“//”符号保证运算结果仍为整数
chicken = 75 + cock // 4 * 3
print(f'公鸡{cock}只,母鸡{hen}只,小鸡{chicken}只')
尽可能减少循环嵌套的层数,让代码趋于扁平,使逻辑更简单,更容易阅读、理解和维护代码。需多重循环求解时,可以将内层循环的功能定义成函数,将二重循环转换为两个一重循环,使代码逻辑更清晰。