原来的博客是去年大二的时候写的,后来发现没有解释可能比较难看懂(毕竟代码比较长),于是后面加了一段解说,但是发现以前的博客摘要居然没有改过来。。。。于是一直想着要重新写一篇,直到最近老师布置相关作业,我顺手把这个算法优化了一下,去掉了一些多于的操作和不合理的结构,算法的时间消耗顿时减少了一半多(优化很重要啊)!
下面进入正题:
首先是题目(非爱因斯坦测试原题,不知道是哪个老师出的╮(╯▽╰)╭):
有五座房子,每座房子的颜色不同,里面分别住着不同国家的人,每个人都有自己养的不同宠物喜欢喝的不同饮料、抽的不同牌子的烟。现已知以下一些信息:
英国人住在红色的房子里 ;
西班牙人养了一条狗;
挪威人住在左边的第一个房子里;
黄房子里的人喜欢抽kools牌的香烟;
抽chesterfields牌香烟的人与养狐狸的人是邻居;
挪威人住在蓝色房子旁边;
抽winston牌香烟的人养了一个蜗牛;
抽lucky strike牌香烟的人喜欢喝橘子汁;
乌克兰人喜欢喝茶;
日本人抽parlianments牌的烟;
抽kools牌香烟的人与养马的人是邻居;
喜欢喝咖啡的人住在绿房子里;
绿房子在象牙白房子的右边;
中间那个房子里的人喜欢喝牛奶。
根据以上条件,请你判断哪个房子里的人养斑马?哪个房子里的人喜欢喝水?最后把所有的东西对号入座。
代码太长,就不先上了。先看运行结果:
首先说一下,由于不能确定答案的个数,所以遍历所有的情况是必须的,也就是说即使找出了一个答案,程序也不能立即返回,因为答案有可能不唯一。
然后是解题思路,刚开始也没什么思路,经过一番混沌式的探索之后(……),渐渐摸到一些门道。核心思想就是"分层过滤"。将房子从左到右(或者从右到左也可以)依次搭建起来,如果建立的房子满足条件,则保留并建立下一所房子,否则推倒重建。这样可以减少大量不必要的计算组合,速度也会快很多。
说到过滤,过滤的条件自然是重要的。
根据题目提供的信息,我们可以将所给的14个条件分为三类:
- 确定型(根据描述可以完全判定某项属性的位置)
- 挪威人住在左边的第一个房子里;
- 挪威人住在蓝色房子旁边;
- 中间那个房子里的人喜欢喝牛奶
- 位置关系型(由位置条件组成的二维关系)
- 抽chesterfields牌香烟的人与养狐狸的人是邻居
- 抽kools牌香烟的人与养马的人是邻居
- 绿房子在象牙白房子的右边
- 非位置关系的二维关系型(由与位置条件无关的二元关系组成)
- 英国人住在红色的房子里;
- 西班牙人养了一条狗;
- 黄房子里的人喜欢抽kools牌的香烟
- 抽winston牌香烟的人养了一个蜗牛
- 抽lucky strike牌香烟的人喜欢喝橘子汁
- 乌克兰人喜欢喝茶
- 日本人抽parlianments牌的烟
- 喜欢喝咖啡的人住在绿房子里
然后,根据前面说过的方法,这里选择从左往右进行搭建,生成对象时,如果有确定型的关系,则直接运用到属性中,其他条件则使用循环进行遍历。对象生成之后,放入条件表达式中进行匹配,如果满足条件,就以此为基础生成下一个对象,否则就打回重建。
大致原理就是这样,关键点在于条件怎么组合才能最大限度的将所有不符合条件的对象驳回,以减少后续筛选判断的次数。
这里使用的是离散数学中的逆否命题。关于逆否命题的概念此处不多讲,如不明白请自行百度。 百度百科:逆否命题
简而言之,利用原命题与逆否命题的等价性,我们可以对判断条件的逻辑进行强化,以达到增强筛选条件的目的。
举个例子:"英国人住在红色的房子里"这个条件,如果直接翻译就是"如果一个对象的国籍是英国,并且他所住的房子是红色的,则条件为真";而他的逆否命题“不住在红色房子里的人不是英国人”这样一句看起来很绕,听起来像废话一样的句子翻译后的效果就不一样了,它的翻译是“如果存在一个对象的国籍是英国,并且他住的房子不是红色 或者 如果存在一个对象的房子是红色,但是他的国籍不是英国,则条件为假”。这个条件很显然比前面一个条件能筛掉的组合更多。要知道,漏筛一种情况对后面的条件判断次数的影响都是巨大的,所以,越先建立的对象要筛得的越彻底越好。对比下面一个图,你可以看到区别。
下图是加入了五个经过人工分析后得到的条件的运算结果,这五个条件分别是:
- 第一间房子不是红色、蓝色、白色,主人不喝牛奶、茶,不抽parlianments烟,不养狗
- 第二间房子不住挪威人、英国人,主人不喝咖啡、牛奶,不抽kools的烟
- 第三间房自不是绿色、蓝色,主人不是乌克兰人,不是挪威人,不抽lucky strike的烟
- 第四间房子不是蓝色、红色、黄色,主人不是挪威人,不喝牛奶
- 第五间房子不是蓝色、白色,主人不是挪威人,不喝牛奶
其中T1,T2,T3,T4,T5分别是每栋房子通过筛选条件的次数,从中我们可以计算出上图的结果是经过 13×78×65×141×141 >13亿次遍历建立的,而下面的结果只经历了1×3×11×17×11=6171次遍历,差异明显。
下面是本程序的全部代码(为了代码的可读性,大部分枚举常量采用中文定义):
//有五座房子,每座房子的颜色不同,里面分别住着不同国家的人,每个人都有自己养的不同宠物、
//喜欢喝的不同饮料、抽的不同牌子的烟。现已知以下一些信息:
//英国人住在红色的房子里;
//西班牙人养了一条狗;
//挪威人住在左边的第一个房子里;
//黄房子里的人喜欢抽kools牌的香烟;
//抽chesterfields牌香烟的人与养狐狸的人是邻居;
//挪威人住在蓝色房子旁边;
//抽winston牌香烟的人养了一个蜗牛;
//抽lucky strike牌香烟的人喜欢喝橘子汁;
//乌克兰人喜欢喝茶;
//日本人抽parlianments牌的烟;
//抽kools牌香烟的人与养马的人是邻居;
//喜欢喝咖啡的人住在绿房子里;
//绿房子在象牙白房子的右边;
//中间那个房子里的人喜欢喝牛奶。
//根据以上条件,请你判断哪个房子里的人养斑马?哪个房子里的人喜欢喝水?最后把所有的东西对号入座。
using System;
using System.Diagnostics;
namespace 爱因斯坦智力题暴力破解
{
class Program
{
static void Main(string[] args)
{
Stopwatch ts = new Stopwatch();
ts.Start();
//被计时的代码段
Work work = new Work();
if (work.Start())
Console.WriteLine("以上为全部答案!");
else
Console.WriteLine("此题没有答案!");
//
ts.Stop();
Console.WriteLine("\n耗时: {0} ms", ts.ElapsedMilliseconds);
Console.ReadKey(true);
}
}
public class Work
{
int progress = 0;//生成进度
Person[] persons;
int t1, t2, t3, t4, t5;//记录每间房子生成的次数
public Work()
{
persons = new Person[7];//实际使用1~5,0和6用来防止数组越界,因为要做相邻判断
persons[0] = new Person();
persons[6] = new Person();
}
public bool Start()
{
return h1();
}
//组装第一间房子(从左往右数)
private bool h1()
{
progress++;
for (int h = 1; h <= 5; h++)
for (int p = 1; p <= 5; p++)
for (int d = 1; d <= 5; d++)
for (int s = 1; s <= 5; s++)
{
//确定条件1:挪威人住在左边的第一个房子里;
persons[1] = new Person(h, 3, p, d, s);
//第一间房子不是红色、蓝色、白色,主人不喝牛奶,茶,不抽parlianments烟,不养狗(后面几个类似的注释都是经过分析得到的加强条件,如果解除这5条注释耗时可以达到现在的一半)
//if (persons[1].housecolor != HouseColor.红色 && persons[1].housecolor != HouseColor.蓝色 && persons[1].housecolor != HouseColor.白色 && persons[1].drink != Drink.牛奶 && persons[1].drink != Drink.茶 && persons[1].pet != Pet.狗 && persons[1].smoke != Smoke.parlianments)
if (match())
{
t1++;
if (!h2())
continue;
else
return true;
}
}
return false;
}
//组装第二间房子
private bool h2()
{
progress++;
for (int c = 1; c <= 5; c++)
for (int p = 1; p <= 5; p++)
for (int d = 1; d <= 5; d++)
for (int s = 1; s <= 5; s++)
{
//确定条件2:第二间房子是蓝色的
persons[2] = new Person(5, c, p, d, s);
if (persons[2].HasEqual(persons[1]))
continue;
//第二间房子主人不是挪威人、英国人,不喝咖啡、牛奶,不抽kools的烟
//if (persons[2].country != Country.挪威 && persons[2].country != Country.英国 && persons[2].drink != Drink.咖啡 && persons[2].drink != Drink.牛奶 && persons[2].smoke != Smoke.kools)
if (match())
{
t2++;
if (!h3())
continue;
else
return true;
}
}
progress--;
return false;
}
//组装第三间房子
private bool h3()
{
progress++;
for (int h = 1; h <= 5; h++)
for (int c = 1; c <= 5; c++)
for (int p = 1; p <= 5; p++)
for (int s = 1; s <= 5; s++)
{
//确定条件3:中间房子的人喝牛奶
persons[3] = new Person(h, c, p, 2, s);
if (persons[3].HasEqual(persons[2]) || persons[3].HasEqual(persons[1]))
continue;
//第三间房自不是绿色、蓝色,主人不是乌克兰人,不是挪威人,不抽lucky strike的烟
//if (persons[3].housecolor != HouseColor.绿色 && persons[3].housecolor != HouseColor.蓝色 && persons[3].country != Country.乌克兰 && persons[3].country != Country.挪威 && persons[3].smoke != Smoke.lucky_strike)
if (match())
{
t3++;
if (!h4())
continue;
else
return true;
}
}
progress--;
return false;
}
//组装第四间房子
private bool h4()
{
progress++;
for (int h = 1; h <= 5; h++)
for (int c = 1; c <= 5; c++)
for (int p = 1; p <= 5; p++)
for (int d = 1; d <= 5; d++)
for (int s = 1; s <= 5; s++)
{
persons[4] = new Person(h, c, p, d, s);//第四间房子
if (persons[4].HasEqual(persons[3]) || persons[4].HasEqual(persons[2]) || persons[4].HasEqual(persons[1]))
continue;
//第四间房子不是蓝色、红色、黄色,主人不是挪威人,不喝牛奶
//if (persons[4].housecolor != HouseColor.蓝色 && persons[4].housecolor != HouseColor.红色 && persons[4].housecolor != HouseColor.黄色 && persons[4].country != Country.挪威 && persons[4].drink != Drink.牛奶)
if (match())
{
t4++;
if (!h5())
continue;
else
return true;
}
}
progress--;
return false;
}
//组装第五间房子
private bool h5()
{
progress++;
for (int h = 1; h <= 5; h++)
for (int c = 1; c <= 5; c++)
for (int p = 1; p <= 5; p++)
for (int d = 1; d <= 5; d++)
for (int s = 1; s <= 5; s++)
{
persons[5] = new Person(h, c, p, d, s);//第五间房子
if (persons[5].HasEqual(persons[4]) || persons[5].HasEqual(persons[3]) || persons[5].HasEqual(persons[2]) || persons[5].HasEqual(persons[1]))
continue;
//第五间房子不是蓝色、白色,主人不是挪威人,不喝牛奶
//if (persons[5].housecolor != HouseColor.蓝色 && persons[5].housecolor != HouseColor.白色 && persons[5].country != Country.挪威 && persons[5].drink != Drink.牛奶)
t5++;
if (test())
{
ShowResult();
Anwser();
return true;
}
}
progress--;
return false;
}
private bool match() //二维条件的逆否式
{
/*逆否式等价于原命题,此处使用逆否式可以加强原命题*/
//英国人住在红色的房子里;
for (int i = 1; i <= progress; i++)
if (persons[i].country == Country.英国 && persons[i].housecolor != HouseColor.红色 || persons[i].country != Country.英国 && persons[i].housecolor == HouseColor.红色)
return false;
//乌克兰人喜欢喝茶;
for (int i = 1; i <= progress; i++)
if (persons[i].country == Country.乌克兰 && persons[i].drink != Drink.茶 || persons[i].country != Country.乌克兰 && persons[i].drink == Drink.茶)
return false;
//西班牙人养了一条狗;
for (int j = 1; j <= progress; j++)
if (persons[j].country == Country.西班牙 && persons[j].pet != Pet.狗 || persons[j].country != Country.西班牙 && persons[j].pet == Pet.狗)
return false;
//黄房子里的人喜欢抽kools牌的香烟;
for (int k = 1; k <= progress; k++)
if (persons[k].housecolor == HouseColor.黄色 && persons[k].smoke != Smoke.kools || persons[k].housecolor != HouseColor.黄色 && persons[k].smoke == Smoke.kools)
return false;
//抽winston牌香烟的人养了一个蜗牛;
for (int x = 1; x <= progress; x++)
if (persons[x].pet == Pet.蜗牛 && persons[x].smoke != Smoke.winston || persons[x].pet != Pet.蜗牛 && persons[x].smoke == Smoke.winston)
return false;
//抽lucky strike牌香烟的人喜欢喝橘子汁;
for (int y = 1; y <= progress; y++)
if (persons[y].drink == Drink.橘子汁 && persons[y].smoke != Smoke.lucky_strike || persons[y].drink != Drink.橘子汁 && persons[y].smoke == Smoke.lucky_strike)
return false;
//日本人抽parlianments牌的烟;
for (int z = 1; z <= progress; z++)
if (persons[z].country == Country.日本 && persons[z].smoke != Smoke.parlianments || persons[z].country != Country.日本 && persons[z].smoke == Smoke.parlianments)
return false;
//喜欢喝咖啡的人住在绿房子里;
for (int l = 1; l <= progress; l++)
if (persons[l].housecolor == HouseColor.绿色 && persons[l].drink != Drink.咖啡 || persons[l].housecolor != HouseColor.绿色 && persons[l].drink == Drink.咖啡)
return false;
return true;
}
private bool test()
{
if (test_3() && test_1() && test_2())
{
return true;
}
return false;
}
private bool test_1()//已给定的二维条件4/8
{
//英国人住在红色的房子里;
for (int m = 1; m <= progress; m++)
if (persons[m].country == Country.英国 && persons[m].housecolor == HouseColor.红色)
//乌克兰人喜欢喝茶;
for (int i = 1; i <= progress; i++)
if (persons[i].country == Country.乌克兰 && persons[i].drink == Drink.茶)
//西班牙人养了一条狗;
for (int j = 1; j <= progress; j++)
if (persons[j].country == Country.西班牙 && persons[j].pet == Pet.狗)
//黄房子里的人喜欢抽kools牌的香烟;
for (int k = 1; k <= progress; k++)
if (persons[k].housecolor == HouseColor.黄色 && persons[k].smoke == Smoke.kools)
return true;
return false;
}
private bool test_2()//已给定的二维条件4/8
{
//抽winston牌香烟的人养了一个蜗牛;
for (int x = 1; x <= progress; x++)
if (persons[x].pet == Pet.蜗牛 && persons[x].smoke == Smoke.winston)
//抽lucky strike牌香烟的人喜欢喝橘子汁;
for (int y = 1; y <= progress; y++)
if (persons[y].drink == Drink.橘子汁 && persons[y].smoke == Smoke.lucky_strike)
//日本人抽parlianments牌的烟;
for (int z = 1; z <= progress; z++)
if (persons[z].country == Country.日本 && persons[z].smoke == Smoke.parlianments)
//喜欢喝咖啡的人住在绿房子里;
for (int l = 1; l <= progress; l++)
if (persons[l].housecolor == HouseColor.绿色 && persons[l].drink == Drink.咖啡)
return true;
return false;
}
private bool test_3()//相邻位置判断条件3/3
{
//绿房子在象牙白房子的右边;
for (int k = 1; k <= progress; k++)
if (persons[k].housecolor == HouseColor.白色 && persons[(k + 1) % 6].housecolor == HouseColor.绿色)
//抽chesterfields牌香烟的人与养狐狸的人是邻居;
for (int i = 1; i <= progress; i++)
if (persons[i].pet == Pet.狐狸 && persons[i - 1].smoke == Smoke.chesterfields || persons[i].pet == Pet.狐狸 && persons[(i + 1) % 6].smoke == Smoke.chesterfields)
//抽kools牌香烟的人与养马的人是邻居;
for (int j = 1; j <= progress; j++)
if (persons[j].smoke == Smoke.kools && persons[j - 1].pet == Pet.马 || persons[j].smoke == Smoke.kools && persons[(j + 1) % 6].pet == Pet.马)
return true;
return false;
}
public void ShowResult()
{
for (int n = 1; n <= 5; n++)
Console.WriteLine(persons[n]);
Console.WriteLine("--------------------------------------");
Console.WriteLine("T1:{0}\nT2:{1}\nT3:{2}\nT4:{3}\nT5:{4}\n", t1, t2, t3, t4, t5);
}
public void Anwser()
{
Console.WriteLine("答案是:");
for (int i = 1; i <= 5; i++)
if (persons[i].drink == Drink.水)
Console.WriteLine(" {0}人喝{1}", persons[i].country, persons[i].drink);
for (int i = 1; i <= 5; i++)
if (persons[i].pet == Pet.斑马)
Console.WriteLine(" {0}人养{1}", persons[i].country, persons[i].pet);
Console.WriteLine();
}
}
public enum HouseColor { 红色 = 1, 白色, 黄色, 绿色, 蓝色 }
public enum Country { 英国 = 1, 西班牙, 挪威, 乌克兰, 日本 }
public enum Pet { 狐狸 = 1, 狗, 蜗牛, 马, 斑马 }
public enum Drink { 咖啡 = 1, 牛奶, 水, 茶, 橘子汁 }
public enum Smoke { kools = 1, chesterfields, winston, lucky_strike, parlianments }
public class Person
{
public HouseColor housecolor;
public Country country;
public Pet pet;
public Drink drink;
public Smoke smoke;
public Person() { }
public Person(int h, int c, int p, int d, int s)
{
switch (h)
{
case 1:
housecolor = HouseColor.红色;
break;
case 2:
housecolor = HouseColor.白色;
break;
case 3:
housecolor = HouseColor.黄色;
break;
case 4:
housecolor = HouseColor.绿色;
break;
case 5:
housecolor = HouseColor.蓝色;
break;
}
switch (c)
{
case 1:
country = Country.英国;
break;
case 2:
country = Country.西班牙;
break;
case 3:
country = Country.挪威;
break;
case 4:
country = Country.乌克兰;
break;
case 5:
country = Country.日本;
break;
}
switch (p)
{
case 1:
pet = Pet.狐狸;
break;
case 2:
pet = Pet.狗;
break;
case 3:
pet = Pet.蜗牛;
break;
case 4:
pet = Pet.马;
break;
case 5:
pet = Pet.斑马;
break;
}
switch (d)
{
case 1:
drink = Drink.咖啡;
break;
case 2:
drink = Drink.牛奶;
break;
case 3:
drink = Drink.水;
break;
case 4:
drink = Drink.茶;
break;
case 5:
drink = Drink.橘子汁;
break;
}
switch (s)
{
case 1:
smoke = Smoke.kools;
break;
case 2:
smoke = Smoke.chesterfields;
break;
case 3:
smoke = Smoke.winston;
break;
case 4:
smoke = Smoke.lucky_strike;
break;
case 5:
smoke = Smoke.parlianments;
break;
}
}
public bool HasEqual(Person pe)//只要有部分元素相等则返回真,非全等
{
if (housecolor == pe.housecolor || country == pe.country || pet == pe.pet || drink == pe.drink || smoke == pe.smoke)
return true;
return false;
}
public override string ToString()
{
return housecolor + " " + country + " " + pet + " " + drink + " " + smoke + " ";
}
}
}
转载请保留出处!谢谢!http://blog.csdn.net/u012436908/article/details/40486169