优化函数以将圆放置在不与其他圆重叠的位置

时间:2016-09-13 05:31:06

标签: algorithm

我有一个算法将圆圈放在xy平面上。为了记录,我现在用PHP编程,但这个问题主要是关于更大的算法。

所以这是基本的算法代码:

function create_circles($n) {
  for ($i = 0; $i < $n; $i++) {
    $x = rand_x();
    $y = rand_y();
    $coord = array($x, $y);
    $radius = rand_radius();
    if ($i !== 0) {
      for ($m = 0; $m < $i; $m++) {
        $distance = distance($coord, $position[$m]);
        if ($radius + $radii[$m] > $distance) {
          //REPEAT FUNCTION AGAIN
        } else {
          continue;
        }
      }
    }
    //If function completes correctly
    $radii[$i] = $radius;
    $position[$i] = $coord;
  }
}

这是一个简化的代码,我没有包含任何变量声明或辅助功能。我希望这是足够的信息。

目前,此功能有效,但需要一个小时才能放置100个圆圈。我正在寻找一种简化此功能并缩短执行时间的方法。

修改

好的,解决评论和答案。首先非常感谢你。其次,我将提供更多信息。

环境维度是无限的,这很重要,随机位置生成器有利于靠近原点(0,0)的位置。因此,最终结果应该包括大多数圆圈位于中心。此分布由1 / x表示。

圆圈也沿螺旋路径绘制(即y = atsint,x = atcos),并包含一些随机偏移。

我不需要完全随机性,但只是模拟随机性,如果这会提高性能,即。我会考虑缩小随机min,max以支持增加速度。

2 个答案:

答案 0 :(得分:2)

