2D循环搜索模式

时间:2017-08-06 08:10:39

标签: algorithm math 2d

我需要一种算法来为我提供距离最近的单元格(按距离的顺序)到2D网格中另一个单元格的坐标。它用于搜索算法,然后检查这些坐标是否适合各种事物。无论如何,到目前为止我想出了这个:



function testy(cx, cy, idx) {
	var radius = Math.floor(Math.sqrt(idx / Math.PI));
	var segment = Math.round(idx - (radius * Math.PI));
	var angle = segment / radius;
	var x = Math.round(cx + radius * Math.cos(angle));
	var y = Math.round(cy + radius * Math.sin(angle));
	return [x, y];
}

addEventListener("load", function() {
	var canv = document.createElement("canvas");
	document.body.appendChild(canv);
	canv.width = 800;
	canv.height = 600;
	var ctx = canv.getContext("2d");
	
	var scale = 5;
	var idx = 0;
	var idx_end = 10000;
	
	var func = function() {
		var xy = testy(0,0,idx++);
		var x = xy[0] * scale + canv.width / 2;
		var y = xy[1] * scale + canv.height / 2;
		ctx.rect(x, y, scale, scale);
		ctx.fill();
		
		if (idx < idx_end) setTimeout(func, 0);
	}
	
	func();
	
});
&#13;
&#13;
&#13;

但正如你所知,它有点废话,因为它会跳过一些细胞。我在那里做了一些假设:

  1. 一定半径的圆的圆周对应于该圆的路径上的单元的数量。虽然因为半径中的实际单元格数应该低于导致重复的圆周(少量是可以的)但是不是排除(不是正确的),我并不认为问题太大了。

  2. 指定的第n个索引的圆的半径将略大于Math.floor(Math.sqrt(idx / Math.PI)),因为每次增加1到半径对应于2 * Math.PI被添加到圆的圆周。同样,应该导致轻微的重复但不排除。

  3. 除此之外我不知道它有什么问题,我在数学方面的失败比这更复杂,所以可能与此有关。

    也许还有其他类似的算法呢?一个不跳过细胞的人?语言并不重要,我使用js来制作原型,但它可以是任何东西。

1 个答案:

答案 0 :(得分:1)

不要考虑整个圈子,而是考虑一个象限。稍后将其调整为完整的圆圈应该相当容易。为方便起见,使用(0,0)作为圆的中心。因此,您希望列出 x,y ≥0的网格单元格,其顺序为非递减 x ²+ y ²。

一个有用的数据结构是优先级队列。它可用于跟踪每个 x 值的下一个 y 值,并且您可以提取最小 x ²+ <的值em> y ²很容易。

q = empty priority queue, for easy access to element with minimal x²+y²
Insert (0,0) into queue
while queue is not empty:
  remove minimal element from queue and call it (x,y)
  insert (x,y+1) into queue unless y+1 is off canvas
  if y = 0:
    insert (x+1,0) into queue unless x+1 is off canvas
  do whatever you want to do with (x,y)

因此,对于大小为 n 的画布,这将枚举所有 n ²点,但优先级队列将仅包含 n 元素最。整个循环在 O n ²log( n ))中运行。如果你因为找到了你想要的东西而中止循环,那么它会变得更便宜,而不是简单地对所有点进行排序。另一个好处是您可以独占使用整数运算,因此数字错误不会成为问题。一个缺点是JavaScript没有开箱即用的优先级队列,但我确信你可以找到一个可以重用的实现,例如: tiniqueue

进行完整循环时,除非 x = 0,否则生成( - x y ),同样适用于( x , - y )和( - x , - y )。你可以通过仅使循环超过圆的,来利用对称性,即如果 x x y +1)除非 x = ÿ。对于许多用例,性能差异应该是微不足道的。

"use strict";

function distCompare(a, b) {
  const a2 = a.x*a.x + a.y*a.y;
  const b2 = b.x*b.x + b.y*b.y;
  return a2 < b2 ? -1 : a2 > b2 ? 1 : 0;
}

// Yields points in the range -w <= x <= w and -h <= y <= h
function* aroundOrigin(w,h) {
  const q = TinyQueue([{x:0, y:0}], distCompare);
  while (q.length) {
    const p = q.pop();
    yield p;
    if (p.x) yield {x:-p.x, y:p.y};
    if (p.y) yield {x:p.x, y:-p.y};
    if (p.x && p.y) yield {x:-p.x, y:-p.y};
    if (p.y < h) q.push({x:p.x, y:p.y+1});
    if (p.y == 0 && p.x < w) q.push({x:p.x + 1, y:0});
  }
}

// Yields points around (cx,cy) in range 0 <= x < w and 0 <= y < h
function* withOffset(cx, cy, w, h) {
  const delegate = aroundOrigin(
    Math.max(cx, w - cx - 1), Math.max(cy, h - cy - 1));
  for(let p of delegate) {
    p = {x: p.x + cx, y: p.y + cy};
    if (p.x >= 0 && p.x < w && p.y >= 0 && p.y < h) yield p;
  }
}

addEventListener("load", function() {
	const canv = document.createElement("canvas");
	document.body.appendChild(canv);
  const cw = 800, ch = 600;
	canv.width = cw;
	canv.height = ch;
	const ctx = canv.getContext("2d");
	
	const scale = 5;
  const w = Math.ceil(cw / scale);
  const h = Math.ceil(ch / scale);
  const cx = w >> 1, cy = h >> 1;
  const pointgen = withOffset(cx, cy, w, h);
  let cntr = 0;

	var func = function() {
		const {value, done} = pointgen.next();
    if (done) return;
    if (cntr++ % 16 === 0) {
      // lighten older parts so that recent activity is more visible
      ctx.fillStyle = "rgba(255,255,255,0.01)";
  		ctx.fillRect(0, 0, cw, ch);
	    ctx.fillStyle = "rgb(0,0,0)";
    }
		ctx.fillRect(value.x * scale, value.y*scale, scale, scale);
    setTimeout(func, 0);
	}
	
	func();
	
});
<script type="text/javascript">module={};</script>
<script src="https://cdn.rawgit.com/mourner/tinyqueue/54dc3eb1/index.js"></script>