假设我有一个大小为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
值,则可能需要永远“猜测”哪个指标是一个我感兴趣的。我应该如何使这个算法最佳?
答案 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。