zoukankan      html  css  js  c++  java
  • 在矩形范围内种怪的算法问题

          今天在工作中遇到这样一个问题:给定1个矩形,左下角的点point(x, y),长w,高h,要在这个矩形里随机出n个不同的点用来种怪。这个算法该怎么写呢?这对于我来说确实成为了一个问题。图示如下:

                          

          由于任务时间紧,做的又是Demo的原因,我不假思索的写出了下面这个算法:

    //从[from, to]区间中随机一个整数
    function randomInt (from, to) {
        //方法实现就不写了
    }
    function randomCoords (point, w, h, n) { //目的数组
    var coords = []; var map = {}; var count = n; if ((w + 1) * (h + 1) < n) { count = (w + 1) * (h + 1); } while (count > 0) { var x = randomInt(point.x, point.x + w); var y = randomInt(point.y, point.y + h); //已经随机出该点,就丢掉 if (map[x] && map[x][y]) continue; if (!map[x]) map[x] = {}; map[x][y] = true; coords.push({x:x, y:y}); count --; }
    return coords;
    }

          这个算法着实太笨了,当时也没有细想。等把任务完成了,我发现离下班的时间还有很长一段时间。因为制度上要求要加班,我只能忍了。我想,如果被别人看到我写的这么一个垃圾算法,那他肯定认为我实在太Low了。所以,我开始认真思索这个问题。

          先不考虑优化的算法该如何实现,我想先了解一下这个算法到底有多Low。我假设了一种情况,长和高都是5,然后把所有点都随机出来,需要的平均次数是多少呢?答案是:5/5 * 5/4 * 5/3 * 5/2 * 5/1 = 26.04166667。还好,不是很多次,还能承受。然后我把5改成n做成一个图表,图表如下:

          当n=10的时候,就需要循环2000多次;而当n=12的时候,就得需要循环18000多次了。图表是不是很吓人,我反正是惊呆了。这绝对不行,如果是做项目的时候要这样写,我肯定已经被老板骂死了。

          那该怎么改呢?我马上就想到了一个改进的方法。如果要随机的点数超出一半点数的话,我就随机出不用的点来,那么剩下的点就是我要的点了。这个想法其实很好,但是要结合上面的算法用的话,其实还不是很好。

          那么到底该怎么实现呢?我开始从问题本质考虑。一般情况下,随机分为两种,放回抽样随机和不放回抽样随机,它们随机所需要的次数都是n次,只不过不放回抽样需要把已经随机出来的元素从样本库里拿出来。

          很显然,这个问题是不放回抽样随机。那么又该怎么把已经随机出来的元素从样本库里拿出来呢?有一种代码里常用的方法,就是构建一个样本库的数组,然后再从数组里把那个元素拿出来。这样的确可以避免随机次数成近似指数级增长。它构建样本库数组的增长曲线是平方级,一般情况下是服务器可以接受的。但是如果要在手机等移动设备上执行的话,我们就要精益求精了。那么有没有更好的算法呢?当然有。

          我就不废话了,说一下我最终是如何实现的:

              1、求出矩形中所拥有的点数totalNum;

              2、在[0, totalNum-1]区间中随机出一个数字来,然后把该数字按一定规则对应到矩形中的某个点上,然后totalNum减1;

              3、随机n次,得到所有随机点。

          这里最关键的是第二步,要实现它需要做到如下两点:

              1、指定数字到(x,y)坐标的映射规则。我采用的是x=point.x+rand%(w+1); y=point.y+Math.floor(rand/(w+1));

              2、设置map,其key值是已经随机出的数字,其value值是最近一次随机出该数字时随机区间的末尾数字,即totalNum-1。我管这种方法叫尾数置换。其图示如下:

          废话不多说了,代码呈上:

    function randomCoords (point, w, h, n) {
        var coords = [];
        var map = {};
        var totalNum = (w + 1) * (h + 1);
        var num = n;
        if (totalNum < n) {
            num = totalNum;
        }
        while (coords.length < num) {
            //在区间[0, totalNum-1]区间里随机
            var randRaw = utils.random_int(0, totalNum - 1);
            //求出有效值并计算出坐标值
            var rand = map[randRaw] === undefined ? randRaw : map[randRaw];
            var x = point.x + rand % (w + 1);
            var y = point.y + Math.floor(rand / (w + 1));
            coords.push({x:x, y:y});
            //更新置换尾数和区间
            totalNum --;
            map[randRaw] = totalNum;
        }
        return coords;
    }

          这个算法的时间复杂度是O(n)级的,远小于前面两种算法。而且它还可以优化,思路前面已经给出来了,就是当要随机的点数超出总点数一半的时候,就随机出不需要的点数,然后剩下的点就是所要求的点了。思路很简单,代码我就不在这里写了。

  • 相关阅读:
    SpringBoot Maven项目 Helloworld 测试
    Oracle client安装教程
    quartz定时任务时间设置
    maven导出项目依赖的jar包
    Java 集合类
    Webservice客户端动态调用服务端功能方法
    使用Eclipse自带的Axis1插件生成Web Service服务端客户端
    SpringBatch Sample (五)(复合格式文件的读、多文件的写)
    Mysql性能分析
    设置nginx中文件上传的大小限制度
  • 原文地址:https://www.cnblogs.com/alwu007/p/6041314.html
Copyright © 2011-2022 走看看