演算法系列之七 愛因斯坦的思考題(下)

2021-08-26 11:46:56 字數 3721 閱讀 1542

checkgrouprelation()函式需要根據當前組group的位置進行適當的處理,如果當前組是第乙個組或最後乙個組,則group的相鄰組只有乙個,就是最靠近group的組,其它情況下group的相鄰組都是兩個。checkgrouprelation()函式的實現如下:

162bool checkgrouprelation(group *groups,

int groupidx, item_type type,

int value)

163170}

171else

if(groupidx ==

(groups_count -1))

172177}

178else

179185}

186187

return

true;

188}

最後是列舉演算法部分的說明。前面系列文章中多次使用窮舉法解決問題,但都是一維線性組合的列舉,本題則有些特殊,因為需要對不同型別的元素分別用窮舉法進行列舉,因此不是簡單的線性組合。本文演算法採用的列舉方法是對不同型別的元素分別用窮舉法進行列舉,然後再進行組合,這個組合不是線性關係的組合,而是類似階乘的幾何關係的組合。具體思路就是按照group中的元素順序,首先對房子根據顏色組合進行窮舉,每得到一組房子顏色組合後,就在此基礎上對住在房子裡的人的國籍進行窮舉,在房子顏色和國籍的組合結果基礎上,在對飲料型別進行窮舉,依次類推,直到窮舉完最後一種型別得到完整的窮舉組合。

現在來具體推算一下窮舉的過程,首先是對房子顏色進行窮舉。因為是5種顏色的不重複組合,因此應該有5!= 120個顏色組合結果,但是根據線索4「綠房子緊挨著白房子,在白房子的左邊」,相當於綠房子和白房子有穩定的繫結關係,實際就只有4!= 24個顏色組合結果。接下來對24個房子顏色組合結果中的每乙個結果再進行住戶國籍的窮舉,理論上國籍也有5!= 120個結果,但是根據線索9「挪威人住在第乙個房子裡面」,相當於固定第乙個房子住得人始終是挪威人,因此就只有4!= 24個國籍組合結果。窮舉完房子顏色和國籍後就已經有24 x 24 = 576個組合結果了,接下來需要對這576個組合結果中的每乙個結果再進行飲料型別的窮舉,理論上飲料型別也有5!= 120個結果,但是根據線索8「住在中間那個房子裡的人喝牛奶」,相當於固定了乙個飲料型別,因此也只有4!= 24個飲料組合型別。窮舉完飲料型別後就得到了576 x 24 = 13824個組合結果,接下來對13824個組合結果中的每乙個結果再進行寵物種類的窮舉,這一步沒有線索可用,共有5!= 120個結果。窮舉完寵物種類後就得到了13824 x 120 = 1658880個組合結果,最後對1658880個組合結果中的每乙個結果再進行香菸品牌的窮舉,這一步依然沒有線索可用,共有5!= 120個結果。窮舉完香菸品牌後就得到了全部組合共1658880 x 120 = 199065600個結果,剩下的事情就是對這將近2億個結果進行判斷。

以下就是對房子顏色進行窮舉的演算法實現:

369/* 遍歷房子顏色*/

370void enumhousecolors(group *groups,

int groupidx)

371377

378for(

int i = color_blue; i <= color_yellow; i++)

379387

388 enumhousecolors(groups, groupidx +1);

393}

394}

395}

enumhousecolors()函式每得到乙個房子顏色的窮舉結果,就呼叫arrangehousenations()函式進行進一步處理。arrangehousenations() 函式做的事情就是繼續列舉住在每個房子裡的人的國籍,但是在這之前,先要根據已知條件進行預處理。比如已知規則(9)的描述:挪威人住在第乙個房子裡,就可以將group[0]的國籍鎖定為nation_norway,從而減少一層遍歷。

361void arrangehousenations(group *groups)

362實際上,真正的遍歷國籍過程是在enumhousenations()函式中完成。enumhousenations()函式每得到乙個完整的房子與國籍關係,就呼叫arrangepeopledrinks()函式進一步遍歷每個人喝的飲料型別。

