完美洗牌:隐藏在简单算法中的奇异分形

发布: (2025年12月11日 GMT+8 02:57)
6 min read
原文: Dev.to

Source: Dev.to

引言

我一直对能够产生复杂图案的基础算法着迷。其中一种算法是 完美洗牌(在扑克牌魔术师中称为 Faro 洗牌)。在对实体扑克牌进行实验后,我转向编程,以探索其异常特性并生成惊人的分形。

完美洗牌算法

完美洗牌以一种极其简单的方式重新排列数组中的元素:

  1. 将数组分成两等份。
  2. 将第一半的元素放入偶数位置,将第二半的元素放入奇数位置。

JavaScript 实现(内洗)

function shuffle(array) {
    const half = array.length / 2;
    const temparray = [];
    for (let i = 0; i

为简化起见,我们只关注内洗,并假设数组长度为偶数(奇数长度的数组会让最后一个元素保持不变)。

周期性

由于该算法是确定性的且可能状态的数量是有限的,反复洗牌最终会把数组恢复到原始顺序。所需的迭代次数始终 ≤ 数组长度。

数组长度返回所需迭代次数
1212
144

长度为 2n 的数组的迭代计数序列在 OEIS 中被编为 A002326。下面的 JavaScript 代码用于计算它:

function a002326(n) {
    let a = 1, m = 0;
    do {
        a = (a * 2) % (2 * n + 1);
        m++;
    } while (a > 1);
    return m;
}
for (let n = 1; n < 11; n++) {
    console.log(a002326(n));
}

逆转属性

对于某些长度,数组在特定的迭代次数后会出现顺序逆转。例如:12 元素的数组在第 6 次迭代时逆转。

可视化洗牌

状态空间图

  • X 轴:元素数量(除以 2)
  • Y 轴:返回起始状态所需的迭代次数

这些点呈现出混沌分布的特征。

二进制模式可视化

将数组的前半部分填充为 0(黑),后半部分填充为 1(白)。每一行代表数组的一个状态;行按连续迭代堆叠。示例:

  • 142–150 元素的数组
  • 288 元素(144 × 2)
  • 610 元素

仓库中的小脚本(shuffle1d.html)可以生成这些图案。输入半尺寸并点击 Draw

跟踪单个元素

元素在每次迭代后的位置信息可以直接计算:

x[n+1] = {
    x[n]/2 + y/2, if x[n] is even;
    (x[n] - 1)/2, if x[n] is odd;
}

其中 y 为数组长度。长度为 2–22 的绘图显示出不可预测的轨迹。将许多此类轨迹堆叠会产生类分形图像(原文中可点击查看)。

扩展到矩阵

简单矩阵技巧

  1. 在每次迭代时 反转 第二半部分元素的顺序。
  2. 翻转(交换)第二半部分的元素。

这些操作在对矩阵进行洗牌时非常有用。

行列洗牌

function shufflecr(array) {
    const half = array.length / 2;
    const temparray = [];

    for (let x = 0; x < half; x++) {
        temparray[x * 2] = [];
        temparray[x * 2 + 1] = [];
        for (let y = 0; y < half; y++) {
            temparray[x * 2][y * 2] = array[x + half][y + half];
            temparray[x * 2 + 1][y * 2] = array[x][y + half];
            temparray[x * 2][y * 2 + 1] = array[x + half][y];
            temparray[x * 2 + 1][y * 2 + 1] = array[x][y];
        }
    }
    return temparray;
}

该函数同时洗牌行和列。对 80 × 80 矩阵使用它会产生错综复杂的图案;再加入第二条对角线会产生莫尔条纹效果。更大的矩阵(例如 242 × 242)在第 27–35 次迭代时会展现出演化结构。

类环面行为

当矩阵被视为环面(边缘相连)时,边缘效应在仅几次迭代后即可显现。

从洗牌生成分形

Morse–Thue 序列

通过迭代扩展数组并与其反转副本进行洗牌,可得到 Morse–Thue 序列(OEIS A010060):

[1,0]
→ [1,0,0,1]
→ [0,1,1,0]
→ [0,1,1,0,1,0,0,1]
→ …

该二进制序列是经典的分形。

基于矩阵的分形

  1. 复制矩阵。
  2. 应用变换(旋转、翻转)。
  3. 将原矩阵与变换后的副本进行洗牌。

可能的变换(编号 0‑15):

  • 0:保持不变
  • 1‑7:旋转(0°、90°、180°、270° 等)
  • 8‑15:翻转(镜像)

反复使用类似 0, 0, 7, 7 的模式(其中动作 7 表示顺时针旋转 90°)会在若干迭代后产生独特的分形。若省略完美洗牌而使用相同的动作,则会得到另一种有趣的图案。

其它视觉技巧

  • 叠加 一份带有透明白像素的矩阵副本,并将其旋转 90°。
  • 在叠加之前对副本进行垂直旋转

这些技巧进一步丰富了使用完美洗牌能够实现的视觉复杂度。

所有代码片段均提供了语法高亮提示。OEIS 序列 A002326 与 A010060 的链接已保留供参考。

Back to Blog

相关文章

阅读更多 »

从算法到冒险

《From Algorithms to Adventures》的封面图片 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-...

数组和字符串操作问题

使用另一个数组反转数组 java class Main { public static void mainString args { int num = {10, 11, 12, 13, 14, 15, 16}; System.out.println'Original...