随机指出一个布尔网格

时间:2016-08-27 08:07:51

标签: algorithm random

假设我有一个大小为boolean的正方形grid N(二维数组)。有些值为true,有些值为false<true values> / <false values>比率未指定)。我想随机选择一个indice (x, y),以便grid[x][y]true。如果我想要一个节省时间的解决方案,我会做这样的事情(Python):

x, y = random.choice([(x, y) for x in range(N) for y in range(N) if grid[x][y]])

但这是O(N^2),这对于一个tic-tac-toe游戏实现来说已经绰绰有余了,但我猜测它会为大型N带来更多的内存消耗。

如果我想要一些不消耗内存的东西,我会这样做:

x, y = 0, 0
t = N - 1
while True:
    x = random.randint(0, t)
    y = random.randint(0, t)
    if grid[x][y]:
        break

但问题是,如果我的网格大小为10^4,并且其中只有一个或两个true值,则可能需要永远“猜测”哪个指标是一个我感兴趣的。我应该如何使这个算法最佳?

2 个答案:

答案 0 :(得分:1)

您可以使用以对数深度实现为二叉树的字典。这需要O(N^2)空间,并允许您在O(log(N^2)) = O(logN)时间内搜索/删除。例如,您可以使用Red-Black Tree

查找随机值的算法可能是:

t = tree.root
if (t == null)
    throw Exception("No more values");
// logarithmic serach
while t.left != null or t.right != null
     pick a random value k from range(0, 1, 2)
     if (k == 0)
         break;
     if (k == 1)
         if (t.left == null)
             break
         t = t.left
     if (k == 2)
         if (t.right == null)
             break
         t = t.right

result = t.value
// logarithmic delete
tree.delete(t)
return result

当然,您可以将(i, j)索引表示为i * N + j

如果没有额外的记忆,您无法跟踪细胞状态的变化。在我看来,你不能比O(N^2)更好(迭代数组)。

答案 1 :(得分:1)

如果网格是静态的或变化不大,或者您有时间进行一些预处理,则可以存储一个数组,该数组包含每行的真值数,真值的总数以及非零行(如果网格发生变化,所有这些行都可以保持更新):

grid        per row

0 1 0 0 1 0    2
0 0 0 0 0 0    0
0 0 1 0 0 0    1
0 0 0 0 1 0    1
0 0 0 0 0 0    0
1 0 1 1 1 0    4
       total = 8

non-zero rows: [0, 2, 3, 5]

要选择随机索引,请选择一个随机值r,直到真值的总数,在每个非零行的真值数量上迭代数组,将它们相加,直到知道r-哪一行为止th值是,然后遍历该行以找到第r个真值的位置。

(您可以先选择一个非空行,然后从该行中选择一个真值,但这会产生非均匀概率。)

对于N×N大小的网格,预处理将花费N×N时间和2×N空间,但最坏情况查找时间将为N.实际上,使用下面的JavaScript代码示例,预处理和查找时间(以ms为单位)的顺序为:

  grid size      pre-processing    look-up  
10000 x 10000        5000            2.2  
 1000 x  1000          50            0.22  
  100 x   100           0.5          0.022  

正如您所看到的,查找速度比大型网格的预处理快2000多倍,因此如果您需要在相同(或稍微改变)的网格上随机选择多个位置,则预处理会产生很有道理。

function random2D(grid) {
    this.grid = grid;
    this.num = this.grid.map(function(elem) {         // number of true values per row
        return elem.reduce(function(sum, val) {
            return sum + (val ? 1 : 0);
        }, 0);
    });
    this.total = this.num.reduce(function(sum, val) { // total number of true values
        return sum + val;
    }, 0);

    this.update = function(row, col, val) {           // change value in grid
        var prev = this.grid[row][col];
        this.grid[row][col] = val;
        if (prev ^ val) {
            this.num[row] += val ? 1 : -1;
            this.total += val ? 1 : -1;
        }
    }

    this.select = function() {                        // select random index
        var row = 0, col = 0;
        var rnd = Math.floor(Math.random() * this.total) + 1;
        while (rnd > this.num[row]) {                 // find row
            rnd -= this.num[row++];
        }
        while (rnd) {                                 // find column
            if (this.grid[row][col]) --rnd;
            if (rnd) ++col;
        }
        return {x: col, y: row};
    }
}

var grid = [], size = 1000, prob = 0.01;              // generate test data
for (var i = 0; i < size; i++) {
    grid[i] = [];
    for (var j = 0; j < size; j++) {
        grid[i][j] = Math.random() < prob;
    }
}
var rnd = new random2D(grid);                         // pre-process grid
document.write(JSON.stringify(rnd.select()));         // get random index

保留包含至少一个true值的行的列表仅对非常稀疏填充的网格有意义,其中许多行不包含真值,因此我没有在代码示例中实现它。如果确实实现了它,非常稀疏阵列的查找时间将减少到不到1μs。