计算思维 by徐明星老师 逻辑推理、下楼、跳马、分书、八皇后、人鬼过河

任务1.1 谁做的好事

A:不是我
B:是C
C:是D
D:他(C)胡说
四个人只有一个人做了好事。
三个人说真话,一个人说假话。请判断谁做了好事。
布尔代数(离散数学):将自然语言转换为机器语言(关系表达式)。用ABCD代表四个人
A:不是我 thisman != ‘A’
B:是C thisman = = ‘C’
C:是D thisman = = ‘D’
D:他(C)胡说 thisman != ‘D’

枚举法求解
按照四种可能性,逐一去测试四个人中每句话的真假情况。
(1) thisman = ‘A’ 带入四句话中 0 0 0 1 三假一真
(2)thisman = ‘B’ 1 0 0 1 二真二假
(3)thisman = ‘C’ 1 1 0 1 三真一假
(4)thisman = ‘D’ 1 0 1 0 二真二假
因此情况三正确。 这个做好事的人是C。

#include<iostream>
using namespace std;
int main()
{
	char thisman;
	int sum;

	/// A
	thisman = 'A';
	sum = ( thisman != ''A ) + ( thisman == 'C' )
			+ ( thisman == 'D' ) + ( thisman != 'D' );
	if (sum == 3)
	{
		cout<<"做好事者为"<<thisman<<endl;
		return 0; 
	}

	/// B
	thisman = 'B';
	sum = ( thisman != ''A ) + ( thisman == 'C' )
			+ ( thisman == 'D' ) + ( thisman != 'D' );
	if (sum == 3)
	{
		cout<<"做好事者为"<<thisman<<endl;
		return 0; 
	}
	
	/// C
	thisman = 'C';
	sum = ( thisman != ''A ) + ( thisman == 'C' )
			+ ( thisman == 'D' ) + ( thisman != 'D' );
	if (sum == 3)
	{
		cout<<"做好事者为"<<thisman<<endl;
		return 0; 
	}

	/// D
	thisman = 'D';
	sum = ( thisman != ''A ) + ( thisman == 'C' )
			+ ( thisman == 'D' ) + ( thisman != 'D' );
	if (sum == 3)
	{
		cout<<"做好事者为"<<thisman<<endl;
		return 0; 
	}
}

若代码中出现共同部分:则需要对代码进行重构(例如采用循环来枚举)

#include<iostream>
using namespace std;
int main()
{
	int g=0
	for(k=0;k<4;k++)
	{
		char thisman = 'A' + k;
		int sum = ( thisman != ''A ) + ( thisman == 'C' )
					+ ( thisman == 'D' ) + ( thisman != 'D' )
		if (sum == 3)
		{
		cout<<"做好事者为"<<thisman<<endl;
		g = 1 ;
		}
	}
	if(g != 1)
	{
		cout << "找不出做好事者" << endl;
	}
	return 0 ;
}

任务1.2 下楼

从楼上到楼下一共h个台阶
每一步可以跨一个、两个台阶、三个台阶。
问一共可以走出多少种方案?
可以用与或图模拟出下楼的过程。在这里插入图片描述

将要走下去的每一步存在一定的相似性(仅仅是问题的规模不同),可以用同样的方法去算。也就是所谓的递归
在处于每一级台阶时,有三种可能的解法,采用for循环实现。
1、开始时 i=h(初值)
2、以后 i = i-j (j = 1、2、3)
3、i=0 时,表明剩余台阶数为0,说明走到楼下。

#include<iostream>
using namespace std;

int take[99], num = 0; 

void Try(int i, int s);  // 有i级台阶,从第s步开始

int main(){
	cout<<"请输入楼梯台阶数:";
	int h;
	cin >> h;	 //输入楼梯台阶数
	
	Try(h,1);   //从第h级,开始下第一步
	
	cout<<"总方案数:"<<num<<endl;

	return 0;
}
//在循环体里套着一个递归
void Try(int i, int s){  //有i级台阶,从第s步开始
	for(int j=3; j>0; j--){
		if(i >= j){
			take[s] = j;  //记录第s步走j个台阶
			if(i==j){	//说明走到楼下
				num++;
				cout<<"方案"<<num<<":";
				for(int k=1;k<=s;k++) cout<<take[k];
				cout<<endl;
			}
			else
				Try(i-j,s+1);
		}
	}
}

