就当是闲得蛋疼吧,此次改进之后算法,10次运行平均耗时不超过9ms/次, 千次运行平均耗时不超过4ms/次!同样的环境,算法更简洁、更高效。
还是老题目:
有五座房子,每座房子的颜色不同,里面分别住着不同国家的人,每个人都有自己养的不同宠物喜欢喝的不同饮料、抽的不同牌子的烟。现已知以下一些信息:
英国人住在红色的房子里 ;
西班牙人养了一条狗;
挪威人住在左边的第一个房子里;
黄房子里的人喜欢抽kools牌的香烟;
抽chesterfields牌香烟的人与养狐狸的人是邻居;
挪威人住在蓝色房子旁边;
抽winston牌香烟的人养了一个蜗牛;
抽lucky strike牌香烟的人喜欢喝橘子汁;
乌克兰人喜欢喝茶;
日本人抽parlianments牌的烟;
抽kools牌香烟的人与养马的人是邻居;
喜欢喝咖啡的人住在绿房子里;
绿房子在象牙白房子的右边;
中间那个房子里的人喜欢喝牛奶。
根据以上条件,请你判断哪个房子里的人养斑马?哪个房子里的人喜欢喝水?最后把所有的东西对号入座。
还是先将条件分类:
- 确定型(根据描述可以完全判定某项属性的位置)
- 挪威人住在左边的第一个房子里;
- 挪威人住在蓝色房子旁边;
- 中间那个房子里的人喜欢喝牛奶
- 位置关系型(由位置条件组成的二维关系)
- 抽chesterfields牌香烟的人与养狐狸的人是邻居
- 抽kools牌香烟的人与养马的人是邻居
- 绿房子在象牙白房子的右边
- 非位置关系的二维关系型(由与位置条件无关的二元关系组成)
- 英国人住在红色的房子里;
- 西班牙人养了一条狗;
- 黄房子里的人喜欢抽kools牌的香烟
- 抽winston牌香烟的人养了一个蜗牛
- 抽lucky strike牌香烟的人喜欢喝橘子汁
- 乌克兰人喜欢喝茶
- 日本人抽parlianments牌的烟
- 喜欢喝咖啡的人住在绿房子里
这次我们的算法变了。
第一步:过滤。首先,我们将每栋房子都作为一个单独的个体来看(它具有颜色,国籍,宠物,饮料,香烟这五种属性),不考虑重复情况,则它总共有5^5=3125种不同的组合。然后,根据上面的8个非位置关系的二元关系条件对这3125种组合进行筛选,需要说明的是这里我们必须采用原条件的逆否命题来进行筛选,即“英国人住在红色的房子里”这样的条件要解释成“不住在红色房子里的人不是英国人”,这样,筛选剩下的情况中任何一种都一定符合以上所说的8个条件。这一步之后,我们将每栋房子从3125种情况缩小到了78种!也就是说,不管你从哪一栋房子先盖起,它所有的可能就只需要在这78种情况中去找就够了,不管你选哪一个,它都一定不会违背8个条件中的任何一条。不过,即使是这样,所有排列组合的数量依然很大,根据组合公式78选5全排列的数目为78×77×76×75×74>25亿。所以我们还要进一步缩小组合的范围。
第二步:分类。这一步,我们根据上面筛选剩下的78个条件构建5个链表,分别代表5栋房子各自可能的情况。根据确定型的三个条件(参见上部条件分类)将上述78种情况分类装入5个链表中(例如:如果是挪威人,则放入第一栋房子对应的链表,如果房子是蓝色,则放入第二栋房子对应的链表;如果喝的是牛奶,则放入第三个房子对应的链表,否则放入第四个和第五个链表中)。这一步之后,你得到的五个链表就是每栋房子最终能够进行组合的情况了。每栋房子的组合数分别是:32,13,9,24,24。其中第四个和第五个相等的原因是它们的筛选条件并没有给定,二者都可以从余下的24种情况中挑选并组合。这一步之后,组合数下降到了:32×13×9×24×24>215万。
最后一步,就是组合了。这一步最重要的是解决组合有可能出现重复属性的问题,比如有可能出现三栋房子的人都喝牛奶或者都是抽同一种烟之类的情况。排查操作可以放到最后一步去做,也可以按照”分层过滤“的方法来做。前者需要遍历所有215万种情况,耗时在300ms左右,而后者在只需在每一层都保证与前面的n层(n<=4)不出现相同属性即可,判断和循环的次数都大大减少,遍历的效率更高!对于此题,只有8640种情况通过了4层考验。而结果就是,这8640中情况中只有一种情况通过了最后三个条件的检验,就是最终的唯一结果了(在无法预计的情况下,结果的可能性也许会有多种,此题只有一种)。
下面是输出结果,分别比较了1次,10次,100次,1000次执行的平均耗时,最低只需3ms:
然后就是源代码了:
//转载请保留出处!谢谢!http://blog.csdn.net/u012436908/article/details/40558915
//题目:
//有五座房子,每座房子的颜色不同,里面分别住着不同国家的人,每个人都有自己养的不同宠物、
//喜欢喝的不同饮料、抽的不同牌子的烟。现已知以下一些信息:
//英国人住在红色的房子里;
//西班牙人养了一条狗;
//挪威人住在左边的第一个房子里;
//黄房子里的人喜欢抽kools牌的香烟;
//抽chesterfields牌香烟的人与养狐狸的人是邻居;
//挪威人住在蓝色房子旁边;
//抽winston牌香烟的人养了一个蜗牛;
//抽lucky strike牌香烟的人喜欢喝橘子汁;
//乌克兰人喜欢喝茶;
//日本人抽parlianments牌的烟;
//抽kools牌香烟的人与养马的人是邻居;
//喜欢喝咖啡的人住在绿房子里;
//绿房子在象牙白房子的右边;
//中间那个房子里的人喜欢喝牛奶。
//根据以上条件,请你判断哪个房子里的人养斑马?哪个房子里的人喜欢喝水?最后把所有的东西对号入座。
using System;
using System.Diagnostics;
using System.Collections.Generic;
namespace 类爱因斯坦测试题
{
class Program
{
static void Main(string[] args)
{
Stopwatch ts = new Stopwatch();
int times =1;//运行次数
bool anwser=false;
ts.Start();
//被计时的代码段
Work work = new Work();
for (int i = 0; i < times;i++ )
{
anwser = work.Start();
}
if (!anwser)
Console.Write("本题无解!");
ts.Stop();
Console.WriteLine("\n{0}次运行平均耗时: {1} ms/次",times, ts.ElapsedMilliseconds/(double)times);
Console.ReadKey(true);
}
}
public class Work
{
int count = 0;//答案个数
Person[] persons;
List<Person> pli = new List<Person>();
List<Person> H1 = new List<Person>();
List<Person> H2 = new List<Person>();
List<Person> H3 = new List<Person>();
List<Person> H4 = new List<Person>();
List<Person> H5= new List<Person>();
public Work()
{
persons = new Person[7];//实际使用1~5,0和6用来防止数组越界,因为要做相邻判断
persons[0] = new Person();
persons[6] = new Person();
}
public bool Start()
{
pli.Clear();
H1.Clear();
H2.Clear();
H3.Clear();
H4.Clear();
H5.Clear();
Filter(pli);//过滤
Classify(pli);//分类
return Find();
}
private void Filter(List<Person> list)
{
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++)
{
Person pe = new Person(h, c, p, d, s);
if (match(pe))
list.Add(pe);
}
}
private void Classify(List<Person> list)
{
foreach (Person pe in list)
{
if (pe.country == Country.挪威)
H1.Add(pe);
else if (pe.housecolor == HouseColor.蓝色)
H2.Add(pe);
else if (pe.drink == Drink.牛奶)
H3.Add(pe);
else
{
H4.Add(pe);
}
}
H5 = H4;
}
private bool Find()
{
bool workout = false;
count = 0;
foreach (Person p1 in H1)
{
persons[1] = p1;
foreach (Person p2 in H2)
{
persons[2] = p2;
if (NoSame(2))
foreach (Person p3 in H3)
{
persons[3] = p3;
if (NoSame(3))
foreach (Person p4 in H4)
{
persons[4] = p4;
if (NoSame(4))
foreach (Person p5 in H5)
{
persons[5] = p5;
if (NoSame(5) && test())
{
count++;
ShowResult();
Anwser();
workout = true;
}
}
}
}
}
}
return workout;
}
private bool NoSame(int k)
{
for (int i=1;i<k;i++ )
{
if (persons[k].HasEqual(persons[i]))
return false;
}
return true;
}
private bool match(Person p)//二元关系条件匹配
{
/*逆否式等价于原命题,此处使用逆否式可以加强原命题*/
//英国人住在红色的房子里;
if (p.country == Country.英国 && p.housecolor != HouseColor.红色 || p.country != Country.英国 && p.housecolor == HouseColor.红色)
return false;
//乌克兰人喜欢喝茶;
if (p.country == Country.乌克兰 && p.drink != Drink.茶 || p.country != Country.乌克兰 && p.drink == Drink.茶)
return false;
//西班牙人养了一条狗;
if (p.country == Country.西班牙 && p.pet != Pet.狗 || p.country != Country.西班牙 && p.pet == Pet.狗)
return false;
//黄房子里的人喜欢抽kools牌的香烟;
if (p.housecolor == HouseColor.黄色 && p.smoke != Smoke.kools || p.housecolor != HouseColor.黄色 && p.smoke == Smoke.kools)
return false;
//抽winston牌香烟的人养了一个蜗牛;
if (p.pet == Pet.蜗牛 && p.smoke != Smoke.winston || p.pet != Pet.蜗牛 && p.smoke == Smoke.winston)
return false;
//抽lucky strike牌香烟的人喜欢喝橘子汁;
if (p.drink == Drink.橘子汁 && p.smoke != Smoke.lucky_strike || p.drink != Drink.橘子汁 && p.smoke == Smoke.lucky_strike)
return false;
//日本人抽parlianments牌的烟;
if (p.country == Country.日本 && p.smoke != Smoke.parlianments || p.country != Country.日本 && p.smoke == Smoke.parlianments)
return false;
//喜欢喝咖啡的人住在绿房子里;
if (p.housecolor == HouseColor.绿色 && p.drink != Drink.咖啡 || p.housecolor != HouseColor.绿色 && p.drink == Drink.咖啡)
return false;
return true;
}
private bool test()//相邻位置判断条件3/3
{
int progress = 5;
//绿房子在象牙白房子的右边;
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("--------------------------------------");
}
public void Anwser()
{
Console.WriteLine("答案 {0}:",count);
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 + " ";
}
}
}
最后,再贴一下用这个算法解决传说中的爱因斯坦测试题原题的情况吧(答案也是唯一的哦!!)。
速度比本题要稍快一些,貌似是因为它给了15个条件,比本题多一个