您可以使用此策略:

  1. 首先创建随机坐标,并为每个坐标假设零半径
  2. 计算距离集合中最近的其他点的距离。这是给定圆可以设置的半径的上限: max_radius
  3. 在此算法中未获得明确半径的圆圈中选择具有最高 max_radius 的圆圈
  4. 为其生成一个随机半径,该半径应低于 max_radius
  5. 检查所有其他点与此新圈子的距离,并在需要时减少 max_radius
  6. 从第3步开始重复,直到没有更多没有半径的圆圈。
  7. 此过程在 O(n²)中运行。它可以进一步优化,但我发现100个圆圈的结果几乎是立即的:

    select ult.*
    from userLogTable ult
    where ult.date > (select max(a.date)
                      from activityTable a
                      where a.name = ult.name and a.surname = ult.surname
                     ) ;
    

    我在PHP中添加了一些JavaScript,它在画布上绘制圆圈。以下是生成脚本的示例,以显示结果:

    <?php
    function rand_x() {
        // use your own code and random distribution here. This is just a mock.
        return rand(10, 490); 
    }
    function rand_y() {
        // use your own code and random distribution here. This is just a mock.
        return rand(10, 290);
    }
    function rand_radius($max) {
        // Use your own code and random distribution here. This is just a mock.
        // However: it should not generate a number higher than the parameter value!
        return rand(0, min($max, 50));
    }
    function distance($a, $b) {
        return sqrt(pow(($a[0]-$b[0]),2) + pow(($a[1]-$b[1]),2));
    }
    
    function create_circles($n) {
        $position = array();
        // Generate all circles with radius 0
        for ($i = 0; $i < $n; $i++) {
            $circles[] = array(
                "id" => $i,
                "position" => array(rand_x(), rand_y()),
                "radius" => 0,
                "max_radius" => 1e100 // "infinity"
            );
        }
        function &adjustRadiiToCircle(&$ref_circle, &$circles) {
            $selected_circle = null;
            foreach ($circles as &$circle) {
                if ($circle['radius'] == 0 and $ref_circle['id'] !== $circle['id']) { // not yet assigned a radius
                    $distance = distance($circle['position'], $ref_circle['position']);
                    $circle['max_radius'] = min($circle['max_radius'], $distance - $ref_circle['radius']);
                    if ($selected_circle == null or $circle['max_radius'] > $selected_circle['max_radius']) {
                        $selected_circle = &$circle;
                    }
                }
            }
            return $selected_circle;
        }
        // Calculate maxium radius that each circle can have
        // and remember the one with the highest value for that
        $selected_circle = null;
        foreach ($circles as $circle) {
            // Set or adapt max_distance for all circles in relation to this circle
            $selected_circle = &adjustRadiiToCircle($circle, $circles);
            // NB: we ignore the return value, except for last iteration
        }
    
        // Choose n times a circle to give it a random radius
        foreach ($circles as $ignore){
            // Get a random radius for the selected circle.
            // The rand_radius function gets an argument, so it can make sure not to generate
            // anything greater than that.      
            $selected_circle['radius'] = rand_radius($selected_circle['max_radius']);
            $selected_circle = &adjustRadiiToCircle($selected_circle, $circles);
        };
        return $circles;
    }   
    
    $circles = create_circles(100);
    
    ?>
    <canvas width="500" height="300" style="border: 1px solid"></canvas>
    <script>
    var circles = <?=json_encode($circles)?>;
    var canvas = document.querySelector('canvas');
    var ctx = canvas.getContext('2d');
    
    for (var circle of circles) {
        console.log(circle.position[0],circle.position[1],circle.radius);
        ctx.beginPath();
        ctx.arc(circle.position[0],circle.position[1], circle.radius, 0, 2 * Math.PI, false);
        ctx.fillStyle = 'yellow';
        ctx.fill();
        ctx.lineWidth = 2;
        ctx.stroke();
    }
    </script>
    
    var circles = [{"id":0,"position":[318,82],"radius":3,"max_radius":51.6623654124},{"id":1,"position":[397,52],"radius":10,"max_radius":15.8113883008},{"id":2,"position":[150,113],"radius":21,"max_radius":27.7308492477},{"id":3,"position":[296,194],"radius":1,"max_radius":15.0512483795},{"id":4,"position":[251,194],"radius":3,"max_radius":20.8086520467},{"id":5,"position":[97,253],"radius":3,"max_radius":3.49285568454},{"id":6,"position":[484,16],"radius":25,"max_radius":73},{"id":7,"position":[76,141],"radius":16,"max_radius":16.1245154966},{"id":8,"position":[133,195],"radius":2,"max_radius":6.39607805437},{"id":9,"position":[361,287],"radius":1,"max_radius":34.8281495345},{"id":10,"position":[57,282],"radius":19,"max_radius":41.4366987102},{"id":11,"position":[252,270],"radius":2,"max_radius":12.0830459736},{"id":12,"position":[67,73],"radius":10,"max_radius":19.2353840617},{"id":13,"position":[356,117],"radius":5,"max_radius":10.9317121995},{"id":14,"position":[152,238],"radius":11,"max_radius":21.3775583264},{"id":15,"position":[394,273],"radius":14,"max_radius":17.0293863659},{"id":16,"position":[43,180],"radius":2,"max_radius":12.0830459736},{"id":17,"position":[53,111],"radius":14,"max_radius":19.4164878389},{"id":18,"position":[478,202],"radius":18,"max_radius":18.3847763109},{"id":19,"position":[18,143],"radius":10,"max_radius":11.0453610172},{"id":20,"position":[174,66],"radius":3,"max_radius":3.60555127546},{"id":21,"position":[175,101],"radius":2,"max_radius":6.73084924772},{"id":22,"position":[117,145],"radius":14,"max_radius":19.2353840617},{"id":23,"position":[391,144],"radius":11,"max_radius":12.8617393793},{"id":24,"position":[78,157],"radius":0,"max_radius":0.124515496597},{"id":25,"position":[233,77],"radius":7,"max_radius":8.94427191},{"id":26,"position":[247,281],"radius":1,"max_radius":3.04536101719},{"id":27,"position":[90,182],"radius":22,"max_radius":23.3452350599},{"id":28,"position":[102,259],"radius":1,"max_radius":7.46424919657},{"id":29,"position":[489,147],"radius":2,"max_radius":15.2626765016},{"id":30,"position":[346,234],"radius":4,"max_radius":15.5600808897},{"id":31,"position":[214,225],"radius":6,"max_radius":27.6586333719},{"id":32,"position":[203,154],"radius":0,"max_radius":1.76305461424},{"id":33,"position":[72,115],"radius":1,"max_radius":5.41648783895},{"id":34,"position":[106,51],"radius":2,"max_radius":6.80441152621},{"id":35,"position":[419,162],"radius":9,"max_radius":12.0415945788},{"id":36,"position":[177,64],"radius":0,"max_radius":0.605551275464},{"id":37,"position":[428,170],"radius":3,"max_radius":3.04159457879},{"id":38,"position":[335,196],"radius":24,"max_radius":33.5261092285},{"id":39,"position":[34,239],"radius":10,"max_radius":17},{"id":40,"position":[100,154],"radius":1,"max_radius":5.23538406167},{"id":41,"position":[38,201],"radius":1,"max_radius":6.76972864801},{"id":42,"position":[241,73],"radius":0,"max_radius":1.94427191},{"id":43,"position":[153,199],"radius":14,"max_radius":20.3960780544},{"id":44,"position":[64,92],"radius":4,"max_radius":7.9544984001},{"id":45,"position":[88,238],"radius":14,"max_radius":17.4928556845},{"id":46,"position":[54,175],"radius":7,"max_radius":10.0830459736},{"id":47,"position":[289,148],"radius":7,"max_radius":39.9624824054},{"id":48,"position":[60,210],"radius":17,"max_radius":23.769728648},{"id":49,"position":[428,95],"radius":2,"max_radius":13.0208242989},{"id":50,"position":[424,213],"radius":10,"max_radius":17.88854382},{"id":51,"position":[418,68],"radius":9,"max_radius":26.4007575649},{"id":52,"position":[451,211],"radius":8,"max_radius":23.6008474424},{"id":53,"position":[385,176],"radius":6,"max_radius":29.8516480713},{"id":54,"position":[193,243],"radius":17,"max_radius":21.6586333719},{"id":55,"position":[208,170],"radius":15,"max_radius":16.7630546142},{"id":56,"position":[176,142],"radius":4,"max_radius":5.09901951359},{"id":57,"position":[131,242],"radius":1,"max_radius":10.3775583264},{"id":58,"position":[345,164],"radius":6,"max_radius":9.52610922848},{"id":59,"position":[251,238],"radius":4,"max_radius":9.20459156783},{"id":60,"position":[416,197],"radius":5,"max_radius":7.88854382},{"id":61,"position":[310,273],"radius":5,"max_radius":6.89173349139},{"id":62,"position":[132,68],"radius":11,"max_radius":23.7234787586},{"id":63,"position":[217,110],"radius":20,"max_radius":22.5610283454},{"id":64,"position":[452,94],"radius":11,"max_radius":22.3882694814},{"id":65,"position":[329,229],"radius":1,"max_radius":9.5410196625},{"id":66,"position":[21,154],"radius":0,"max_radius":1.40175425099},{"id":67,"position":[486,275],"radius":17,"max_radius":43.1856457634},{"id":68,"position":[121,226],"radius":2,"max_radius":18.8679622641},{"id":69,"position":[268,182],"radius":5,"max_radius":17.8086520467},{"id":70,"position":[300,276],"radius":3,"max_radius":5.44009029334},{"id":71,"position":[98,276],"radius":10,"max_radius":17.4642491966},{"id":72,"position":[212,132],"radius":1,"max_radius":2.56102834536},{"id":73,"position":[384,43],"radius":1,"max_radius":5.81138830084},{"id":74,"position":[484,89],"radius":10,"max_radius":32.3882694814},{"id":75,"position":[457,243],"radius":15,"max_radius":22.2036033112},{"id":76,"position":[465,166],"radius":4,"max_radius":21.0950231097},{"id":77,"position":[379,66],"radius":1,"max_radius":2},{"id":78,"position":[377,66],"radius":0,"max_radius":1},{"id":79,"position":[279,201],"radius":8,"max_radius":12.4499705536},{"id":80,"position":[251,73],"radius":1,"max_radius":3.40175425099},{"id":81,"position":[232,158],"radius":8,"max_radius":11.83281573},{"id":82,"position":[365,137],"radius":11,"max_radius":12.319604517},{"id":83,"position":[236,282],"radius":8,"max_radius":11.0453610172},{"id":84,"position":[118,12],"radius":34,"max_radius":40.8044115262},{"id":85,"position":[290,242],"radius":30,"max_radius":35.4400902933},{"id":86,"position":[470,225],"radius":1,"max_radius":6.35159132377},{"id":87,"position":[384,116],"radius":16,"max_radius":28.0178514522},{"id":88,"position":[107,166],"radius":0,"max_radius":1.34523505986},{"id":89,"position":[248,62],"radius":8,"max_radius":11.401754251},{"id":90,"position":[413,130],"radius":1,"max_radius":16.2024843762},{"id":91,"position":[19,231],"radius":5,"max_radius":7},{"id":92,"position":[482,231],"radius":5,"max_radius":11.2745623366},{"id":93,"position":[485,219],"radius":0,"max_radius":0.38477631085},{"id":94,"position":[486,164],"radius":2,"max_radius":17.0950231097},{"id":95,"position":[216,148],"radius":4,"max_radius":8.40939982144},{"id":96,"position":[383,260],"radius":3,"max_radius":3.02938636593},{"id":97,"position":[19,154],"radius":0,"max_radius":1.04536101719},{"id":98,"position":[175,147],"radius":0,"max_radius":1.09901951359},{"id":99,"position":[242,170],"radius":1,"max_radius":15.6204993518}];
    var canvas = document.querySelector('canvas');
    var ctx = canvas.getContext('2d');
    
    for (var circle of circles) {
        ctx.beginPath();
        ctx.arc(circle.position[0],circle.position[1], circle.radius, 0, 2 * Math.PI, false);
        ctx.fillStyle = 'yellow';
        ctx.fill();
        ctx.lineWidth = 2;
        ctx.stroke();
    }

