以伪随机顺序访问所有网格单元的算法,该算法在任何阶段都具有保证的一致性

Algorithm for visiting all grid cells in pseudo-random order that has a guaranteed uniformity at any stage

上下文:

我有一个水力侵蚀算法,需要接收一组液滴起始位置。我也已经有了一个模式复制算法,所以我只需要一个 good 模式来复制。

要求:

我需要一种算法,它以一组格式 (x,y) 或 [index] 生成一组 n^2 条目,描述 nxn 网格中的单元格(其中 n = 2^i,其中 i 是任意的正整数)。

备注 我 was/am 试图通过递归构建的类似分形的模式找到解决方案,但在撰写本文时,我的解决方案是查找 table 棋盘模式(黑色单元格列表 + 白色单元格列表单元格)(这很糟糕,但产生的工件比有序列表少)

C、C++、C#、Java 实现(如果有)是首选

您可以使用 linear congruential generator to create an even distribution across your n×n space. For example, if you have a 64×64 grid, using a stride of 47 will create the pattern on the left below. (Run on jsbin) 从亮到暗访问单元格。

该模式没有聚集,但相当均匀。它使用简单的行范围转换,其中

k = (k + 47) mod (n * n)
x = k mod n
y = k div n

您可以通过使 k 成为 space 填充曲线的索引来添加一点随机性,例如 Hilbert curve. This will yield the pattern on the right. (Run on jsbin)

您可以在 jsbin 链接中查看代码。

我已经自己解决了这个问题,只是分享我的解决方案:

这是我对 0 到 3 之间的 i 的输出:

power: 0
ordering:
0
matrix visit order:
0

power: 1
ordering:
0 3 2 1
matrix visit order:
0       3
2       1

power: 2
ordering:
0 10 8 2 5 15 13 7 4 14 12 6 1 11 9 3
matrix visit order:
0       12      3       15
8       4       11      7
2       14      1       13
10      6       9       5

power: 3
ordering:
0 36 32 4 18 54 50 22 16 52 48 20 2 38 34 6
9 45 41 13 27 63 59 31 25 61 57 29 11 47 43 15
8 44 40 12 26 62 58 30 24 60 56 28 10 46 42 14
1 37 33 5 19 55 51 23 17 53 49 21 3 39 35 7
matrix visit order:
0       48      12      60      3       51      15      63
32      16      44      28      35      19      47      31
8       56      4       52      11      59      7       55
40      24      36      20      43      27      39      23
2       50      14      62      1       49      13      61
34      18      46      30      33      17      45      29
10      58      6       54      9       57      5       53
42      26      38      22      41      25      37      21

代码:

public static int[] GetPattern(int power, int maxReturnSize = int.MaxValue)
{
    int sideLength = 1 << power;
    int cellsNumber = sideLength * sideLength;
    int[] ret = new int[cellsNumber];
    for ( int i = 0 ; i < cellsNumber && i < maxReturnSize ; i++ ) {
        // this loop's body can be used for per-request computation
        int x = 0;
        int y = 0;
        for ( int p = power - 1 ; p >= 0 ; p-- ) {
            int temp = (i >> (p * 2)) % 4; //2 bits of the index starting from the begining
            int a = temp % 2; // the first bit
            int b = temp >> 1; // the second bit
            x += a << power - 1 - p;
            y += (a ^ b) << power - 1 - p;// ^ is XOR
            // 00=>(0,0), 01 =>(1,1) 10 =>(0,1) 11 =>(1,0) scaled to 2^p where 0<=p 
        }
        //to index
        int index = y * sideLength + x;
        ret[i] = index;
    }
    return ret;
}

我确实承认,在某些地方,值被调换了,但这并不重要,因为它是如何工作的。

在做了一些优化之后,我想出了这个循环体:

int x = 0;
int y = 0;

for ( int p = 0 ; p < power ; p++ ) {
    int temp = ( i >> ( p * 2 ) ) & 3;
    int a = temp & 1;
    int b = temp >> 1;

    x = ( x << 1 ) | a;
    y = ( y << 1 ) | ( a ^ b );
}
int index = y * sideLength + x;

(代码假设c#优化器、IL2CPP、CPP编译器会优化变量temp、a、b out)