341/*遍歷國家*/

342void enumhousenations(group *groups,

int groupidx)

343349

350for(

int i = nation_norway; i <= nation_germany; i++)

351358}

359}

360在每一組房子顏色和(住房客)國籍的對應關係上(根據前面的分析,將會有24 x 24 = 576組對應關係),arrangepeopledrinks()函式負責進一步排定它們和飲料型別的關係。arrangepeopledrinks()函式直接呼叫enumpeopledrinks()函式進行飲料型別的列舉,規則(8):「住在中間那個房子裡的人和牛奶」直接體現在enumpeopledrinks()函式中:

313void enumpeopledrinks(group *groups,

int groupidx)

314322

return;

323}

324325

for(

int i = drink_tea; i <= drink_milk; i++)

326332}

333}

在確定完飲料型別後(根據前面的分析,將會得到576 x 24 = 13824組結果),就要進一步遍歷每個人養的寵物(別忘了,魚也算寵物)。題目沒有對寵物提供更多的線索,因此arrangepeoplepet()函式就是簡單的遍歷全部寵物組合,得到120種人和寵物的組合:

288void enumpeoplepats(group *groups,

int groupidx)

289295

296for(

int i = pet_horse; i <= pet_dog; i++)

297304}

305}

人和寵物的關係也遍歷完成以後(根據前面的分析,將會得到13824 x 120 = 1658880組結果),就要最後確定每個人抽的香菸型別。關於人和香菸的關係,題目中也沒有給出更多的線索,因此arrangepeoplecigert()函式僅僅是呼叫enumpeoplecigerts()函式完成120種人和香菸的組合:

264void enumpeoplecigerts(group *groups,

int groupidx)

265271

272for(

int i = cigaret_blends; i <= cigaret_bluemaster; i++)

273280}

281}

每當人和香菸的關係也遍歷完成後,就呼叫dogroupsfinalcheck()對完整的組合進行檢查,檢查主要是基於bind和relation兩種型別的線索進行檢查(另一種型別的線索已經在遍歷的過程中體現)。根據本文前面給出的bind和relation兩種型別的數學模型,dogroupsfinalcheck()函式的實現非常簡單,就是逐個呼叫前面給出的checkgrouprelation()函式和checkgroupbind()函式對兩類關係逐個對比檢查,此處就不再列出**。

圖(3)演示了在每一輪窮舉過程中正確結果組合出來的過程,在我的intel p8600 cpu上,使用單核完成全部列舉和判斷共耗時26秒,得到符合全部線索的答案只有乙個,就是本文開始給出的示例。

圖(3)每一輪窮舉過程中正確結果組合出來的過程

演算法系列之七 愛因斯坦的思考題

據說有五個不同顏色的房間排成一排,每個房間裡分別住著乙個不同國籍的人,每個人都喝一種特定品牌的飲料,抽一種特定品牌的煙,養一種寵物,沒有任意兩個人抽相同品牌的香菸,或喝相同品牌的飲料,或養相同的寵物。問題是誰在養魚作為寵物?為了尋找答案,愛因斯坦給出了以下 15 條線索。英國人住在紅色的房子裡 瑞典...

演算法系列之七 愛因斯坦的思考題(下)

checkgrouprelation 函式需要根據當前組group的位置進行適當的處理,如果當前組是第乙個組或最後乙個組,則group的相鄰組只有乙個,就是最靠近group的組,其它情況下group的相鄰組都是兩個。checkgrouprelation 函式的實現如下 162boolcheckgro...

演算法系列之七 愛因斯坦的思考題(下)

checkgrouprelation 函式需要根據當前組group的位置進行適當的處理,如果當前組是第乙個組或最後乙個組,則group的相鄰組只有乙個,就是最靠近group的組,其它情況下group的相鄰組都是兩個。checkgrouprelation 函式的實現如下 162bool checkgr...