答案 1 :(得分:1)

(如果您有更好的信息,我欢迎任何人编辑我的答案......并且会有人这样做)

思想

关于你需要多少随机性,问题并不明确。现在你在一个随机圆圈的场上放下一个随机半径和随机位置的圆圈。根据参数,这可能需要很长时间。想象一个1x1的正方形,最大半径为0.5。如果第一个圆圈正好位于中间并且半径接近中间,那么几乎每个坐标都必须非常接近0或1并且半径非常接近0.取决于剩余的空间,机会一个不与任何其他圆相交的圆圈在某些方面显然非常渺茫。

现在出现具有随机性的问题。你需要多少随机性?是否可以限制半径的选择?例如,如果方形框中的最大半径为X/2,则测试的平均半径将为X/4。这种圆圈只能在盒子中适合4次(具有完美的定位)。在这四个位置耗尽之后,一半的测试已经失败,因为技术上平均半径的圆不适合任何地方。这是非常保守的做法。

圈子有一个非常讨厌和非常好的财产。令人讨厌的是,他们很难近似。好的是,重叠很容易确定。

天真优化:网格

你可能会制作一个宽度合理的网格。然后,对于每个单元格,都有一个与该单元格重叠的圆圈列表,并且还要注意,如果单元格已经填充。根据网格单元的大小和圆的平均大小(单元格尺寸应该更小),将有许多或几个单元完全重叠(许多更好 - >更窄的网格)。现在,如果您为一个坐标滚动,您已经可以中止,当圆圈无法适应任何地方时,无需滚动随机半径。通过查看周围的单元格,您可以找出最大半径可能是多少。您也可以为每个单元格存储该半径。这些想法最终会导致......

一般优化:空间索引结构

有一些索引结构可以回答覆盖问题并可以存储圆圈(以及其他内容)。关键词空间索引/空间数据库。如果你能提供帮助,你就不应该实施这些。主要思想是,你有一些算法可以将你的空间分成更小和更小的部分,以减少要进行的比较次数,并且可以非常快速地回答覆盖问题。说实话,有很多这些索引,而且不是我的专业领域,所以你可能会研究哪个索引可能是你用例中最好的。

更改问题?

如果允许您更改圆圈的生成方式,您可以首先找到一个尚未与另一个圆重叠的位置,然后确定该位置的圆圈可能具有的最大半径,然后滚动具有该最大值的随机半径。 (如果允许更改问题,这将是我首选的方法。可能会显示 ,这会产生与其他方法几乎相似的结果......但我不确定我可能错了)