JavaScript代碼實(shí)現(xiàn)春晚劉謙魔術(shù)的模擬程序
什么是約瑟夫環(huán)問(wèn)題?
約瑟夫環(huán)(Josephus problem)是一個(gè)經(jīng)典的數(shù)學(xué)問(wèn)題,最早由古羅馬歷史學(xué)家弗拉維奧·約瑟夫斯提出,但它的名字是在19世紀(jì)由德國(guó)數(shù)學(xué)家約瑟夫·喬瑟夫斯(Josef Stein)命名的。
問(wèn)題的描述是這樣的:假設(shè)有n個(gè)人(編號(hào)從1到n)站成一個(gè)圓圈,從第一個(gè)人開(kāi)始報(bào)數(shù),報(bào)到某個(gè)數(shù)字(例如k)的人就被殺死,然后從下一個(gè)人開(kāi)始重新報(bào)數(shù)并繼續(xù)這個(gè)過(guò)程,直到只剩下一個(gè)人留下來(lái)。
問(wèn)題的關(guān)鍵是找出存活下來(lái)的那個(gè)人的編號(hào)。
結(jié)合撲克牌解釋約瑟夫環(huán)問(wèn)題
1、考慮最簡(jiǎn)單的情況
假設(shè)有2張牌,編號(hào)分別是1和2。
首先將1放到后面,扔掉2。剩下的就是最開(kāi)始放在最上邊的那張1。
2、稍微復(fù)雜一點(diǎn)的情況,牌的張數(shù)是2的n次方
比如有8張牌,編號(hào)分別是1、2、3、4、5、6、7、8。
第一輪會(huì)把2、4、6、8扔掉,剩下1、3、5、7按順序放在后面,又退化成了4張牌的情況。
第二輪會(huì)把3、7扔掉,剩下1、5按順序放在后面,又退化成了2張牌的情況。
第三輪把5扔掉,剩下1,就是最初在最前面的那張。
結(jié)論:如果牌的張數(shù)是2^n,最后剩下的一定是最開(kāi)始放在牌堆頂?shù)哪菑垺?/p>
3、考慮任意的情況,牌的張數(shù)是2^n+m
比如牌的張數(shù)是11,等于8+3。把1放到后面,把2扔掉,把3放到后面,把4扔掉,把5放到后面,把6扔掉,現(xiàn)在剩下的編號(hào)序列是7、8、9、10、11、1、3、5,這又是8張牌的情況!最后一定剩下的是現(xiàn)在牌堆頂?shù)?!
因此,只要提前知道牌的張數(shù),就一定能馬上推導(dǎo)出最終是剩下哪一張牌。一切的魔法都是數(shù)學(xué)??!都是算法!!
見(jiàn)證奇跡的時(shí)刻!魔術(shù)的流程
- 4張牌對(duì)折后撕開(kāi),就是8張,疊放在一起就是ABCDABCD。注意,ABCD四個(gè)數(shù)字是完全等價(jià)的。
- 根據(jù)名字字?jǐn)?shù),把頂上的牌放到下面,但怎么放都不會(huì)改變循環(huán)序列的相對(duì)位置。譬如2次,最后變成CDABCDAB;譬如3次,最后換成DABCDABC。但無(wú)論怎么操作,第4張和第8張牌都是一樣的。
- 把頂上3張插到中間任意位置。這一步非常重要!因?yàn)椴僮魍曛蟊厝怀霈F(xiàn)第1張和第8張牌是一樣的!以名字兩個(gè)字為例,可以寫(xiě)成BxxxxxxB(這里的x是其他和B不同的牌)。
- 拿掉頂上的牌放到一邊,記為B。剩下的序列是xxxxxxB,一共7張牌。
- 南方人/北方人/不確定,分別拿頂上的1/2/3張牌插到中間,但是不會(huì)改變剩下7張牌是xxxxxxB的結(jié)果。
- 男生拿掉1張,女生拿掉2張。也就是男生剩下6張,女生剩下5張。分別是xxxxxB和xxxxB。
- 循環(huán)7次,把最頂上的放到最底下,男生和女生分別會(huì)是xxxxBx和xxBxx。
- 最后執(zhí)行約瑟夫環(huán)過(guò)程!操作到最后只剩下1張。當(dāng)牌數(shù)為6時(shí)(男生),剩下的就是第5張牌;當(dāng)牌數(shù)為5時(shí)(女生),剩下的就是第3張牌。Bingo!就是第4步拿掉的那張牌!
下面是完整的 JavaScript 代碼實(shí)現(xiàn):
// 定義一個(gè)函數(shù),用于把牌堆頂n張牌移動(dòng)到末尾 function moveCardBack(n, arr) { // 循環(huán)n次,把隊(duì)列第一張牌放到隊(duì)列末尾 for (let i = 0; i < n; i++) { const moveCard = arr.shift(); // 彈出隊(duì)頭元素,即第一張牌 arr.push(moveCard); // 把原隊(duì)頭元素插入到序列末尾 } return arr; } // 定義一個(gè)函數(shù),用于把牌堆頂n張牌移動(dòng)到中間的任意位置 function moveCardMiddleRandom(n, arr) { // 插入在arr中的的位置,隨機(jī)生成一個(gè)idx // 這個(gè)位置必須是在n+1到arr.length-1之間 const idx = Math.floor(Math.random() * (arr.length - n - 1)) + n + 1; // 執(zhí)行插入操作 const newArr = arr.slice(n, idx).concat(arr.slice(0, n)).concat(arr.slice(idx)); return newArr; } // 步驟1:初始化8張牌,假設(shè)為"ABCDABCD" let arr = ["A", "B", "C", "D", "A", "B", "C", "D"]; console.log("步驟1:拿出4張牌,對(duì)折撕成8張,按順序疊放。"); console.log("此時(shí)序列為:" + arr.join('') + "\n---"); // 步驟2(無(wú)關(guān)步驟):名字長(zhǎng)度隨機(jī)選取,這里取2到5(其實(shí)任意整數(shù)都行) const nameLen = Math.floor(Math.random() * 4) + 2; // 把nameLen張牌移動(dòng)到序列末尾 arr = moveCardBack(nameLen, arr); console.log(`步驟2:隨機(jī)選取名字長(zhǎng)度為${nameLen},把第1張牌放到末尾,操作${nameLen}次。`); console.log(`此時(shí)序列為:${arr.join('')}\n---`); // 步驟3(關(guān)鍵步驟):把牌堆頂三張放到中間任意位置 arr = moveCardMiddleRandom(3, arr); console.log(`步驟3:把牌堆頂3張放到中間的隨機(jī)位置。`); console.log(`此時(shí)序列為:${arr.join('')}\n---`); // 步驟4(關(guān)鍵步驟):把最頂上的牌拿走 const restCard = arr.shift(); // 彈出隊(duì)頭元素 console.log(`步驟4:把最頂上的牌拿走,放在一邊。`); console.log(`拿走的牌為:${restCard}`); console.log(`此時(shí)序列為:${arr.join('')}\n---`); // 步驟5(無(wú)關(guān)步驟):根據(jù)南方人/北方人/不確定,把頂上的1/2/3張牌插入到中間任意位置 // 隨機(jī)選擇1、2、3中的任意一個(gè)數(shù)字 const moveNum = Math.floor(Math.random() * 3) + 1; arr = moveCardMiddleRandom(moveNum, arr); console.log(`步驟5:我${moveNum === 1 ? '是南方人' : moveNum === 2 ? '是北方人' : '不確定自己是哪里人'},\ 把${moveNum}張牌插入到中間的隨機(jī)位置。`); console.log(`此時(shí)序列為:${arr.join('')}\n---`); // 步驟6(關(guān)鍵步驟):根據(jù)性別男或女,移除牌堆頂?shù)?或2張牌 const maleNum = Math.floor(Math.random() * 2) + 1; // 隨機(jī)選擇1或2 for (let i = 0; i < maleNum; i++) { // 循環(huán)maleNum次,移除牌堆頂?shù)呐? arr.shift(); } console.log(`步驟6:我是${maleNum === 1 ? '男' : '女'}生,移除牌堆頂?shù)?{maleNum}張牌。`); console.log(`此時(shí)序列為:${arr.join('')}\n---`); // 步驟7(關(guān)鍵步驟):把頂部的牌移動(dòng)到末尾,執(zhí)行7次 arr = moveCardBack(7, arr); console.log(`步驟7:把頂部的牌移動(dòng)到末尾,執(zhí)行7次`); console.log(`此時(shí)序列為:${arr.join('')}\n---`); // 步驟8(關(guān)鍵步驟):執(zhí)行約瑟夫環(huán)過(guò)程。把牌堆頂一張牌放到末尾,再移除一張牌,直到只剩下一張牌。 console.log(`步驟8:把牌堆頂一張牌放到末尾,再移除一張牌,直到只剩下一張牌。`); while (arr.length > 1) { const luck = arr.shift(); // 好運(yùn)留下來(lái) arr.push(luck); console.log(`好運(yùn)留下來(lái):${luck}\t\t此時(shí)序列為:${arr.join('')}`); const sadness = arr.shift(); // 煩惱都丟掉 console.log(`煩惱都丟掉:${sadness}\t\t此時(shí)序列為:${arr.join('')}`); } console.log(`---\n最終結(jié)果:剩下的牌為${arr[0]},步驟4中留下來(lái)的牌也是${restCard}`);
這段代碼實(shí)現(xiàn)了昨晚春晚上劉謙的第二個(gè)魔術(shù)表演的過(guò)程,并提供了詳細(xì)的解釋。享受魔術(shù)的魅力吧!
以上就是JavaScript代碼實(shí)現(xiàn)春晚劉謙魔術(shù)的模擬程序的詳細(xì)內(nèi)容,更多關(guān)于JavaScript劉謙魔術(shù)模擬程序的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript子類(lèi)用Object.getPrototypeOf去調(diào)用父類(lèi)方法解析
這篇文章主要介紹了JavaScript子類(lèi)用Object.getPrototypeOf去調(diào)用父類(lèi)方法。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12JavaScript子窗口調(diào)用父窗口變量和函數(shù)的方法
這篇文章主要介紹了JavaScript子窗口調(diào)用父窗口變量和函數(shù)的方法,涉及JavaScript窗口調(diào)用的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10JavaScript中內(nèi)置函數(shù)Map()的使用
Map()是JavaScript中內(nèi)置的一種數(shù)據(jù)結(jié)構(gòu),它允許您將鍵值對(duì)映射到任意類(lèi)型的值,主要介紹了JavaScript中內(nèi)置函數(shù)Map()的使用,感興趣的可以了解一下2023-05-05Typescript定義多個(gè)接口類(lèi)型聲明的方式小結(jié)
這篇文章主要介紹了Typescript定義多個(gè)接口類(lèi)型聲明的方式小結(jié),在 TypeScript 中,您可以使用交叉類(lèi)型(&)或聯(lián)合類(lèi)型(|)來(lái)組合多個(gè)接口,從而實(shí)現(xiàn)多個(gè)接口類(lèi)型的混合,文中通過(guò)代碼講解的非常詳細(xì),需要的朋友可以參考下2025-01-01JavaScript 正則應(yīng)用詳解【模式、欲查、反向引用等】
這篇文章主要介紹了JavaScript 正則應(yīng)用,結(jié)合實(shí)例形式詳細(xì)分析了JavaScript 正則表達(dá)式模式、欲查、反向引用等相關(guān)概念、原理與操作注意事項(xiàng),需要的朋友可以參考下2020-05-05Bootstrap實(shí)現(xiàn)提示框和彈出框效果
這篇文章主要為大家詳細(xì)介紹了Bootstrap實(shí)現(xiàn)彈出框和提示框效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01