在矩形范围内种怪的算法问题

今天在工作中遇到这样一个问题:给定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)级的,远小于前面两种算法。而且它还可以优化,思路前面已经给出来了,就是当要随机的点数超出总点数一半的时候,就随机出不需要的点数,然后剩下的点就是所要求的点了。思路很简单,代码我就不在这里写了。

时间: 2024-08-07 15:30:34

在矩形范围内种怪的算法问题的相关文章

四舍五入的一些简单写法(利用内置函数,算法2种写法)

?       //内置函数的写法        //网上零售价和折扣价在计算结束需要进行进位,规则如下:         //个位为1,2,3,4进位到5,例如计算后的价格为1201,则价格为1205:         //个位为6,7,8,9进位到0,例如计算后的价格为1209.则价格为1210:         public static string ChangePrice(double price)         {             int changed = 0;     

一种新型聚类算法(Clustering by fast search and find of density peaksd)

最近在学习论文的时候发现了在science上发表的关于新型的基于密度的聚类算法 Kmean算法有很多不足的地方,比如k值的确定,初始结点选择,而且还不能检测费球面类别的数据分布,对于第二个问题,提出了Kmean++,而其他不足还没有解决,dbscan虽然可以对任意形状分布的进行聚类,但是必须指定一个密度阈值,从而去除低于此密度阈值的噪音点,这篇文章解决了这些不足. 本文提出的聚类算法的核心思想在于,对聚类中心的刻画上,而且认为聚类中心同时具有以下两种特点: 本身的密度大,即它被密度均不超过它的邻

发表在 Science 上的一种新聚类算法

今年 6 月份,Alex Rodriguez 和 Alessandro Laio 在 Science 上发表了一篇名为<Clustering by fast search and find of density peaks>的文章,为聚类算法的设计提供了一种新的思路.虽然文章出来后遭到了众多读者的质疑,但整体而言,新聚类算法的基本思想很新颖,且简单明快,值得学习.这个新聚类算法的核心思想在于对聚类中心的刻画上,本文将对该算法的原理进行详细介绍,并对其中的若干细节展开讨论. 最后,附上作者在补充

七种常用排序算法

七种常用排序算法 一.常见排序算法一览: 时间复杂度: 是一个函数,它定量描述了该算法的运行时间. 空间复杂度:一个算法在运行过程中临时占用存储空间大小的量度. 稳定性:保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同就稳定,反之不稳定. 视觉直观感受 7 种常用的排序算法 二.算法C#实现: 1. 直接插入排序: using System; using System.Collections.Generic; using System.Linq; using Sys

矩形旋转碰撞,OBB方向包围盒算法实现

如何进行2D旋转矩形的碰撞检测,可以使用一种叫OBB的检测算法(Oriented bounding box)方向包围盒.这个算法是基于SAT(Separating Axis Theorem)分离轴定律的.而OBB不仅仅是计算矩形的碰撞检测,而是一种算法模型.简单解释一下概念,包围盒和分离轴定律. 包围盒:是根据物体的集合形状,来决定盒子的大小和方向,这样可以选择最紧凑的盒子来代表物体.见下图 黑色的就是包围盒,可以是凸多边形,最贴近检测物体即可. 分离轴定律:两个凸多边形物体,如果我们能找到一个

五种常用的算法设计技巧之二:分治算法

一,介绍 分治算法主要包含两个步骤:分.治.分,就是递归地将原问题分解成小问题:治则是:在解决了各个小问题之后(各个击破之后)合并小问题的解,从而得到整个问题的解 二,分治递归表达式 分治算法一般都可以写出一个递归表达式:比如经典的归并排序的递归表达式:T(N)=2T(N/2)+O(N) T(N)代表整个原问题,采用了分治解决方案后,它可以表示成: ①分解成了两个规模只有原来一半(N/2)的子问题:T(N/2) ②当解决完这两个子问题T(N/2)之后,再合并这两个子问题需要的代价是 O(N) 递

机器学习几种常见优化算法介绍

机器学习几种常见优化算法介绍 https://blog.csdn.net/class_brick/article/details/78949145 1. 梯度下降法(Gradient Descent) 2. 牛顿法和拟牛顿法(Newton's method & Quasi-Newton Methods) 3. 共轭梯度法(Conjugate Gradient) 4. 启发式优化方法 5. 解决约束优化问题--拉格朗日乘数法 我们每个人都会在我们的生活或者工作中遇到各种各样的最优化问题,比如每个企

四种迷宫生成算法

简介 所谓迷宫生成算法,就是用以生成随机的迷宫的算法 迷宫生成算法是处于这样一个场景: 一个row行,col列的网格地图,一开始默认所有网格四周的墙是封闭的 要求在网格地图边缘,也就是网格的边上打通2面墙 所有网格都至少保证网格周围至少有一堵墙打通 所有网格都能通过打通的墙能形成一条通路 博主已实现RecursiveBacktracking(递归回溯),RecursiveSegmentation(递归分割),随机Prim算法,Kruskal+并查集四种迷宫生成算法,这篇文章主要对这四种算法进行简

转:MD5(Message-Digest Algorithm 一种哈希算法)

什么是MD5算法 MD5讯息摘要演算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码杂凑函数,可以产生出一个128位元(16位元组)的散列值(hash value),用于确保信息传输完整一致. 实质上,MD5 只是一种哈希算法 哈希算法,即 hash,又叫散列算法,是一类把任意数据转换为定长(或限制长度)数据的算法统称.例如我叫张三,你叫李四,那么「人 -> 人名」的算法就叫属于一种哈希算法.哈希算法通常用于制作数字指纹,数字指纹的意思就是「你看到这个东