码迷,mamicode.com
首页 > 编程语言 > 详细

用程序解密爱因斯坦经典难题(C++)

时间:2016-07-15 21:00:57      阅读:254      评论:0      收藏:0      [点我收藏+]

标签:

爱因斯坦曾在20世纪初提过一个经典问题,据说世界上有98%的人回答不出来
问题:在一条街上,有5座房子,喷了5中颜色。每个房子住着不同国籍的人。每个人喝不同的饮料,抽不同品牌的香烟,养不同的宠物。
问题是:谁养鱼?
提示:1.英国人住红色房子
2.瑞典人养狗
3.丹麦人喝茶
4.绿色房子在白色房子左边
5.绿房子主人喝咖啡
6.抽PallMall香烟的人养鸟
7.黄色房子的主人抽Dunhill香烟
8.住在中间房子的人喝牛奶
9.挪威人住第一间房
10.抽Bleeds香烟的人住在养猫的人隔壁
11.养马的人住抽Dunhill香烟的人隔壁
12.抽BlueMaster的人喝啤酒
13.德国人抽Prince香烟
14.挪威人住蓝色房子隔壁
15.抽Bleeds香烟的人有一个喝水的邻居

这个版本的程序我将不会使用任何推理,仅仅用程序描述这15个条件,然后穷举所有组合得到满足这15个条件的唯一解答。

首先构造“人”这个类型,根据题目可以知道每个人拥有颜色,香烟,饮料,宠物,位置,国籍这6个属性。虽然在现实中,人抽香烟,喝饮料,养宠物,拥有国籍,只有这4个属性,颜色和位置是房子的属性,但是人住在哪间房子,就决定了这个人的颜色和位置,所以简便起见,这6种属性全部归类到一个抽象的“人”中。新建一个Person.h的文件:

class Person
{
public:
	Person(Color,Country ,Drink ,Cigarette ,Pet ,int);
	~Person(void);
	Color color;
	Country country;
	Drink drink;
	Cigarette cigarette;
	Pet pet;
	int index;
};

根据这6种属性,可以创造出5^6个不同的“人”。接着,观察这15个条件,条件1,2,3,5,6,7,8,9,12,13,14,全部是以单个人作为约束条件,而剩下的4个都是以2个人之间的关系作为约束条件。首先可以把不满足条件的人过滤掉,再通过组合判断剩下的条件是否满足。比如1.英国人住红色房子,那么国籍是英国但是颜色不是红色的人就是不满足条件的,反过来,颜色是红色但是国籍不是英国的人也不满足条件,用程序的描述就是:

bool test1(const Person &p)
{
	if((p.country==England&&p.color!=Red)||(p.country!=England&&p.color==Red))
		return false;
	return true;
}

同理剩下的以人为约束的条件描述为:

bool test2(const Person &p)
{
	if((p.country==Sweden&&p.pet!=Dog)||(p.country!=Sweden&&p.pet==Dog))
		return false;
	return true;
}

bool test3(const Person &p)
{
	if((p.country==Denmark&&p.drink!=Tea)||(p.country!=Denmark&&p.drink==Tea))
		return false;
	return true;
}

bool test5(const Person &p)
{
	if((p.color==Green&&p.drink!=Coffee)||(p.color!=Green&&p.drink==Coffee))
		return false;
	return true;
}

bool test6(const Person &p)
{
	if((p.cigarette==PallMall&&p.pet!=Bird)||(p.cigarette!=PallMall&&p.pet==Bird))
		return false;
	return true;
}

bool test7(const Person &p)
{
	if((p.color==Yellow&&p.cigarette!=Dunhill)||(p.color!=Yellow&&p.cigarette==Dunhill))
		return false;
	return true;
}

bool test8(const Person &p)
{
	if((p.index==3&&p.drink!=Milk)||(p.index!=3&&p.drink==Milk))
		return false;
	return true;
}

