就當是閒得蛋疼吧,此次改進之後算法,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個條件,比本題多一個