“从零开始的暴力算法之旅”

(PS:非原创!老师下发的Word文件,现在整理一下,转载出来。因为不知道原作者,所以没办法表明原地址,请见谅!)

从零开始的暴力算法之旅

我发现自己在解决一个问题时没有一个系统的思考方法,意识到这个问题的严重性。想必大家都经历过,面对复杂的问题只是傻乎乎的盯着显示器,或者不经过深思熟虑就开始敲打键盘,结果还要辛辛苦苦修改一塌糊涂的代码。

一本书上说道:与通常所想不同,支配设计算法的并不是一时出现的灵感,而是许多策略性的选择。构想算法不仅需要理解问题的特性,还要理解执行时间和占用内存空间之间的对立关系,而且还要选择适当的数据结构。有些算法会共享出解决问题时最重要的领会,将之积累起来就会领悟到一种模式。
只读这一段会觉得有点难以理解但好似有很有道理,其实,我这几天学到了一种如何思考问题的方法,总结起来就一句话:化繁为简。接下来的文章我会通过实例逐一诠释我的领悟所得。

我们最常见的错误就是把简单的问题复杂化,比如前几天去饭店吃饭,一个二年级的小朋友有一道数学题:一根木棍长22米,请问需要截取几次才能将其分为长度都为2米的木棍。答案很简单:22/2。但是我却本能的告诉那孩子是log2(22/2)。现在想来我都笑了,二分算法写多了。

所以,为了避免类似的错误,每当我们遇到问题时应当先自问:能否用暴力的方法解决,并且是否能否优雅的解决【这体现了一个程序员是否有一颗优雅编程的心】。

接下来看一道问题:

求从0开始按顺序标号的n个元素中,选择4个元素的所有可能的组合。假设n=7,那么大家就会想到写一个4重循环就好,看起来是这个样子:

for(int i = 0; i < n; ++i)
for(int j = i+1; j < n; ++j)
for(int k = j+1; k < n; ++k)
for(int l = k+1; l < n; ++l)
cout<<i<<j<<k<<l<<endl;

但是若是选择5个数字的全排列呢,7个,8个呢,是不是得手动在增加循环次数,比追女生都麻烦是不是。要是我们将每一个循环看成一个递归的话,就很容易优雅解决了。

则构成的飘逸的代码是下面这样的:

//n:元素总量

//picked:已选元素的序号

//toPick:还需选择元素的数量

#include<iostream>
#include<vector>
using namespace std;
void printPicked(vector<int> & picked) {
	for(int i = 0; i<picked.size(); i++) {
		cout<<picked[i];
	}
	cout<<endl;
}
void pick(int n, vector<int>& picked, int toPick) {
	if(toPick == 0) {
		printPicked(picked);
		return;
	}
	int smallest = picked.empty() ? 0 : picked.back() + 1;
	for(int next = smallest; next < n; ++next) {
		picked.push_back(next);
		pick(n,picked,toPick-1);
		picked.pop_back();
	}
}
int main() {
	int n = 0;
	cin >> n;
	vector<int> picked;
	pick(10,picked,n);
}


算法之暴力破解法(穷举法)

一,什么是暴力破解法?

暴力破解法,就是把所有条件,相关情况统统考虑进去,让计算机进行检索,指导得出与之所有条件符合的结果

(但是,暴力破解法对计算机资源耗费严重,如果条件太复杂,运算速度缓慢,为了解决这一问题,我们可以事先把与之不相关的条件进行限制,减少计算机的运算量)

二,暴力破解法应用

  1. 鸡兔同笼

问题:有鸡兔共50头,共有脚120只。 问 :鸡兔分别的数量?

【理解】
鸡的头和兔子的头数想加为50个,情况数量并不是很多,最多50个最少0个,是有限的,这个问题就可以使用暴力破解的方法来解决。

代码如下:

public class OneDay {
	public static void main(String[] args) {
	//x为鸡的数目,最小是0,最大为50,在一个循环中一个一个的测试,看哪一个条件能够满足题目要求
		for(int x=0; x<=50; x++) {
			int y=50-x;
			if(x*2+y*4==120) {
				System.out.println("x="+x+"y="+y);
			}
		}
	}
}
答案:x=40   y=10
  1. 韩信点兵

韩信知道部队人数大约1000人左右,具体数字不详,5人一组剩余1人,7个人一组还剩两个人,8个人一组还剩3个人,问:这支部队有多少人?

【理解】可以使用暴力破解法的方式,枚举所有情况,显然人数就是我们列举的情况;代码如下:

