JavaScript模拟刘谦春晚魔术:基于约瑟夫环算法的扑克牌逻辑解析

作者:代码拾荒

时间:2026-11-01

浏览:409

本文利用JavaScript代码模拟春晚刘谦的扑克牌魔术,深入解析其背后的约瑟夫环数学原理。通过2的n次方及任意张数牌的推演,揭示牌序变换中的不变量规律,并展示完整代码实现魔术师与观众互动后的最终结果验证过程。

文章目录

何谓约瑟夫环问题?

约瑟夫环(Josephus problem)系经典数学难题,初由古罗马史家弗拉维奥·约瑟夫斯提出,其名于19世纪由德国数学家约瑟夫·乔瑟夫斯(Josef Stein)命名。

问题设定:n人(编号1至n)围成圆圈,从首人起报数,达特定数字(如k)者出局,继而下人重新报数,直至余一人。

核心在于确定最终存活者的编号。

借扑克牌阐释约瑟夫环问题

1、分析最简单情形

假设仅2张牌,编号分别为1和2。

先将1移至后,丢弃2。余下最初位于最上方的牌1。

2、稍复杂情形,牌数为2的n次方

例如有8张牌,编号依次为1、2、3、4、5、6、7、8。

首轮丢弃2、4、6、8,余1、3、5、7依序置后,退化为4张牌情形。

次轮丢弃3、7,余1、5依序置后,再退化为2张牌情形。

末轮丢弃5,余1,即最初位于最前的牌。

结论:若牌数为2^n,最终剩余者必为初始置于牌堆顶之牌。

3、考虑任意情形,牌数为2^n+m

如牌数11,等于8+3。将1置后,丢2,将3置后,丢4,将5置后,丢6。现余序列为7、8、9、10、11、1、3、5,此乃8张牌情形!最终必剩当前牌堆顶之7!

故只要知晓牌数,即可即时推导最终剩余之牌。所有魔法皆属数学!皆属算法!

见证奇迹时刻!魔术流程

  • 4张牌对折撕开得8张,叠放成ABCDABCD。注意,ABCD四数字完全等价。
  • 依名字字数,将顶牌置底,然此操作不改变循环序列相对位置。如2次,终成CDABCDAB;如3次,终成DABCDABC。无论何操作,第4张与第8张牌恒同。
  • 将顶3张插入中间任意位。此步至关重要!因操作后必现第1张与第8张牌相同!以名字两字为例,可表为BxxxxxxB(此处x为异于B之牌)。
  • 移去顶牌置旁,记为B。余序列为xxxxxxB,共7张牌。
  • 南方人/北方人/不确定,分别将顶1/2/3张牌插入中间,然不改变余7张牌为xxxxxxB之结果。
  • 男生移去1张,女生移去2张。即男生余6张,女生余5张。分别为xxxxxB和xxxxB。
  • 循环7次,将顶牌置底,男生与女生分别得xxxxBx和xxBxx。
  • 最后执行约瑟夫环过程!操作至余1张。当牌数为6时(男生),余者即第5张牌;当牌数为5时(女生),余者即第3张牌。Bingo!即第4步移去之牌!

以下是完整JavaScript代码实现:

// 定义一个函数,用于把牌堆顶n张牌移动到末尾
function moveCardBack(n, arr) {
    // 循环n次,把队列第一张牌放到队列末尾
    for (let i = 0; i < n; i++) {
        const moveCard = arr.shift();  // 弹出队头元素,即第一张牌
        arr.push(moveCard);            // 把原队头元素插入到序列末尾
    }
    return arr;
}

// 定义一个函数,用于把牌堆顶n张牌移动到中间的任意位置
function moveCardMiddleRandom(n, arr) {
    // 插入在arr中的的位置,随机生成一个idx
    // 这个位置必须是在n+1到arr.length-1之间
    const idx = Math.floor(Math.random() * (arr.length - n - 1)) + n + 1;
    // 执行插入操作
    const newArr = arr.slice(n, idx).concat(arr.slice(0, n)).concat(arr.slice(idx));
    return newArr;
}

