【算法】蛮力法/穷举法/枚举法 的基本问题分析

  • 炮兵问题的优化,设立逻辑数组

蛮力法设计思想

有策略地穷举 + 验证

  • 制定穷举策略
  • 避免重复

简单来说,就是列举问题所有可能的解,然后去看看是否满足题目要求,是一种逆向解题方式。(我也不知道答案是什么,但是我知道答案肯定在这些范围里面,我一个个试就完了。)

所以我们应该做的是

  • 知道可能的答案都有哪些
  • 使用方法将其一一列举
  • 将每个解依次与条件比照

蛮力法使用范围

在这里插入图片描述

  • 所有解空间:例如a,b,c取值范围都是 0~9,问abc组成的三位数的所有情况
  • 所有路径:遍历图、遍历树

蛮力法的格式

蛮力法由循环和选择语句构成

  • 使用循环,穷举所有情况
  • 使用选择,判断当前情况是否成立
    • 若成立,则得到问题的解
    • 若不成立,则继续循环

循环 + 分支

for(){for(){// 获取X所有可能的情况if(X满足条件){printf("X");}}
}
蛮力法思想:穷举所有情况,找到符合条件的。

这也意味着,得能够穷举,因此问题规模不能太大

示例1:求完全数

在这里插入图片描述
思想:穷举所有数字,将符合条件的找出来。

关键点:求数字的所有因子。
对于数x,因子y,需要x % y = 0,此处y也需要尝试1 ~ x-1的所有数。

判断因子和是否等于x。

因此程序为

// 求完全数
#include <iostream>
using namespace std;int main() {for (int i = 2; i <= 1000; i++) {int sum = 0;for (int j = 1; j <= i - 1; j++) {if (i % j == 0) { // 若是因子sum += j;}}if (sum == i) {cout << "完全数:" << i << endl;}}return 0;
}

这里还有点bug,是数学知识不完备,因子在1 ~ m/2的范围,因子不可能比原数字的一半还要大(除了它本身),我们循环地太多了。内循环应该是j <= i/2

结果为:
在这里插入图片描述
我们需要非常清晰地体会到构建程序框架的思维过程

int main() {// 穷举所有情况for (int i = 2; i <= 1000; i++) {// 求因子之和// 比较因子与该数是否相等}return 0;
}

然后,我们再实现其细节。

充分体会:穷举情况,再依次判断的计算过程。

示例2:求水仙花数

在数字100~999中,求水仙花数,例如:13 + 53+ 33 = 153。

同样地,穷举 + 验证

关键点:求一个三位数的每一位的数。

先建立思维框架

// 穷举100~999的所有数字
for(){// 求三位数的每一位数,将其立方和相加// 判断数字与其立方和是否相等}

代码为:

// 求水仙花数
#include <iostream>
using namespace std;int main() {for (int i = 100; i <= 999; i++) {int sum = 0;int temp = i;while (temp){int x = temp % 10;sum += (x*x*x);temp /= 10;}if (i == sum) {cout << "水仙花数:" << i << endl;}}return 0;
}

答案为:
在这里插入图片描述
再强调一遍

  • 循环 + 选择
  • 先构建思维框架,再填充细节

示例3:象棋算式

在这里插入图片描述
我们先将问题的输入找到:也就是5个变量,且满足

  • 每个变量范围是0~9
  • 5个变量各不相同

然后穷举所有的可能性,看是否能够满足表达式,这是恐怖的5层循环,但是问题规模还在可接受范围内。

我们试试看。

  • 兵:a
  • 炮:b
  • 马:c
  • 卒:d
  • 车:e
#include <iostream>
using namespace std;int main() {int a, b, c, d, e;for (a = 0; a <= 9; a++) {for (b = 0; b <= 9; b++) {for (c = 0; c <= 9; c++) {for (d = 0; d <= 9; d++) {for (e = 0; e <= 9; e++) {// 若5个数都不同,再看是否满足表达式if (!(a == b || a == c || a == d || a == e|| b == c || b == d || b == e|| c == d || c == e|| d == e)) {int add1 = a * 1000 + b * 100 + c * 10 + d;int add2 = a * 1000 + b * 100 + e * 10 + d;int sum = e * 10000 + d * 1000 + c * 100 + a * 10 + d;if (sum == add1 + add2) {cout << "兵 = " << a << " 炮 = " << b << " 马 = " << c << " 卒 = " << d << " 车 = " << e << endl;}}}}}}}return 0;
}

结果是
在这里插入图片描述
不过显然,太低效率了,如果有n的话,这个算法的时间复杂度是O(n5),这几乎是不可接受的,太暴力了,问题规模显得有点大,是105了。

所以,我们虽然遵循了 循环 + 选择,但是,穷举地过于直接,看看有没有其他办法呢?

思想:即便是穷举,也要“聪明地”穷举

同样是穷举,也有不同的方法,最粗暴的就是循环,一层循环不行就嵌套多重循环……好蠢但是很简单好想。

我们来看看,同样是穷举,不同做法的差异。

示例

对于一个含有6个元素的一维数组,该数组每个数只能是0或1,将所有情况穷举出来。
例如(0,0,0,0,0,0)、(0,1,0,1,1,0)

最愚蠢的做法,直接6重循环

#include <iostream>
using namespace std;int main() {int a, b, c, d, e, f;int number[6];for (a = 0; a <= 1; a++) {for (b = 0; b <= 1; b++) {for (c = 0; c <= 1; c++) {for (d = 0; d <= 1; d++) {for (e = 0; e <= 1; e++) {for (f = 0; f <= 1; f++) {number[0] = a;number[1] = b;number[2] = c;number[3] = d;number[4] = e;number[5] = f;for (int i = 0; i < 6; i++) {cout << number[i] << "  ";}cout << endl;}}}}}}return 0;
}

结果:在这里插入图片描述
这的确可以完成任务,但是这样的算法真的很糟糕,不是吗?

我们来看看优化后的穷举的方法

我们将其与二进制结合,因为对于这样的0、1序列,与二进制有直接联系,我们直接将十进制是0~63,转换为二进制,存入数组中,就可以了,这样大大降低了时间复杂度!

#include <iostream>
using namespace std;int main() {int number[6];for (int i = 0; i <= 63; i++) {// 转换为二进制,除二求余再倒置int temp = i;for (int j = 5; j >= 0; j--) {number[j] = temp % 2;temp /= 2;}// 输出结果for (int k = 0; k < 6; k++) {cout << number[k] << "  ";}cout << endl;}return 0;
}

穷举加验证,循环加选择,蛮力解难题

穷举有方法,验证有策略,循环尽量浅,选择看题目

我们之前谈过,穷举法,需要能够穷举,数据规模不太大,现在,我们在此基础上进行了优化,同样能够穷举的,穷举方式的选择也很重要。

对于循环,我们希望尽可能减少嵌套层数

当然了,简单的属于通用普适技法,稍难的就需要动用你的观察力,但是这些东西你熟悉了,也就变成了简单的了。

接下来我们尝试优化示例3。

示例3优化

我们只关注,如何进行穷举,并且尽可能减小穷举规模空间,关注一个条件:5个数各不相同

这样一来,我们的问题规模就由105 = 10,000 变成了 10 x 9 x 8 x 7 x 6 = 30,240 ,变成了原来的30%左右。

可以用多重循环来列举出它们各种不同的取值情况,逐一地判断它们是否满足上述等式;为了避免同一数字被重复使用,可设立逻辑数组x,x[i](0≤i≤9)值为1时表示数i没有被使用,为0时表示数i已被使用。

int main() {int x[10];int a, b, c, d, e, i, m, n, s;for (i = 0; i <= 9; i++) x[i] = 1;   /*x数组置初值*/for (a = 1; a <= 9; a++){x[a] = 0; /*表示不能再让其他变量取与a相同的值*/for (b = 0; b <= 9; b++)if (x[b])  /*如果b取的当前值未被其他的变量重复*/{x[b] = 0; /*表示不能再让其他变量取与b相同的值*/for (c = 0; c <= 9; c++)if (x[c])   /*如果c取的当前值未被其他的变量重复*/{x[c] = 0;  /*表示不能再让其他变量取与c相同的值*/for (d = 0; d <= 9; d++)if (x[d])    /*如果d取的当前值未被其他的变量重复*/{x[d] = 0;   /*表示不能再让其他变量取与d相同的值*/for (e = 0; e <= 9; e++)if (x[e]){m = a * 1000 + b * 100 + c * 10 + d;n = a * 1000 + b * 100 + e * 10 + d;s = e * 10000 + d * 1000 + c * 100 + a * 10 + d;if (m + n == s)printf("兵:%d 炮:%d 马:%d 卒:%d车:%d\n",a, b, c, d, e);}x[d] = 1;  /*本次循环未找到解,让d取其他值*/}x[c] = 1;  /*本次循环未找到解,让c取其他值*/}x[b] = 1;     /*本次循环未找到解,让b取其他值*/}x[a] = 1;  /*本次循环未找到解,让a取其他值*/}return 0;
}

重要的收获:穷举有方法,不能上来题都不看就开始举,有条件地穷举,减少解空间,对于本题,5个数不同,可以当作题目条件来验证,也可以当成穷举的约束条件

同样的条件,不同的看法,就会产生不同的解决方案。

还记得离散数学中的附加条件证明法吗,将结论中的前提当条件用,再推出最终结论,极大简化了运算。

百元买百鸡问题

已知公鸡5元一只,母鸡3元一只,小鸡1元三只,用100元买100只鸡,问公鸡、母鸡、小鸡各多少只?

  1. 知道解空间
    • 公鸡:x 0~20
    • 母鸡:y 0~33
    • 小鸡:z 0~100
  2. 如何列举
    • 最简单思路:三重循环
  3. 如何验证
    1. x + y + z = 100
    2. 5x + 3y + z/3 = 100
    3. z % 3 == 0
    4. 这样看来,可以将z变量去掉了,解空间也减少了100倍,三重循环也变成了二重循环。也就是我们将验证条件当成解空间的约束条件,减少了解空间的规模,这与我们上面的示例3优化是一个思路。

注意:小鸡是1元3只,需要注意 z/3 时,int会去掉小数点,需要验证z是3的倍数。

#include <iostream>
using namespace std;int main() {int x, y, z;for (x = 0; x <= 20; x++) {for (y = 0; y <= 33; y++) {if ((100 - x - y) % 3 == 0) {if ((5 * x + 3 * y + (100 - x - y) / 3) == 100) {cout << "公鸡:" << x << endl;cout << "母鸡:" << y << endl;cout << "小鸡:" << 100 - x - y << endl;cout << "**********" << endl;}}}}return 0;
}

在这里插入图片描述

示例

求所有的三位数,它除以11所得的余数等于它的三个数字的平方和.

分析:

  1. 解空间:三位数x,100~999
  2. 如何枚举:循环
  3. 如何验证(约束条件):x % 11 == 三个数字平方和
  4. 约束条件可否去限制解空间? 否
#include <iostream>
using namespace std;
// 求所有的三位数,它除以11所得的余数等于它的三个数字的平方和.
int main() {for (int i = 100; i <= 999; i++) {// 求三个数平方和int sum = 0;int temp = i;while (temp){int x = temp % 10;sum += (x * x);temp /= 10;}// 验证if (sum == i % 11) {cout << "数字:" << i << endl;}}return 0;
}

结果:
在这里插入图片描述

对问题进行建模

我们进一步分析上一题

求所有的三位数,它除以11所得的余数等于它的三个数字的平方和.

假设3位数A的三个数是x,y,z。

  1. 0 <= A % 11 <= 10
  2. x2 + y2 + z2 <= 10,x*100 + y*10 + z = A
  3. 1 <= x <= 3, 0 <= y <= 3,0 <= z <= 3,且都是整数

这样一来,经过简单的人工处理,解空间减少了很多,然后再进行 穷举 + 验证 就可以了,显然比之前的更加高效,因为进行了人工分析

int x, y, z;
for (x = 1; x <= 3; x++) {for (y = 0; y <= 3; y++) {for (z = 0; z <= 3; z++) {int num = x * 100 + y * 10 + z;if ((x*x + y * y + z * z) == num % 11) {cout << "数字:" << num << endl;}}}
}

这里强调的其实也是,将验证条件进一步分析,将其转换为解空间的约束条件,以降低解空间规模。

穷举 + 验证:穷举范围、穷举方式、验证条件、约束条件

我们再回顾之前的例子,充分体会穷举法的分析思路

穷举依赖的技术是遍历,也就是解空间的每个解,都要找一遍,注意尽量避免充分。

示例1

在这里插入图片描述

  • 解空间:2~1000
  • 验证条件:各因子之和等于本身
  • 约束条件:没有可以转化的验证条件
  • 穷举方式:解空间 + 约束条件–>循环

示例2

在数字100~999中,求水仙花数,例如:13 + 53+ 33 = 153。

  • 解空间:100~999
  • 验证条件:水仙花数
  • 约束条件:无
  • 穷举方式:解空间+约束条件–>循环

示例3

在这里插入图片描述

  • 解空间:5个变量,每个变量范围0~9
  • 验证条件:满足题目表达式
  • 约束条件:5个变量各不相同
  • 穷举方式:解空间+约束方式–>减小解空间–>5重循环,如果有值重复,则跳出

示例4

对于一个含有6个元素的一维数组,该数组每个数只能是0或1,将所有情况穷举出来。
例如(0,0,0,0,0,0)、(0,1,0,1,1,0)

注意:本题本身就是求解空间

  • 解空间:6个数,每个数是0或1,求全部的0、1序列
  • 验证条件:情况不重复
  • 约束条件:无
  • 穷举方式:解空间+约束条件–>6重循环

问题建模 & 转化:类二进制数

很明显这题的0、1序列,与二进制数类似,可以转换问题,改为求0~63十进制的二进制,间接解题。

属于特殊解法,需要惊人的洞察力。

示例5

已知公鸡5元一只,母鸡3元一只,小鸡1元三只,用100元买100只鸡,问公鸡、母鸡、小鸡各多少只?

  • 原始解空间:3个变量,x∈[0,20],y∈[0,33],z∈[0,100]
  • 验证条件
    • x + y + z = 100(看做约束条件)
    • 5x + 3y + z/3 = 100
    • z / 3为整数(z % 3 = 0)
  • 约束条件:将验证条件中第一条,转换为约束条件,也就是z = 100 - x -y这与就去掉了一个变量,这个变量范围最大,能够充分减少解空间。
  • 约束后解空间:2个变量,x∈[0,20],y∈[0,33]
  • 穷举方式:双重循环

注意:谁是验证条件,谁是约束条件,是跟你的看法有关的。

示例6

求所有的三位数,它除以11所得的余数等于它的三个数字的平方和.

  • 原始解空间:数字A,三个位是x,y,z,A范围100~999
  • 验证条件
    • A % 11 = x2 + y2 + z2
  • 约束条件:由验证条件进一步分析得出,需要有经验和洞察力
    • A % 11 ∈[0,10]
    • x∈[1,3],y∈[0,3],z∈[0,3]
  • 约束后解空间:x∈[1,3],y∈[0,3],z∈[0,3]
  • 穷举方式:三重循环

备注:验证方式需要具体问题具体分析

充分体会不同算法的组合,因为一道题目,可能背后涉及到多个不同的算法的组合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384685.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【计算机网络实验·北航】实验一:网络实验入门(1)

1.3 远程在线环境使用 PCA、PCB、PCC和PCD&#xff1a;4台PC机S1、S2&#xff1a;2台交换机R1、R2&#xff1a;2台路由器中间的设备&#xff1a;组网连线器 远程组网连线&#xff1a; 使用PCA上的组网连线软件&#xff0c;配置组网连线器&#xff0c;实现组网连线。 PCA和PCB…

【操作系统】虚拟化CPU、Memory,共享文件

几个概念 CPU、虚拟CPU进程内存、虚拟地址空间 物理的CPU被OS虚拟成了多个虚拟的CPU&#xff0c;这些虚拟CPU分别运行各自的程序&#xff0c;这些正在运行的程序被称为进程。物理内存被OS虚拟成了多个虚拟地址空间&#xff0c;每个进程都有独立的、自己的地址空间&#xff0c;…

【Linux】编译C语言文件(-o -lpthread)

在gcc中使用-o编译 对于一个一般的程序&#xff0c;直接使用gcc <C语言文件名> -o <编译后生成的文件名>即可&#xff0c;例如以下程序&#xff1a; // cpu.c #include <stdio.h> #include <unistd.h> #include <stdlib.h>int main(int argc,…

【Linux】Ubuntu下进行C语言编程

前言 需要您会使用Windows下cd切换目录的基本命令&#xff0c;否则请先自学相关知识&#xff0c;之后再阅读本文。 0 基础命令 介绍最基础的Linux终端命令。 su - root&#xff1a;切换到root用户&#xff08;不用也可以&#xff09;ls&#xff1a;查看当前目录位置cd&…

【Linux】Ubuntu 18下安装Vim自动补全插件YouCompleteMe(可高速下载安装)

前言 本文写于2020年10月&#xff0c;如果你多年后看见这篇文章&#xff0c;方法可能已经失效&#xff0c;但是请牢记&#xff0c;尽量下载你所处时代的最新版本的软件&#xff0c;会减少很多麻烦。 摆正心态 即便按照本文操作&#xff0c;由于你的系统状态和我的不一样&…

【操作系统】进程调度(1):FIFO(先进先出)算法 原理与实践

0 前言 本文基于书籍《Operating System&#xff1a;Three Easy Pieces》。 中译本&#xff1a;《操作系统导论》&#xff0c;中译本质量还可以&#xff0c;但是英文版后来的更新&#xff0c;中文版目前没有进行同步更新&#xff08;写下此文的时间是2020年10月&#xff09; 1…

【操作系统】进程调度(2a):SJF(短任务优先) 算法 原理与实践

0 前言 接上一篇文章&#xff1a;进程调度&#xff08;1&#xff09;&#xff1a;FIFO&#xff08;先进先出&#xff09;算法 原理与实践 1 前提铺垫 请参考上一篇文章的前提铺垫部分&#xff0c;本文与之完全一致。 2 SJF 原理 SJF&#xff08;Shortest Job First&#x…

【操作系统】进程调度(2b):STCF(最短完成时间优先) 算法 原理与实践

0 前言 接上一篇文章&#xff1a;进程调度&#xff08;2a&#xff09;&#xff1a;SJF&#xff08;短任务优先&#xff09; 算法 原理与实践 1 前提铺垫 与上一篇同。 2 STCF 原理 STCF&#xff08;Shortest Time-to-Completion First&#xff09;最短完成时间优先。 2.1…

【操作系统】进程调度(3):RR(轮转) 算法 原理与实践

0 前言 接上一篇文章&#xff1a;进程调度&#xff08;2b&#xff09;&#xff1a;STCF&#xff08;最短完成时间优先&#xff09; 算法 原理与实践 1 前提铺垫 除了与上一篇相同的&#xff0c;这里介绍新的基础知识。 1.1 三种类型的程序 计算密集型&#xff08;CPU导向&…

【操作系统】进程调度(4):I/O、不可预测的运行时间

0 前言 上一篇文章&#xff1a;进程调度&#xff08;3&#xff09;&#xff1a;RR&#xff08;轮转&#xff09; 算法 原理与实践 1 前提铺垫 与上一篇同。 2 引入I/O操作 之前我们一直提及的是计算密集型程序&#xff0c;现在我们的程序可以进行I/O交互了&#xff0c;它会…

【计算机系统设计】重点 · 学习笔记(0)

HDL等硬件描述语言&#xff0c;例如Verilog&#xff0c;是并行的&#xff0c;而不像软件一样的顺序执行的&#xff0c;例如很多的always块&#xff0c;initial块&#xff0c;都是并行的&#xff0c;他们会转换为硬件电路&#xff0c;而在仿真的时候&#xff0c;他们也是并发执行…

【计算机系统设计】学习笔记(1)03,04

疑问&#xff1a;sw和lw指令&#xff0c;获取的地址不是4的整倍数&#xff08;字节不对齐&#xff09;的时候&#xff0c;应该如何处理&#xff1f; 东南大学MOCC 计算机系统综合设计 03 03-1 寄存器 介绍了MIPS寄存器&#xff0c;32个寄存器的基本功能和使用&#xff0c;注…

【期末考试】计算机网络、网络及其计算 考试重点

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 计算机网络及其计算 期末考点 &#x1f680;数…

【计算机系统设计】学习笔记(2)

5.1 对于CPU与外界的读写&#xff0c;只有load和store指令能够做&#xff0c;所以很多情况下&#xff0c;直接通过bypass跳过去了&#xff0c;或者闲置&#xff0c;尤其对于流水线&#xff0c;更应该直接跳过而不是闲置&#xff08;如何设计?&#xff09;。 另一方面&#xf…

【计算机系统设计】重点 · 学习笔记(1)(资源消耗)

这一点先浅显理解&#xff0c;就好比我要造一个楼 我是用现成的材料造节省?还是需要用XX材料&#xff0c;但是XX材料还需要现成材料造呢&#xff1f; 这也不一定&#xff0c;但是基本来说&#xff0c;如果使用现有资源&#xff0c;能够直接用&#xff0c;那其实是最好不过的…

【计算机系统设计】重点 · 学习笔记(0)(数据通路设计思想)

重点1&#xff1a;05.1 设计思想 设计思想至关重要&#xff0c;这决定了你能不能自己根据ISA设计出来CPU架构&#xff0c;而不是只是抄别人的&#xff0c;也决定你能不能完成自己的设计更优化的架构。 描述方式约定 6 数据通路 ≠ Verilog代码 我们构建的数据通路&#…

【计算机系统设计】实践笔记(1)数据通路构建:取指部件分析

0 核心思想 根据指令功能&#xff0c;分析出需求&#xff0c;从而得出需要的部件、控制信号以及其他设计。 1. 针对的指令 取指阶段&#xff0c;针对所有指令&#xff0c;任何指令都需要进行取指。 2 功能&#xff08;需求&#xff09;分析 CPU的内部采用的是字节编址&…

【计算机系统设计】实践笔记(2)数据通路构建:第一类R型指令分析(1)

0 回顾 上一次实践笔记&#xff08;0&#xff09;我们实现了一个最简单的&#xff0c;能够每个上升沿4的PC。 我们最需要关注的就是器件功能的独立性&#xff0c;避免内外功能混杂&#xff0c;同时一定要注意脑中有电路&#xff08;RTL级描述的抽象电路而不是实际的门级电路&…

接口的抽象与实现(概述)

概述 我们先建立一个整体的接口格局观&#xff0c;建立知识地图&#xff0c;了解接口的大概面貌。 整体来说&#xff0c;就这点事儿&#xff0c;4个箭头代表了所有&#xff01; 三个器件4个箭头 把这几个都想明白&#xff0c;就完事儿了。 第一层&#xff08;顶层&#xf…