4.2 循环语句
循环语句的基本思想就是重复,通过反复执行某些语句, 来满足重复计算的要求。在编写 MATLAB 程序时, 循环是一项非常重要且常用的技术,它允许我们有效地执行相同或类 似的操作多次,而不必每次都手动重复相同的代码。
在本节中, 我们将深入探讨两种主要的循环语句: for-end 语句和 while-end 语句。这两 种循环语句能够以不同的方式控制循环的次数,以及在何时终止循环。其中, for-end 语句用 于事先已知循环次数的情形,如果在循环开始之前, 我们已经知道了循环的次数,那么我们 通常使用 for-end 语句;而 while-end 语句则更适合在循环的次数未知的情况下使用。
4.2.1 for-end 语句
for-end 语句(简称 for 循环) 用于事先已知循环次数的情形,其语法如下:
在该语法中, 循环变量是用于迭代的变量名, 它会在每次循环迭代中从向量或矩阵中取 出一列的值。数值向量或者矩阵则表示了循环变量可以取值的范围, 通常根据实际需要事先 给定。 一旦循环变量遍历完数值向量或者矩阵中的所有值,循环就会结束。
下面我们来看几个简单的例子:
通过上面三个例子可以看出, 一旦给定具体的数值向量或者矩阵, for 循环的次数就固定 了,取决于向量或者矩阵中有多少列。
(1)MATLAB 的断点调试功能
下面我们来看 for 循环的例子(一定要看讲解视频, 会介绍 MATLAB 的断点调试功能)。
(1)不使用 sum 函数,计算行向量 x 中所有元素的和。
(2)计算当 n 等于 100 时,下面式子的结果:
(3)计算当 n 从 1一直取到 100 时,上一小问式子的计算结果,并将计算结果保存到一
个长度为 100 的行向量 S 中(S 中第 i 个元素表示 y(i)的结果)。
(4)计算从公元 1 年到公元 9999 年间,有多少个闰年。闰年的判读条件是年份能够被 4 整除,但不能被 100 整除,或者年份能够被 400 整除。
(5)一个三位正整数各位数字的立方和等于该数本身则称该数为水仙花数,例如:,则 153 是水仙花数。请你找出所有的水仙花数并将其保存到向量 S 中。
(7)生成一个 5 行 8 列的矩阵,矩阵中每个元素都是在区间[1, 10]上取值的随机整数。 接下来请循环每一列, 若发现同一列的五个元素各不相同, 则保留该列; 若该列中有重复的 元素则删除该列。
(2)for循环的使用注意事项
在使用 for 循环时,需要注意以下事项:
(1)若 for 语句后面的向量或者矩阵为空,则循环一次也不会被执行。
(2)for 语句后面的向量或者矩阵只会在循环开始时使用一次, 向量或者矩阵元素一旦确定将不会再改变。即使你在循环体中改变向量或者矩阵的值,循环变量的值也不改变。
(3)可以在循环体中修改循环变量的值,但当程序执行流程再次回到循环开始时,循环 变量会自动恢复成向量或者矩阵的下一列元素。
4.2.2 while-end 语句
除了 for-end 语句之外,MATLAB 还提供了另一种强大的循环语句: while-end 语句(简 称 while 循环)。与 for 循环不同, while 循环的特点在于它允许我们在不知道具体循环次数的情况下执行循环体,这种灵活性使得 while 循环在某些情境下非常有用,尤其是当我们需要 满足某些某些条件时才执行循环操作。
在这里, 表达式通常是一个判断条件,当这个条件为 true 时,循环会不断的迭代执行下 去; 一旦表达式的值变为 false,循环就会被终止,程序将跳出循环体。
这段代码的计算思路如下:
- 首先,我们初始化斐波那契数列的前两个元素 a(1) 和 a(2),它们都被初始化为 1。
- 我们引入了一个索引变量 n ,用于表示当前计算的斐波那契数列的第 n 项。初始时, n 被设置为 2,因为我们已经知道了前两项。
- 进入 while 循环,条件是 a(n) 小于等于 99999。这表示代码将持续计算斐波那契数列, 直到找到第一个大于 99999 的元素为止。
- 在循环中,首先将索引变量 n 变成 n+1,准备计算下一个斐波那契数。
- 接着,我们使用递推关系 a(n) = a(n- 1) + a(n-2) 计算下一个斐波那契数,并将其存储 在向量 a 中的索引变量 n 的位置。
- 循环会继续,不断计算下一个斐波那契数,直到条件 a(n) <= 99999 不再满足。
- 一旦找到第一个大于 99999 的斐波那契数,循环结束。
- 最后,输出向量 a 中的最后一个元素,它就是第一个大于 99999 的元素。
注意:对于循环次数已知的题目,也可以使用 while 循环进行求解,但求解过程没有 for循环那么直观。例如要计算 等于 100 时,的值,可以使用下面的代码进行求解:
(1)while循环的使用注意事项
在使用 while 循环时,需要注意以下事项:
(1) 如果不小心执行了一个无限循环(即永远不会自行结束的循环,又称死循环),可 以在脚本编辑器或命令行窗口中按下快捷键 Ctrl+C 来中断程序的运行。
(2)while 后面表达式的计算结果不一定非得是逻辑值 1 或 0 。如果表达式的计算结果 是一个数值常数, 则只有当这个常数为非零值时循环才会进行; 若表达式的计算结果是一个 数值向量或者矩阵,则仅当该向量或矩阵中的所有元素都是非零数时循环才会进行。
4.2.3 break 和 continue
break 和 continue 也是 MATLAB 中的关键字,它们可以更加灵活地控制循环过程的执行。 在 MATLAB 中, break 和 continue 只能与 for 循环或 while 循环一同使用,不能用于其他场 合。下面我们来简要介绍一下 break 和 continue 的用法:
- break关键字用于终止执行 for 或 while 循环。实际使用中,当满足某个条件时,我 们会使用 break 立即退出循环。这在找到所需结果后立即退出循环的场景非常有用。
- continue关键字用于跳过循环的当前迭代,然后继续下一次迭代。实际使用中, 当 满足某个条件时, continue 将跳过当前循环迭代的剩余部分,然后继续进行下一次迭代。这对于在某些情况下跳过特定的迭代非常有用,而不必完全退出循环。
下面我们来看两个简单的案例:
(1)已知 ,当 最小取多少时, 的计算结果大于 10?这 个例子在 while 函数中出现过,下面我们尝试使用 for 循环求解。
(2)使用循环输出 1 至 10 中所有的奇数。
注意,如果存在循环的嵌套,break 和 continue 仅在调用它的循环的主体中起作用。 即 break 仅从它所发生的循环中退出, continue 仅跳过它所发生的循环体内的剩余语句。
下面我们来看一个典型的例子,该例子中有两个 for 循环,因此存在循环的嵌套,我们 称第一次出现的循环为外层循环(简称外循环),第二次出现的循环称为内层循环(简称内循 环)。另外,在内循环中我们加上了 if 条件语句,并在 if 的条件不满足时使用了 break 关键字,
此时的 break 由于出现在内循环中,因此在起作用时仅会跳过内循环,外循环会继续下去。
上面这段代码详细的计算思路如下:
- 外层循环(由变量ii控制, ii = 1:2)首先从 ii 的值 1 开始。
- 内层循环(由变量jj控制)在每个外层循环迭代内部执行。
- 在外层循环的第一次迭代(ii 等于1)内部,内层循环(jj =1:3)执行。
- 当jj 等于 1 时,满足条件jj <= ii,因此执行 disp(ii) 和 disp(jj),输出 1 和 1。
- 当jj 等于 2 时,不再满足条件jj <= ii,因此执行 break 语句退出内层循环。
4. 外层循环的第二次迭代(ii 等于 2)开始。
5. 再次进入内层循环(jj =1:3),jj从 1 重新开始。
- 当jj 等于 1 时,满足条件jj <= ii,因此执行 disp(ii) 和 disp(jj),输出 2 和 1。
- 当jj 等于 2 时,满足条件jj <= ii,因此执行 disp(ii) 和 disp(jj),输出 2 和 2。
- 当jj 等于 3 时,不再满足条件jj <= ii,因此执行 break 语句退出内层循环。
在实际编程中, break 的使用频率远高于 continue,下面我们来看几道典型例题。
(1)质数(Prime number),又称素数,指在大于 1 的自然数中,除了 1 和该数自身外, 无法被其他自然数整除的数(也可定义为只有 1 与该数本身两个正因数的数)。给定任意一个 大于 100 的自然数 n (例如 n=135389),请判断 n 是否为质数。
思路:我们可以遍历从 2 到n-1 的所有整数,检查它们是否能够整除 n 。如果找到任何一 个能够整除 n 的整数,那么 n 就不是质数;否则, n 就是质数。
(2)一副扑克牌有 54 张牌(桃杏梅方四种花色的 A 2 3 4 5 6 7 8 9 10 J Q K 加双王),假设三名玩家玩斗地主,其中地主有 20 张牌,两个农民各 17 张牌。若你是其中一名玩家, 且你每次都选择当农民。请编程模拟以下场景:先玩第一把,若这把手上有炸弹则这把玩
完后下场换其他人玩;若手上没有炸弹则继续玩第二把,直到玩到第 k 把时手上有炸弹, 此时玩完这一把后下场换其他人玩。请输出你模拟的k。注意:假设每把牌都洗的足够的混乱,确保为无序;有炸弹是指手上有双王或者有四张相 同的牌例如 4 张 3。(这里的 k 表示你作为农民首次出现炸弹的轮数,由于发牌过程是随机的, 那么 k 肯定也是一个随机的变量,即每次模拟的 k 可能都不相同。比如运气好可能第一把就出现了炸弹,此时 k 等于 1,运气不好可能需要好多把才会出现炸弹,此时 k 较大)。
思路:由于循环的次数不定,因此我们可以使用 while 循环来不断模拟游戏的进行,直 到满足退出的条件。在每一轮中,我们作为农民会随机抽取 17 张牌,并检查是否有双王或者 普通的炸弹。如果有任何一种炸弹, 就会退出循环,否则会增加游戏的轮数, 继续下一轮。 最后,我们可以输出模拟的 k 值,表示玩到第 k 把时手上有炸弹。
在本题中,既用到了 while 循环又用到了 for 循环,且出现了两个不同用途的 break,大 家课后一定要认真消化,并尝试自己求解这个例题(当然, 判断是否存在普通的炸可以不用 循环语句,我们在第三章的课后习题中有讲解,详情请看第三章课后习题挑战篇的 Q 5)。
另外,本题还能继续扩展下去,例如重复上面的模拟过程 N 次(N 可以设置得大一点,例如 N 等于 10 万),得到这 N 次模拟结果的 k,并计算这 N 次 k 的平均值,这个平均值就能 表示你作为农民首次出现炸弹所需的期望轮数。这个拓展的问题将留作本章最后的课后习题, 我们下一道题也会介绍类似的思想。
(3)一只失明的小猫掉进山洞里,山洞有三个门, 其中进入第一个门后走 2h 后可以回 到地面,进入第二个门后走 4h 会回到原始的出发点,进入第三个门后走 6h 还是回到原始的 出发点。假设小猫每次都随机地选择这三个门中的一个进入,求小猫走出山洞的期望时间?
思路: 在上一章的课后习题中,我们见到过类似的题目,当时我们介绍过蒙特卡罗模拟 这种方法,蒙特卡罗模拟将所求解的问题同一定的概率模型相联系, 用计算机实现统计模拟 或抽样来获得问题的近似解。我们可以模拟这个过程 N 次(N 一般要设置的大一点,例如让 N 等于 10 万),每次模拟中我们都让一只猫走出山洞,并记录下这只猫所需的时间。接下来 我们只需要对这 N 次模拟结果得到的时间计算平均值,就能估计小猫走出山洞的期望时间。
(4)这个例题我们介绍二分搜索法求函数零点。若函数f(x) 在区 间 [a, b] 上连续严格 单调 ,且满 足f(a) × f(b) < 0,那么f(x)在区间[a, b]上有且仅有一个零点。二分搜索法的基本思想是不断将区间[a, b]一分为二,然后判断零点位于哪一半区间内,接着继续将包含零点的那一半区间一分为二, 如此循环, 直到得到足够精确的零点的估计值。以下是二分搜索法的一般步骤:
步骤 1:选择函数零点所在的初始区间[a, b],确保f(a) × f(b) < 0。
步骤 2:计算区间的中点c = (a + b) / 2,并计算函数在c 处的值f(c)。
步骤 3:如果f(c)的值恰好等于零,或者f(c)的绝对值小于某个给定的误差阈值,那么c 就可以当成零点,迭代结束。
步骤 4:如果f(c)与零的差异较大,那么需要根据f(c)的正负号,将原来包含零点的区间 [a, b]更换为[a, c]或[c, b],确保零点仍然在新的区间内(例如: f(a) × f(c) < 0则更换为[a, c])。
步骤 5:重复步骤 2 到 4,直到找到零点或者达到所需的精度停止迭代。
下面看一个具体的题目:函数, f(x)在区间[6, 10]严格递增且 f(6) < 0 , f(10) > 0,请用二分搜索法求零点(和 0 的误差控制在 1e-8 内即可)。