法二:一般递归算法。先判断终止条件再计算。
每一步所应对的可能性做依次的尝试,每一种可能性又有相似的地方 --> 枚举 + 递归

任务1.3 跳马

马:只允许往右跳…从左下角跳到右上角(半张棋盘)。
问一共有多少种方法。
计算思维:问题数字化。
1、棋盘上的每一个位置用二维数字编号去表示它。(0,0)–>(8,4)
2、跳的步骤,坐标的变化。(+1,+2)、(+2,+1)、(+1,-2)、(+2,-1)
每一步仅有四种跳法。
设计数据结构。
采用数组。int dx=[1,2,1,2] int dy=[2,1,-2,-1]
采用一个二维数组记录记录操作步骤。一维是跳步的次序、另一维是位置
注意改进:此处可以采用一个结构体变量来存储一个二维坐标。

for I from 0 to 4 do:
(x,y) -> (x+dx,y+dy)

与节点:每一步都要进行四种跳法的遍历。
或节点:计算出新的节点
非法(不继续);达到;继续(递归)

增加了自定义的函数检查落点的坐标。可读性增强

#include<iostream>
using namespace std;

struct

任务1.4 分书

在这里插入图片描述

用一个二维数组描述喜欢与否。like ij 编号I的人喜欢编号为j的书
书籍状态用一个一位数组描述。 -1 表示没有分配。若分配出去的话,则相应下标为的标号,值为哪位读者拿到了这本书。

try函数的递归实现:
void Try(int reader){
if(reader == 5){

} 

}

能否不使用回溯。可以。利用一个结构体、加一个变量state。
原理:state仍未进入循环之前的状态。每一次循环并不改变state。利用函数调用形参不会改变实参,将被变化的量用临时变量(形参)临时代替了。

任务1.5 八皇后问题

国际象棋棋盘上
皇后。不在同一行、不在同一列、不在对角线。
请问怎么放->怎么变成计算机的运算。
*解题思路一:*枚举八重循环。
在循环体里写一个Issafe(q)
Issafe(q):已知八皇后位置判断安全性。
法一:以皇后为枚举对像、看插入的对象与前面的皇后对比(类似与插入排序)
法二:以行、列、对角线为枚举对象,统计各个方向上皇后的数量。(类似于词频统计)

*解题思路二:*不采用暴力枚举
从第一列开始逐一放皇后、直至8列均放上皇后。
对于第i个皇后(在第i列),逐一检查该列的8行,判断是否会被前i-1个皇后攻击到,选择一个安全的位置。

*解题思路三:*枚举+递归 (与或图)
采用试探方法:回溯法:向前走(放置皇后),碰壁回头(取走皇后)
由于棋盘的对称性:假定为逐列放置皇后

关键点:对棋盘上指定位置的安全性进行判断—>表述为数学语言
行:纵坐标y
列:横坐标x
对角线:x+y 、 x-y

一维数组:下标为皇后的列号,值为皇后的行号。

注意也需要回溯!(恢复三个方向的原有安全性)
能否不进行回溯呢?可以
采用一个结构变量存储起来,传递到try递归函数中。
在try函数中产中一个newstate状态、改变newstate。对原有的实参没有影响

任务1.6 人鬼过河

三人三鬼如何过河。希望摆渡次数最少。
一次最多两人、两鬼、一人一鬼
一旦鬼多于人,人会被吃掉。
怎样渡河(东岸到西岸)由人决定。

目标找到一种方案(一系列的指令)
指令:A->B 考虑计算机的特性,指令必须对“数”进行改变。但数在哪儿?
(跳马:移动-坐标、分书:读者与书籍的约束-矩阵元素)
计算思维就是在找数学模型(可以被计算的东西)