bool test9(const Person &p)
{
	if((p.country==Norway&&p.index!=1)||(p.country!=Norway&&p.index==1))
		return false;
	return true;
}

bool test12(const Person &p)
{
	if((p.cigarette==BlueMaster&&p.drink!=Bear)||(p.cigarette!=BlueMaster&&p.drink==Bear))
		return false;
	return true;
}

bool test13(const Person &p)
{
	if((p.country==Germany&&p.cigarette!=Prince)||(p.country!=Germany&&p.cigarette==Prince))
		return false;
	return true;
}
//条件14可直接表示为蓝色房子是第二间房
bool test14(const Person &p)
{
	if((p.color==Blue&&p.index!=2)||(p.color!=Blue&&p.index==2))
		return false;
	return true;
}
筛选出了有效的“人”,接着把这些“人”装进不同的"房子"里

vector<Person> v1;
	vector<Person> v2;
	vector<Person> v3;
	vector<Person> v4;
	vector<Person> v5;
	for(int color=0;color<5;++color)
		for(int country=0;country<5;++country)
			for(int drink=0;drink<5;++drink)
				for(int cigarette=0;cigarette<5;++cigarette)
					for(int pet=0;pet<5;++pet)
						for(int index=1;index<=5;++index)
						{
							Person p((Color)color,(Country)country,(Drink)drink,(Cigarette)cigarette,(Pet)pet,index);
							if(test1(p)&&test2(p)&&test3(p)&&test5(p)&&test6(p)&&test7(p)&&test8(p)&&test9(p)&&test12(p)&&test13(p)&&test14(p))
							{
								switch(p.index)
								{
								case 1:
									v1.push_back(p);
									break;
								case 2:
									v2.push_back(p);
									break;
								case 3:
									v3.push_back(p);
									break;
								case 4:
									v4.push_back(p);
									break;
								case 5:
									v5.push_back(p);
									break;
								}
							}
						}

5个集合分别表示编号为1-5的5个房子,通过循环构建“人”,把符合条件的人扔到不同的房子里去。这样一来,得到了5个房子,每个房子里都有一些人,这些人都是已经满足了前面所有条件的,接着每个房子里分别派出一个人,判断这5个人是否能满足剩下的条件,如果满足,那这5个人就是答案,不满足,那就换人,直到所有的人都组合过就结束。

	size_t s1=v1.size();
	size_t s2=v2.size();
	size_t s3=v3.size();
	size_t s4=v4.size();
	size_t s5=v5.size();
    int count=0;
	string countryName[5]={"英国人","瑞典人","丹麦人","挪威人","德国人"};
	string colorName[5]={"蓝色","绿色","白色","红色","黄色"};
	string drinkName[5]={"茶","牛奶","咖啡","啤酒","水"};
	string cigaretteName[5]={"PallMall","Dunhill","Bleeds","BlueMaster","Prince"};
	string petName[5]={"鱼","狗","鸟","猫","马"};
	for(int i1=0;i1<s1;++i1)
		for(int i2=0;i2<s2;++i2)
			for(int i3=0;i3<s3;++i3)
				for(int i4=0;i4<s4;++i4)
					for(int i5=0;i5<s5;++i5)
					{
						Person pp[5]={v1[i1],v2[i2],v3[i3],v4[i4],v5[i5]};
						if(testDistinct(pp)&&test4(pp)&&test10(pp)&&test11(pp)&&test15(pp))
						{
							cout<<"答案"<<++count<<":"<<endl;
							for(int i=0;i<5;++i)
							{
								cout<<"第"<<i+1<<"间房是"<<colorName[pp[i].color]<<",住的是"<<countryName[pp[i].country]<<",喝"<<drinkName[pp[i].drink]<<",抽"<<cigaretteName[pp[i].cigarette]<<",养"<<petName[pp[i].pet]<<endl;
							}
						}
					}

这里除了剩下的4个条件还有一个隐藏条件,那就是人的属性是不能重复的,例如这5个人当中不能有2个英国人,也不能有2个人或者3个人同时养鱼。

