【问题标题】:Finding the furthest point in a grid when compared to other points与其他点相比,查找网格中最远的点
【发布时间】:2015-11-04 07:19:21
【问题描述】:

我有一个可变大小的矩形网格,但平均为 500x500,其中有少量 x,y 点(小于 5)。我需要找到一个算法,它返回一个距离其他点最远的 x,y 对。

上下文:应用的屏幕(网格)和一组 x,y 点(敌人)。玩家死了,我需要一个算法让他们在远离敌人的地方重生,这样他们就不会在重生后立即死亡。

我目前所拥有的: 我编写的算法有效,但在速度较慢的手机中表现不佳。我基本上将网格划分为正方形(很像井字游戏),并为每个正方形分配一个数字。然后,我检查每个方格与所有敌人的情况,并存储每个方格最近的敌人是什么。数字最大的方格是距离最近的敌人最远的方格。我还尝试平均现有点并做类似的事情,虽然性能可以接受,但该方法的可靠性不是。

【问题讨论】:

  • 如果您还没有这样做,请尝试将 squareroot(deltax^2 + deltay^2) 替换为 deltax+deltay,这应该会提高您的性能。
  • 一种方法可能是设置一些最小距离阈值,新玩家必须远离所有敌人才能重生,然后生成随机点,直到其中一个达到该阈值。它不是最佳的,但它会很快并且可能“足够好”。
  • 与最近的敌人的距离是最重要的,还是如果所有其他人都远,一个人可以相对近吗?
  • @huck_cussler 这是一个好主意,请将其作为答案发布,因为它可能正是我正在寻找的。 m69 是的,这很重要,因为它是一款快节奏的游戏。 Roy Shahaf,仍然没有骰子,谢谢!
  • 对敌人最好的战术是围绕中心组织成一个半径为 125*sqrt(2) 的圆圈,然后在玩家进入陷阱时拉紧网。

标签: algorithm search point


【解决方案1】:

Triangulate 敌人(少于 5 个?);并用最近的一对敌人对网格的每个角落进行三角测量。这些三角形之一的外心应该是一个不错的重生地点。

以下是 JavaScript 中的示例。我使用 m69 的答案中的 canvas 方法进行演示。绿点是经过测试以得出蓝色建议的候选人。由于我们正在对角进行三角测量,因此这里没有将它们作为解决方案提供(也许随机更接近的解决方案可能会让玩家兴奋?或者,也只需测试角......)。

// http://stackoverflow.com/questions/4103405/what-is-the-algorithm-for-finding-the-center-of-a-circle-from-three-points
function circumcenter(x1,y1,x2,y2,x3,y3)
{
  var offset = x2 * x2 + y2 * y2;
  var bc =   ( x1 * x1 + y1 * y1 - offset ) / 2;
  var cd =   (offset - x3 * x3 - y3 * y3) / 2;
  var det =  (x1 - x2) * (y2 - y3) - (x2 - x3)* (y1 - y2);

  var idet = 1/det;

  var centerx =  (bc * (y2 - y3) - cd * (y1 - y2)) * idet;
  var centery =  (cd * (x1 - x2) - bc * (x2 - x3)) * idet;

  return [centerx,centery];
}

var best = 0,
    candidates = [];

function better(pt,pts){
  var temp = Infinity;
  for (var i=0; i<pts.length; i+=2){
    var d = (pts[i] - pt[0])*(pts[i] - pt[0]) + (pts[i+1] - pt[1])*(pts[i+1] - pt[1]);
    if (d <= best)
      return false;
    else if (d < temp)
      temp = d;
  }
  best = temp;
  return true;
}

function f(es){
  if (es.length < 2)
    return "farthest corner";
    
  var corners = [0,0,500,0,500,500,0,500],
      bestcandidate;  
  
  // test enemies only
  if (es.length > 2){
    for (var i=0; i<es.length-4; i+=2){
      for (var j=i+2; j<es.length-2; j+=2){
        for (var k=j+2; k<es.length; k+=2){
          var candidate = circumcenter(es[i],es[i+1],es[j],es[j+1],es[k],es[k+1]);
          if (candidate[0] < 0 || candidate[1] < 0 || candidate[0] > 500 || candidate[1] > 500)
            continue;
          candidates.push(candidate[0]);
          candidates.push(candidate[1]);
          if (better(candidate,es))
            bestcandidate = candidate.slice();
        }
      }
    }
  }
  //test corners
  for (var i=0; i<8; i+=2){
    for (var j=0; j<es.length-2; j+=2){
      for (var k=j+2; k<es.length; k+=2){
        var candidate = circumcenter(corners[i],corners[i+1],es[j],es[j+1],es[k],es[k+1]);
        if (candidate[0] < 0 || candidate[1] < 0 || candidate[0] > 500 || candidate[1] > 500)
          continue;
        candidates.push(candidate[0]);
        candidates.push(candidate[1]);
        if (better(candidate,es))
          bestcandidate = candidate.slice();
      }
    }
  }
  best = 0;
  return bestcandidate;
}

