前言
在我们设计程序时,常常会遇到这种问题:
- 判断一个数是否为质数。某个循环中发现因数,便确定该数为质数并结束循环体。
- 对一组数据冒泡排序。某个循环中发现没有元素移动,便确定这组数据排序完毕并结束循环体。
我才接触计算机不到半年,便遇到了这么多相似类型的结构,可见它的重要性和常见性。因此,我要在这篇文章中进行分析,力求在找到最合适的解决思路。首先,我要先入为主地给它命名为“扳机问题”。
一、如何认识扳机问题?
这种问题包含一些基本特征:
- 具有至少一层循环。一旦满足“某种条件”立刻结束循环并获取【状态一】,反之则不断进行循环。如果进行了完整的循环,则获取【状态二】。
- 为了生成、判断是否满足“某种条件”,或者是进行基本的处理,程序可能会再嵌套循环体。比如冒泡排序的一轮冒泡,“某种条件”是该轮冒泡中是否进行了交换。
在对“某种条件”进行判断的前后,出现了类似于扳机的结构——箭在弦上,一触即发。
for(初始状态;继续条件;进程变量){
//这里可能用于生成判断条件)
{
基本处理;
//可能这里也是循环体
}
//这里可能用于修改判断条件
if(条件判断){
后续处理;
break;
}
}
二、如果拆解扳机问题?
扣动扳机需要解决三个问题:扣动扳机指的是什么?这种条件从哪里来?没有扣动扳机的话怎么办?
1.扣动扳机指什么?
毫无疑问,扣动扳机意味着结束循环。那么只要分析【满足什么条件就可以结束循环】即可。
例如质数判断问题,只要发现某个数是因数就可以确定不是质数,因此成为因数就是满足条件就要扣动的扳机。如果一个数一直是质数,则可以完成循环体。
2.这种条件从哪里来?
如果是判断质数问题,只要判断该数是不是因数即可。但在冒泡排序里,必须确定一轮循环之后没有发生交换,这时就出现了思路上的不统一。
其实,我们在每次循环中都是默认没有扣动扳机的,这可以成为突破口。只要在进行一次循环体时根据触发条件设置扳机为未触发状态,在程序运行中调整触发状态,再在本次循环结束前判断是否触发了扳机即可,这会用到一个布尔类型的变量。
我们以最简单的素数判断为例:
for(int i=1; i<sqrt(n); i++){//循环体
bool is_yinshu = false;//先未触发
{
if (n%i==0) is_yinshu = true;//设为触发
}
if (is_yinshu) break;//根据触发状态扣动扳机
}
这就启示我们在设计程序时,一定要注意什么是“基本循环体”,即“足以判断状态的最小循环体”。在基本操作的前后调试扳机,在操作过程中确定扳机,这样写思路比较清晰。
3.没有扣动扳机怎么办?
如果我们扣动了扳机,我们的程序就跳到了循环体之外......
如果我们没有扣动扳机,我们的程序照样走到了循环体之外......
不仅如此,如果不想在循环体外写下那个布尔类型的语句的话,循环体外也无法访问到是否扣动扳机了......
扣不扣扳机到了外面的结果都一个样啊,区别仅仅是多执行了几次循环体?如果我之后还需要这个结果呢?
我在这里想到的几种方法:
- 扣动扳机时解决部分问题。就是在扣动扳机的同时对一些数据进行处理,一般而言,用于要变动外部变量的数值的情况。比如,计算合数的和。这只能解决扣动扳机时顺便的问题,有局限性。
- 在基本循环体外面声明并定义,这样就可以将结果保留下来,再用if条件判断。这里要注意的是,if条件判断的是如果未触发会怎样怎样,而已经触发造成的后果直接在循环体内的判断里写就好了。
- 利用函数和返回值,这个相信大家可以理解。
//第二种方法
bool trigger;
for(int i=1; i<sqrt(n); i++){//循环体
trigger = false;//先未触发
{
if (n%i==0) trigger = true;//设为触发
}
if (trigger){
Things to be done;
break;//根据触发状态扣动扳机
}
}
if (trigger == false) {
Things to be done;
}
补充
本篇文章只是讲解了这类问题的一种思路,但实际上,出现的三次关于扳机变量的检测和更新不一定只能是false-true-false,还可以是false-true-fasle,或者这两种情况的取反。例如在冒泡排序中,检测一轮冒泡有没有发生交换,就是设置move为false-true-false。只不过是前者是一触即发,一个三思后行,但都属于这种扳机问题。