抽象出的数学模型:考察变化的量。(变化的量就是一种可以计算的量)
变化的:西岸的数量、东岸的数量、船上的数量、行船的方向。
不变的:所有的,总计为三人三鬼。
船上的数量->东西两岸鬼的数量变化。
(R,G)R:人 G:鬼
借助于跳马问题的思路 也就是使 东(原)岸:(3,3)->(0,0)
△k:船上的人。
Sk+1 = Sk + △k S为(R,G)东岸人鬼数量
方向的数值化:用k的奇偶来判断方向。
东到西:奇数 东岸减 西到东:偶数 东岸加
定义一个二维向量dk 为第k次渡河的方案
dk =(Uk,Vk) Uk为上船人数 Vk为上船鬼数 一共五种可能性(2,0)(1,0)(1,1)(0,1)(0,2)
Sk =(Rk,Gk) : 东岸(原)
安全状态规则:(0,0123)(3,0123)(1,1)(2,2)
也就是:人数为3或0,或者人数与鬼数相等。

解决思路 多步决策
状态转移公式: Sk+1 = Sk +(-1)k次方 dk
迭代:从起始到结束、枚举:所有可能决策、递推:状态转移方程
用DO-WHILE进行迭代。

//可以运行的c++代码
#include<iostream>
#include<iomanip>
using namespace std;

struct state{int R,G;};

state s[20];						//记录状态转移过程
int choice[20] = {0};	 	//记录状态转移过程的决策号
int k;								//状态号

state d[6]={{0,0},{2,0},{1,0},{1,1},{0,1},{0,2}}; //0号决策不会用,1-5号决策会用

void display();
void transfer_state();

int main(){
	transfer_state();
	display();
	return 0;
}

void transfer_state(){
	k = 1;			//初始状态设为1
	s[1].R = 3;
	s[1].G = 3;

	do{
		int fx = 1;
		if( k%2 == 1 ) fx =-1; 		//利用fx确定摆渡方向 -1为东向西 1为西向东

		int i;  //决策号
		//针对下一状态(次序为k+1,choice数组元素初值为0),一次尝试所有决策(决策号从1到5)
		for(i=choice[k+1]+1; i <= 5; i++){ // 试探采用哪个决策能安全走一步
			int u = s[k].R + fx * d[i].R;			// 按第i号策略走1步后,东岸的人数u与鬼数v
			int v = s[k].G + fx * d[i].G; 

			if(u > 3 || v > 3 || u < 0 || v < 0) continue;

			bool AQ = (u == 3) || (u == 0)||(u == v);
			if(!AQ) continue;

			bool CHF = false; //是否重复
			for(int j = k-1; j >= 1; j -=2)
				if(s[j].R == u && s[j].G == v) CHF = true;
			if(CHF) continue;			
			
			k++;
			s[k].R = u;
			s[k].G = v;
			choice[k] = i;
			break;
		}
		if(i>5){choice[k+1]=0;k--;}
	}while(!(s[k].R==0 && s[k].G==0) );
}

void display(){
	for(int i = 1;i <= k; i++)
			cout<< setw(2)<<i<<":choice"<< choice[i] //决策号
					<<"{"<<d[choice[i]].R<<","<<d[choice[i]].G<<"}" //决策内容
					<<"("<<s[i].R<<","<<s[i].G<<")" // 状态内容
					<<endl;
}

变式:过河问题(人、狼、羊、菜)

https://blog.csdn.net/weixin_43829465/article/details/96316882

任务1.7 人鬼渡河 2.0

遍历所有的渡河方案,要求没有重复的步骤
视为特殊的跳马。
空间的坐标点从(3,3)变为(0,0)
dxy[] = {{},{},{},{},{}} 五种载人方案。

!重构是编程的一种常态。可以一步步地把那些函数抽离出来。

备忘录法(标记) 防止重复
利用三维数组存储判断重复性 方向、人数、鬼数

是否可以不用回溯
解题思路2:
方向也是状态的一部分。
查历史决策记录法判断是都重复
这种情况下不再需要回溯了。

思考:华容道、青蛙跳河(双向跳)、一个9L一个4L如何得到6L水。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值