// SHOW RESULT ON CANVAS
var canvas = document.getElementById("canvas");
canvas.width = 500; canvas.height = 500;
context = canvas.getContext("2d");

//setInterval(function() {
  // CREATE TEST DATA
  context.clearRect(0, 0, canvas.width, canvas.height);
  candidates = [];

  var enemy = [];
  for (var i = 0; i < 8; i++) enemy.push(Math.round(Math.random() * 500));

  // RUN FUNCTION
  var result = f(enemy);

  for (var i = 0; i < 8; i+=2) {
    paintDot(context, enemy[i], enemy[i+1], 10, "red");
  }

  for (var i = 0; i < candidates.length; i+=2) {
    paintDot(context, candidates[i], candidates[i+1], 7, "green");
  }

  paintDot(context, result[0], result[1], 18, "blue");

  function paintDot(context, x, y, size, color) {
  context.beginPath();
  context.arc(x, y, size, 0, 6.2831853);
  context.closePath();
  context.fillStyle = color;
  context.fill();
  }
//},1500);
<BODY STYLE="margin: 0; border: 0; padding: 0;">
<CANVAS ID="canvas" STYLE="width: 200px; height: 200px; background: 
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.15) 30%, rgba(255,255,255,.3) 32%, rgba(255,255,255,0) 33%) 0 0,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.1) 11%, rgba(255,255,255,.3) 13%, rgba(255,255,255,0) 14%) 0 0,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 17%, rgba(255,255,255,.43) 19%, rgba(255,255,255,0) 20%) 0 110px,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 11%, rgba(255,255,255,.4) 13%, rgba(255,255,255,0) 14%) -130px -170px,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 11%, rgba(255,255,255,.4) 13%, rgba(255,255,255,0) 14%) 130px 370px,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.1) 11%, rgba(255,255,255,.2) 13%, rgba(255,255,255,0) 14%) 0 0,
linear-gradient(45deg, #343702 0%, #184500 20%, #187546 30%, #006782 40%, #0b1284 50%, #760ea1 60%, #83096e 70%, #840b2a 80%, #b13e12 90%, #e27412 100%);
background-size: 470px 470px, 970px 970px, 410px 410px, 610px 610px, 530px 530px, 730px 730px, 100% 100%;
background-color: #840b2a;"></CANVAS>
<!-- http://lea.verou.me/css3patterns/#rainbow-bokeh -->
</BODY>

【讨论】:

  • 也许你应该把动画放慢一点;有时安全点的选择似乎不是最优的,但真正判断它需要更多时间,尤其是绿点使图像复杂化。
【解决方案2】:

您可以在网格上选择一些随机点,然后从敌人中迭代地移动它,这是我在 python 中的实现:

from numpy import array
from numpy.linalg import norm
from random import random as rnd


def get_pos(enem):
    # chose random start position
    pos = array([rnd() * 500., rnd() * 500.])
    # make several steps from enemies
    for i in xrange(25):  # 25 steps
        s = array([0., 0.])  # step direction
        for e in enem:
            vec = pos - array(e)  # direction from enemy
            dist = norm(vec)  # distance from enemy
            vec /= dist  # normalize vector
            # calculate size of step
            step = (1000. / dist) ** 2
            vec *= step
            s += vec
        # update position
        pos += s
        # ensure that pos is in bounds
        pos[0] = min(max(0, pos[0]), 500.)
        pos[1] = min(max(0, pos[1]), 500.)
    return pos


def get_dist(enem, pos):
    dists = [norm(pos - array(e)) for e in enem]
    print 'Min dist: %f' % min(dists)
    print 'Avg dist: %f' % (sum(dists) / len(dists))

enem = [(0., 0.), (250., 250.), (500., 0.), (0., 500.), (500., 500.)]
pos = get_pos(enem)
print 'Position: %s' % pos
get_dist(enem, pos)

输出:

Position: [   0.          250.35338215]
Min dist: 249.646618
Avg dist: 373.606883

【讨论】:

    【解决方案3】:

    这是一个有趣的解决方案,但我无法测试它的效率。对于每个敌人,从每个数字中制作一行数字,从 1 开始,距离每增加 1 就增加 1。四个初始线将来自四个边缘,每次你再往外走,你就会创建另一条以 90 度角出来的线,也会增加每次距离变化的数量。如果数轴遇到一个比它小的已经创建的数,它不会覆盖它并且将停止进一步到达。从本质上讲,这使得如果线条找到比它小的数字,它不会检查任何进一步的网格标记,从而无需检查所有敌人的整个网格。

    <<<<<<^^^^^^^
    <<<<<<^^^^^^^
    <<<<<<X>>>>>>       
    vvvvvvv>>>>>>
    vvvvvvv>>>>>>
    
    public void map(int posX, int posY)
    {
        //left up right down
        makeLine(posX, posY, -1, 0, 0, -1);
        makeLine(posX, posY, 0, 1, -1, 0);
        makeLine(posX, posY, 1, 0, 0, 1);
        makeLine(posX, posY, 0, -1, 1, 0);
        grid[posX][posY] = 1000;
    }
    public void makeLine(int posX, int posY, int dirX, int dirY, int dir2X, int dir2Y)
    {
        int currentVal = 1;
        posX += dirX;
        posY += dirY;
        while (0 <= posX && posX < maxX && posY < maxY && posY >= 0 && currentVal < grid[posX][posY])
        {
            int secondaryPosX = posX + dir2X;
            int secondaryPosY = posY + dir2Y;
            int secondaryVal = currentVal + 1;
            makeSecondaryLine( secondaryPosX, secondaryPosY, dir2X, dir2Y, secondaryVal);
            makeSecondaryLine( secondaryPosX, secondaryPosY, -dir2X, -dir2Y, secondaryVal);
            grid[posX][posY] = currentVal;
            posX += dirX;
            posY += dirY;
            currentVal++;
        }
    }
    public void makeSecondaryLine(int secondaryPosX, int secondaryPosY, int dir2X, int dir2Y, int secondaryVal)
    {
        while (0 <= secondaryPosX && secondaryPosX < maxX && secondaryPosY < maxY && 
                secondaryPosY >= 0 && secondaryVal < grid[secondaryPosX][secondaryPosY])
        {
            grid[secondaryPosX][secondaryPosY] = secondaryVal;
            secondaryPosX += dir2X;
            secondaryPosY += dir2Y;
            secondaryVal++;
        }
    }
    

    }

    这是我用来绘制整个网格的代码。这样做的好处是,检查/写入数字的次数并不太依赖于屏幕上敌人的数量。使用计数器和随机生成的敌人,我能够得到:124 个敌人和 1528537 个检查,68 个敌人和 1246769 个检查,15 个敌人和 795695 500 个敌人和 1747452 个检查。与您之前的代码相比,这是一个巨大的差异,它会执行敌人数 * 空格数。 对于 124 个敌人,你应该进行 31000000 次检查,而这却做了 1528537 次,不到正常检查次数的 5%。

    【讨论】:

    • 我不完全确定您所说的“从每个数字制作一行数字”和“四个初始行将来自四个边缘”是什么意思。您能否澄清答案或发布一些伪代码?
    • 我很想知道我的代码与此相比的表现如何,因此我尝试对您的代码进行 javascript 化以进行快速比较。您对 maxX 和 maxY 使用哪些值?
    • maxX 和 maxY 是网格的大小。如果网格是 500x500 maxX 是 500 而 maxY 是 500。我使用了 500x500 网格。您还可以与网格上的每个敌人一起运行地图,而不仅仅是其中一个。
    • 然后通过网格找到数字最高的点。
    • 这样做的好处是您也可以使用更粗的网格;这样您就可以选择是否需要准确性或速度,或者介于两者之间。 (很难计时,因为我必须在每次运行之间用初始值填充网格。)
    【解决方案4】:

    这是我能想到的最简单的算法,但仍能提供良好的结果。它只检查 9 个可能的位置:角、边的中间和中心点。大多数情况下,玩家最终都在角落里,但你显然需要比敌人更多的位置。

    算法在我的 i5 桌面上运行时间为 0.013 毫秒。如果将 Math.pow() 替换为 Math.abs(),则可以降低到 0.0088 毫秒,尽管结果显然不太可靠。 (奇怪的是,这比我使用三角函数的其他答案要慢。)

    (重复)运行代码 sn-p 将在画布元素中显示随机定位的敌人的结果。

    function furthestFrom(enemy) {
        var point = [{x:0,y:0},{x:250,y:0},{x:500,y:0},{x:0,y:250},{x:250,y:250},{x:500,y:250},{x:0,y:500},{x:250,y:500},{x:500,y:500}];
        var dist2 = [500000,500000,500000,500000,500000,500000,500000,500000,500000];
        var max = 0, furthest;
    
        for (var i in point) {
            for (var j in enemy) {
                 var d = Math.pow(point[i].x - enemy[j].x, 2) + Math.pow(point[i].y - enemy[j].y, 2);
                 if (d < dist2[i]) dist2[i] = d;
            }
            if (dist2[i] > max) {
                max = dist2[i];
                furthest = i;
            }
        }
        return(point[furthest]);
    }
    
    // CREATE TEST DATA
    var enemy = [];
    for (var i = 0; i < 5; i++) enemy[i] = {x: Math.round(Math.random() * 500), y: Math.round(Math.random() * 500)};
    
    // RUN FUNCTION
    var result = furthestFrom(enemy);
    
    // SHOW RESULT ON CANVAS
    var canvas = document.getElementById("canvas");
    canvas.width = 500; canvas.height = 500;
    canvas = canvas.getContext("2d");
    for (var i = 0; i < 5; i++) {
        paintDot(canvas, enemy[i].x, enemy[i].y, 10, "red");
    }
    paintDot(canvas, result.x, result.y, 20, "blue");
    function paintDot(canvas, x, y, size, color) {
    canvas.beginPath();
    canvas.arc(x, y, size, 0, 6.2831853);
    canvas.closePath();
    canvas.fillStyle = color;
    canvas.fill();
    }
    <BODY STYLE="margin: 0; border: 0; padding: 0;">
    <CANVAS ID="canvas" STYLE="width: 200px; height: 200px; background-color: #EEE;"></CANVAS>
    </BODY>

    【讨论】:

    • 在尝试了此页面上的所有答案之后,我最终使用了这个答案,因为它给了我可预测的性能。我在每次玩游戏时动态调整预设点,这样它们至少在每个关卡上都会“看起来”随机。谢谢!
    • @Julian 很高兴你发现它很有用。
    【解决方案5】:

    此方法从中心点查看所有敌人,检查他们所在的方向,找到最空的扇区,然后返回通过该扇区中间的直线上的一个点,距离中心 250。 结果并不总是完美的,安全点永远不会在中心(尽管可以添加),但也许它已经足够好了。

    该算法在我的 i5 台式机上每秒运行超过一百万次,但手机的三角函数可能不是那么好。该算法对每个敌人使用 3 个三角函数:atan2()、cos() 和 sin()。这些可能对执行速度的影响最大。也许您可以用查找表替换 cos() 和 sin()。

    运行代码 sn-p 以查看随机定位敌人的示例。

    function furthestFrom(e) {
        var dir = [], widest = 0, bisect;
        for (var i = 0; i < 5; i++) {
            dir[i] = Math.atan2(e[i].y - 250, e[i].x - 250);
        }
        dir.sort(function(a, b){return a - b});
        dir.push(dir[0] + 6.2831853);
        for (var i = 0; i < 5; i++) {
            var angle = dir[i + 1] - dir[i];
            if (angle > widest) {
                widest = angle;
                bisect = dir[i] + angle / 2;
            }
        }
        return({x: 250 * (1 + Math.cos(bisect)), y: 250 * (1 + Math.sin(bisect))});
    }
    
    // CREATE TEST DATA
    var enemy = [];
    for (var i = 0; i < 5; i++) enemy[i] = {x: Math.round(Math.random() * 500), y: Math.round(Math.random() * 500)};
    
    // RUN FUNCTION AND SHOW RESULT ON CANVAS
    var result = furthestFrom(enemy);
    var canvas = document.getElementById("canvas");
    canvas.width = 500; canvas.height = 500;
    canvas = canvas.getContext("2d");
    for (var i = 0; i < 5; i++) {
        paintDot(canvas, enemy[i].x, enemy[i].y, "red");
    }
    paintDot(canvas, result.x, result.y, "blue");
    
    // PAINT DOT ON CANVAS
    function paintDot(canvas, x, y, color) {
    canvas.beginPath();
    canvas.arc(x, y, 10, 0, 6.2831853);
    canvas.closePath();
    canvas.fillStyle = color;
    canvas.fill();
    }
    <BODY STYLE="margin: 0; border: 0; padding: 0">
    <CANVAS ID="canvas" STYLE="width: 200px; height: 200px; background-color: #EEE;"CANVAS>
    </BODY>

    【讨论】:

      【解决方案6】:

      这与您已经在做的类似,但有两个通道,第一个通道可能相当粗糙。首先降低分辨率。将 500x500 网格划分为 10x10 网格,每个网格为 50x50。对于生成的 100 个子网格中的每一个 - 确定哪些子网格至少有一个敌人,并找到离包含敌人的子网格最远的子网格。在这个阶段,只有 100 个子网格需要担心。一旦你找到离敌人最远的子网格——提高分辨率。该子网格有 50x50 = 2500 个正方形。用这些方块做你原来的方法。结果是要处理 50x50 + 100 = 2600 个方格,而不是 500x500 = 250,000。 (在没有 500x500 但基本策略相同的情况下,适当调整数字)。

      这是一个 Python3 实现。它使用两个功能:

      1) fullResSearch(a,b,n,enemies) 该函数获取一组敌人、一个角位置(a,b) 和一个int,n,并在nxn 平方的位置中找到左上角为(a, b) 并在该方格中找到与敌人的最小距离最大的点。不假定敌人在这个 nxn 网格中(尽管他们当然可以)

      2) findSafePoint(n, enemies, mesh = 20) 这个函数接受一组敌人,这些敌人被假定在从 (0,0) 开始的 nxn 网格中。 mesh 确定子网格的大小,默认为 20。我认为整个网格分为 mesh x mesh 子网格(如果mesh 不划分n,则沿边界稍小)作为领土。如果一个领土内有敌人,我称它为敌对领土。我创建了一组敌方领土并将其传递给fullResSearch,参数n除以mesh而不是n。返回值给了我离任何敌方领土最远的领土。这样的领土可以认为是相当安全的。我将该区域反馈到fullResSearch,以找到该区域中最安全的点作为整体返回函数。结果点要么是最优的,要么是接近最优的,并且计算得非常快。这是代码(连同test 函数):

      import random
      
      def fullResSearch(a,b,n,enemies):
      
          minDists = [[0]*n for i in range(n)]
          for i in range(n):
              for j in range(n):
                  minDists[i][j] = min((a+i - x)**2 + (b+j - y)**2 for (x,y) in enemies)
      
          maximin = 0
          for i in range(n):
              for j in range(n):
                  if minDists[i][j] > maximin:
                      maximin = minDists[i][j]
                      farthest = (a+i,b+j)
          return farthest
      
      def findSafePoint(n, enemies, mesh = 20):
          m = n // mesh 
          territories = set() #enemy territories
          for (x,y) in enemies:
              i = x//mesh
              j = y//mesh
              territories.add((i,j))
          (i,j) = fullResSearch(0,0,m,territories)
          a = i*mesh
          b = j*mesh
          k = min(mesh,n - a,n - b) #in case mesh doesn't divide n
          return fullResSearch(a,b,k,enemies)
      
      def test(n, numEnemies, mesh = 20):
          enemies = set()
          count = 0
          while count < numEnemies:
              i = random.randint(0,n-1)
              j = random.randint(0,n-1)
              if not (i,j) in enemies:
                  enemies.add ((i,j))
                  count += 1
          for e in enemies: print("Enemy at", e)
          print("Safe point at", findSafePoint(n,enemies, mesh))
      

      典型的运行:

      >>> test(500,5)
      Enemy at (216, 67)
      Enemy at (145, 251)
      Enemy at (407, 256)
      Enemy at (111, 258)
      Enemy at (26, 298)
      Safe point at (271, 499)
      

      (我通过在整个网格上使用 fullResSearch 验证了 (271,499) 实际上对于这些敌人来说是最优的)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多