public class OneDay {
	public static void main(String[] args) {
		for(int i=0; i<=2000; i++) {
			if(i%5==1 && i%7==2 && i%8==3) {
				System.out.println(i);
				}
			}
		}
}
答案:51、331、611、891、1171

【总结】

暴力破解法:仅仅就是对所有可能的情况逐一的去列举,并且用条件进行筛选,把满足条件的列举出来,就可以了。


基础算法之 暴力搜索

  • 算法说明

暴力算法也叫蛮力算法,之所以称为暴力,是因为该算法是枚举当前所有出现的情况,从而得到需要的情况。该算法可以求的一些情况较少的问题的解,若问题规模太大,该算法便不适用。

  • 算法分析
    若给定一个集合 α ,求得该集合的所有子集,求得的子集即为某种情况,列出所有子集及是暴力算法。比如给定一个数组 [1, 3, 5] ,其所有子集为 [ ],[ 1 ],[ 3 ],[ 5 ],[ 1, 3 ],[ 1, 5 ], [ 3, 5 ], [ 1, 3, 5 ] 共八种情况。对于一个元素数量为 n 的集合来说,其子集数目为 2 的 n 次方。证明为: 对于集合中的每个元素,都有选择或者不选择两种情况,则 全部情况为 2 的 n 次方,该证明即为算法的核心。

  • 问题示例

  • 背包问题

给定一定数量的物品,每个物品都有其重量和价值,给定一个容量为 c 的背包,用该背包装物品,求 装的物品价值之和为最大的情况。

  • 问题分析

设集合 A ,其元素为每个物品,利用暴力搜索算法列出集合 A 的所有子集,在其子集中找到物品价值之和最大的情况即可。对于该问题选择的数据结构,在此我用的是一个一维数组存放物品的重量,另一个一维数组存放物品的价值,对应下标即可。

代码如下(C语言)

#include<stdio.h>
int goods_all = 5;    						/* 物品数量  */
int goods_weight[5] = {1, 3, 6, 8, 12};		/* 物品的重量 */
int goods_value[5]  = {1, 4, 8, 10, 20};    /* 物品的价值 */
int package = 20;							/* 背包容量 */

int maxValue = 0;							/* 当前最大价值(后面用到)*/
int choose[5];								/* 存放最优方案的物品下标 */

/* 输出结果函数

*/
void outputResult() {
	for (int i = 0; i < goods_all && choose[i] != -1; i++) {
		printf("重量:%d,价值:%d\n", goods_weight[choose[i]], goods_value[choose[i]]);
	}
}

/* 存放当前最优方案(不断的更新 choose 数组)
   @param    goods_list[]       当前的选择项数组
   @param    goods_num          当前的选择项 数目
*/
void storeChoose(int goods_list[], int goods_num) {
	for (int i = 0; i < goods_all; i++) choose[i] = -1;
	for (int i = 0; i < goods_num; i++) choose[i] = goods_list[i];
}

/* 暴力枚举所有方案(递归实现)
   @param   goods_list  存放当前方案的数组
   @param   goods_num   当前选择的物品数目
   @param   goods_index 当前选择或不选择的物品下标

*/
void chooseGoods(int goods_list[],int goods_num, int goods_index) {

	/* 当物品的下标到最后一个物品时,即枚举完毕 ,
	   此时 遍历当前方案 goods_list[], 若当前方案价值
	   大于 当前的 maxValue ,则重写 choose数组
	*/
	if (goods_index == goods_all) {
		int value = 0, weight = 0;
		for (int i = 0; i < goods_num; i++) {
			weight += goods_weight[goods_list[i]];
			value += goods_value[goods_list[i]];
		}
		if (weight <= package && value > maxValue) {
			storeChoose(goods_list, goods_num);
			maxValue = value;
		}
	} else {
		/* 不选择当前物品 */
		chooseGoods(goods_list, goods_num, goods_index + 1);

		/* 选择当前物品 */
		goods_list[goods_num] = goods_index;
		goods_num ++;
		chooseGoods(goods_list, goods_num, goods_index + 1);
	}
}
int main() {
	int arr[5];
	chooseGoods(arr, 0, 0);
	outputResult();
}
  • 总结

对于暴力搜索,最重要的是对于当前元素的选择或者不选择,在这里能保存数组的多样性是因为下标的原因,因为选择的元素都存在了数组一端。


补充:https://www.cnblogs.com/luyouqi233/category/1142002.html (转载一波其他优秀的暴力算法分类详解qvq)


posted @ 2020-06-08 17:28  Eleven谦  阅读(321)  评论(0编辑  收藏  举报