去重的验证,这里用了一点小技巧:

bool testDistinct(Person *pp) 
{
	int state[25]={0};
	for(int i=0;i<5;++i)
	{
		for(int j=0;j<5;++j)
		{
			state[j]+=pp[i].color==(Color)j;
			state[j+5]+=pp[i].drink==(Drink)j;
			state[j+10]+=pp[i].cigarette==(Cigarette)j;
			state[j+15]+=pp[i].pet==(Pet)j;
			state[j+20]+=pp[i].country==(Country)j;
		}
	}

	for(int i=0;i<25;i++)
	{
		if(state[i]!=1)
			return false;
	}
	return true;
}
人已经被分到不同房子里了,所以这5个人的index肯定是不会重复了,只需判断剩下的5个属性是否重复。每种属性都是5个状态,5种属性就是25个状态,然后用一个长度为25的一维数组来表示,比如state[0]表示蓝色属性的数量,state[1]表示绿色属性的数量,state[5]表示喝茶人的数量,然后5个人循环判断是否存在这些状态,如果有那么state数组计数就加1。如果组合是不存在重复的,也就表示state数组的每一项都正好是1,如果其中某一项不等于1,就表示一定存在重复。

剩下的4个条件判断:

bool test4(Person *pp)
{
	Person *p1,*p2;
	for(int i=0;i<5;++i)
	{
		if(pp[i].color==Green)
			p1=&pp[i];
	    if(pp[i].color==White)
			p2=&pp[i];
	}
	return p1->index==p2->index-1;
}

bool test10(Person *pp)
{
	Person *p1,*p2;
	for(int i=0;i<5;++i)
	{
		if(pp[i].cigarette==Bleeds)
			p1=&pp[i];
	    if(pp[i].pet==Cat)
			p2=&pp[i];
	}
	return p1->index==p2->index-1||p1->index==p2->index+1;
}

bool test11(Person *pp)
{
	Person *p1,*p2;
	for(int i=0;i<5;++i)
	{
		if(pp[i].pet==Horse)
			p1=&pp[i];
	    if(pp[i].cigarette==Dunhill)
			p2=&pp[i];
	}
	return p1->index==p2->index-1||p1->index==p2->index+1;
}

bool test15(Person *pp)
{
	Person *p1,*p2;
	for(int i=0;i<5;++i)
	{
		if(pp[i].drink==Water)
			p1=&pp[i];
	    if(pp[i].cigarette==Bleeds)
			p2=&pp[i];
	}
	return p1->index==p2->index-1||p1->index==p2->index+1;
}

原理就是从5个人中找到条件中的2个人,然后判断他们的index属性,所谓邻居,隔壁都是表示他们的index是相连的。

到这里代码就已经写完了,最后的计算结果是:

技术分享


最后总结:为什么有的问题人无法解决却可以用计算机解决,虽然这个题目用画表格的方法推理出答案也不太难,但是相比较而言,计算机程序的思路更简单粗暴,而人不可能用这种方式思考问题。假如你要计算987*512,而你并不会乘法口诀,你怎么得到结果?这时给了你一张无比巨大的草稿纸,而且在上面的书写速度会非常快,幸好你的加法还不错,于是你不断演算987+987=1974,1974+987=2961...至到加上512个987为止,最终你得到了正确答案,由于书写速度非常快,甚至快过你用乘法口诀得出答案,这就是计算机解决人无法解决问题的原理,计算机就好比这样的草稿纸。至于如何把问题描述给计算机计算(把乘法问题转成加法问题),以及让计算机运行得更快(你可以累加512次,你也可以仅仅累加9次),这就涉及到计算机科学的一门重要课程--数据结构与算法。


本文章原创地址:http://blog.csdn.net/maidou0921/article/details/51910075

用程序解密爱因斯坦经典难题(C++)

标签:

原文地址:http://blog.csdn.net/maidou0921/article/details/51910075

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!