// 步骤1:初始化8张牌,假设为"ABCDABCD"
let arr = ["A", "B", "C", "D", "A", "B", "C", "D"];
console.log("步骤1:拿出4张牌,对折撕成8张,按顺序叠放。");
console.log("此时序列为:" + arr.join('') + "\n---");

// 步骤2(无关步骤):名字长度随机选取,这里取2到5(其实任意整数都行)
const nameLen = Math.floor(Math.random() * 4) + 2;
// 把nameLen张牌移动到序列末尾
arr = moveCardBack(nameLen, arr);
console.log(`步骤2:随机选取名字长度为${nameLen},把第1张牌放到末尾,操作${nameLen}次。`);
console.log(`此时序列为:${arr.join('')}\n---`);

// 步骤3(关键步骤):把牌堆顶三张放到中间任意位置
arr = moveCardMiddleRandom(3, arr);
console.log(`步骤3:把牌堆顶3张放到中间的随机位置。`);
console.log(`此时序列为:${arr.join('')}\n---`);

// 步骤4(关键步骤):把最顶上的牌拿走
const restCard = arr.shift();  // 弹出队头元素
console.log(`步骤4:把最顶上的牌拿走,放在一边。`);
console.log(`拿走的牌为:${restCard}`);
console.log(`此时序列为:${arr.join('')}\n---`);

// 步骤5(无关步骤):根据南方人/北方人/不确定,把顶上的1/2/3张牌插入到中间任意位置
// 随机选择1、2、3中的任意一个数字
const moveNum = Math.floor(Math.random() * 3) + 1;
arr = moveCardMiddleRandom(moveNum, arr);
console.log(`步骤5:我${moveNum === 1 ? '是南方人' : moveNum === 2 ? '是北方人' : '不确定自己是哪里人'},\
把${moveNum}张牌插入到中间的随机位置。`);
console.log(`此时序列为:${arr.join('')}\n---`);

// 步骤6(关键步骤):根据性别男或女,移除牌堆顶的1或2张牌
const maleNum = Math.floor(Math.random() * 2) + 1;  // 随机选择1或2
for (let i = 0; i < maleNum; i++) {  // 循环maleNum次,移除牌堆顶的牌
    arr.shift();
}
console.log(`步骤6:我是${maleNum === 1 ? '男' : '女'}生,移除牌堆顶的${maleNum}张牌。`);
console.log(`此时序列为:${arr.join('')}\n---`);

// 步骤7(关键步骤):把顶部的牌移动到末尾,执行7次
arr = moveCardBack(7, arr);
console.log(`步骤7:把顶部的牌移动到末尾,执行7次`);
console.log(`此时序列为:${arr.join('')}\n---`);

// 步骤8(关键步骤):执行约瑟夫环过程。把牌堆顶一张牌放到末尾,再移除一张牌,直到只剩下一张牌。
console.log(`步骤8:把牌堆顶一张牌放到末尾,再移除一张牌,直到只剩下一张牌。`);
while (arr.length > 1) {
    const luck = arr.shift();  // 好运留下来
    arr.push(luck);
    console.log(`好运留下来:${luck}\t\t此时序列为:${arr.join('')}`);
    const sadness = arr.shift();  // 烦恼都丢掉
    console.log(`烦恼都丢掉:${sadness}\t\t此时序列为:${arr.join('')}`);
}
console.log(`---\n最终结果:剩下的牌为${arr[0]},步骤4中留下来的牌也是${restCard}`);

此代码实现昨晚春晚上刘谦第二个魔术表演过程,并提供详细解释。享受魔术魅力吧!

何谓约瑟夫环问题?

何谓约瑟夫环问题?

以上为JavaScript代码实现春晚刘谦魔术模拟程序之详细内容,欲知更多关于JavaScript刘谦魔术模拟程序资料,请关注本站其他相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章