http://www.dewen.io/q/12226

 

在一个平面上有很多加油站(固定点),随机驶入一辆汽车(新的点,且位置随机),

求高效率的方法给出离汽车最近的加油站(点)。当时没答出来,但是面试官说我的

思路是对的(我是想先对加油站点做预处理,就是排序和分类)。大家讨论一下!

这个题我后来发现了一种数据结构:K-D树,很适合解这类问题。
大家可以参看这里:k-d tree算法

 

 

该问题属于计算几何学的一个经典问题。对于一个点集来说,可能存在若干个三角剖分,但是Delaunay三角剖分是唯一的,如下图所示的点集P和其对应的三角剖分(Delaunay图)
点击查看原始尺寸

点击查看原始尺寸
而Delaunay图中的每一个三角形都可以确定一个圆,将这些圆的圆心连接起来就可以构成Delaunay图的对偶图,称为Voronoi图,如下所示:

点击查看原始尺寸

点击查看原始尺寸
如图可见,Voronoi图将P做了一个划分,每个结点p都对应有一个区域,这个区域有个性质:位于该区域内的任何一个点到p的距离一定比P中任何其他点的距离近。
把这些红点理解为加油站,如果平面内任意点出现了一辆汽车,那么它所在Voronoi区域所对应的点就是最近的加油站。
获取Voronoi图最快的算法是Fortune's algorithm,时间复杂度O(nLogn),空间复杂度O(n),n表示P中点的个数。
假设加油站已经固定,则具体的算法不需要动态生成Voronoi图,那么获取最近加油站算法的时间复杂度可以为O(logn),空间复杂度为O(1)。当然,要实现这个复杂度需要一些小技巧,比如对Voronoi图的区域集合建立适合的搜索结构。
在微软游戏“帝国”中,如果占领了一座城市,帝国的疆域会向前推进,我估计这个边界就是用Voronoi图实现的,当然要做个平滑处理。

引用:
http://en.wikipedia.org/wiki/Fortune%27s_algorithm
http://en.wikipedia.org/wiki/Delaunay_triangulation
http://en.wikipedia.org/wiki/Voronoi_diagram

ClaudeHe
编辑于 2013-04-25
评论 (5) • 链接 • 2013-04-25
  • 0
    如何建立搜索结构?二叉树。在图的中间划一刀(当然要顺着Voronoi 的边缘来划分)把整个图一分为二,这样所有的Voronoi区域(称为Cell)会被划分到两个类,每个类实际上是个简单多边形,汽车和最近加油站的问题 就被转换为点和多边形的关系问题,这同样也是个计算几何问题。以此类推不断划分,这个过程就是构建二叉树的过程,而搜索二叉树的时间复杂度为 O(logn)。当然还有判断点和多边形的关系问题,它的复杂度也是O(logn)。这样整体复杂度不变。 – ClaudeHe 2013-04-25
  • 0
    貌似判断点和多边形的关系这个问题的时间复杂度也要乘进去,如果不能在常数内解决,那么总体复杂度就比O(logn)大了。这个还没仔细考虑。 – ClaudeHe 2013-04-25
  • 0
    这个思想挺新颖的,但是这个方法只是对解决这一个问题适用还是对于解决所有相关的问题都可以,我暂时也没有想到“相关的问题”会有哪些 – 放弃天堂的神 2013-10-16
  • 0
    使用Voronoi图解决这一问题的算法复杂度为O(v * Logn),其中v=Voronoi图中具有最大顶点数的Cell的最大顶点数(或边),目前我所知道的判断点与简单多边形的关系算法,最多能达到 O(v)。只要牵扯到判断某个点与某个点集合的关系时(任意维空间),都可以使用该算法。 – ClaudeHe 2013-10-17
显示更多隐藏的评论

我认为是求车 所在点 直线距离最短的 加油站.

预处理

把地图按划成一个一个的正方形, 建立hash (正方形N) => (此正方形里所有的加油站); 不用Hash, 二维数组+linkedlist也可以.

把地图划片, 看加油站的分布, 最理想情况应该是每个方块里有且仅有一个加油站;也可以考虑分级处理, 比如以中国地图为例, 东部肯定加油站密集, 西部肯定稀疏. 我们把中国地图化为几个大块(西部, 西北, 东北, 华东...), 这是一级; 下面各个省再化为一级. 比如西藏, 可能100km100km划一个方块; 北京, 0.5km0.5km划一个方块. 各省维护自己的二维数组+linkedlist. 当然各个省都不会是正方形, 所以只是近似处理.有车的点加进来, 首先根据坐标确认是那一个省, 再向下处理.

这样, 地图就预处理完了

搜索过程

当有车(新的点)进入后, 通过Hash表直接找到车所在的正方形X, 可以知道X中是否有加油站; 如果没有, 把X周围的8个正方形加入搜索加油站, 即X增大9倍; 最终找到有加油站的正方形, 设为Y;

这样我们已经找到了一个可能最近的加油站了,但是, 在Y临近的方块内还可能有更近的加油站.
如下图的25个方块中:
25个方块
从左到右, 从上到下, Y在13的位置, 那么因为车和Y内已找到的加油站最大可能距离为Y的边长*1.42;
在方格2,3,4, 6,7,8,9,10, 11,12,14,15,16,17,18,19,20,22,23,24都可能有距离更近的加油站; 所以我们需要把Y扩大25倍搜索, 确保不会漏掉更近的加油站.

进一步的优化

这样, 基本的处理过程就结束了, 下面讨论一些优化:
1. 25个方块中, 1, 5, 21,25不用计算;
2. 可以首先计算Y中 车与加油站最短距离,如果小于Y的边长,不用扩大25倍,
Y扩大9倍计算就可以;
3. 可以把Y分为四个小方块, 比如车落在左上角的方块, 则扩大二十五倍后,
4,5,9,10,15,16,19,20,22,23,24不用处理.

brayden
编辑于 2013-10-15
评论 (5) • 链接 • 2013-04-14
  • 0
    如果地图是几万km*几万km只有几个加油站怎么办,格子应该是自适应的吧 – 灵剑2012 2013-04-15
  • 0
    @灵剑2012 : 你说的对. 我早上在 "答案补充"里加了: "把地图划片, 看加油站的分布, 最理想情况应该是每个方块里有且仅有一个加油站" – brayden 2013-04-15
  • 0
    @brayden 你的算法严重需要优化,假设以中国地图为例,如果汽车出现在一个人迹罕至的沙漠中(周围啥也没有)咋办?所以个人以为分格子应该从粗粒度分起 1000KM 100KM 10KM 1KM 。。。 – hightower 2013-04-16
  • 0
    @hightower 呵呵, 有道理. 我想想, 再更新一下 – brayden 2013-04-16
显示更多隐藏的评论

地图上所有的交叉路口都可以求出离哪一个加油站最近。如果一条 路(中间没有其他分叉口,也就是无向图中的一条边)的两个端点最近的加油站相同,则整条路上所有的点都是到这个加油站距离最近;否则存在一个分界点,使得 分界点到两个加油站的距离相等,分界点位置可以通过两个端点到各自的最近加油站的距离求出。从分界点将路分成两部分,则两部分各自的最近加油站与自己一边 端点的最近加油站相同。这样可以将所有的道路按照最近的加油站分类,最后只要根据汽车所在道路就可以知道最近的加油站在哪里。

求所有交叉路口的最近加油站可以通过增加一个虚拟节点,该节点到所有加油站有边且距离为0,然后求所有路口到该虚拟节点的最短路径,可以运用dijkstra算法,复杂度为O(V^2)或者O(ElogV)。

如果只是求欧式距离最短的话,对每个点A,其他点和这个点的中垂线会围成一个凸多边形,在这个多边形内的所有点都是到点A的距离最 近。每个凸多边形可以先用一个包含凸多边形在内的矩形(长和宽平行于坐标轴)来近似,矩形根据左上角、右下角坐标记为(x1k,y1k)- (x2k,y2k),横坐标范围和纵坐标范围各自为(x1k,x2k), (y1k,y2k),把所有的矩形按照横坐标范围和纵坐标范围分别排序。对新给定的点,只需要进行二分查找就可以找到这个点可能的最近点是哪些,然后分别 计算距离,找出实际最近的一个即可。

评论 (0) • 链接 • 2013-04-14

四叉排序树Tree

节点P包含四个子节点GL,GG,LG,LL

GL:子节点x坐标大于父节点x坐标,但y坐标小
LL:子节点x,y坐标均小于父节点

typedef struct quad_node_s {
struct quad_node_s *gg;
struct quad_node_s *gl;
struct quad_node_s *ll;
struct quad_node_s *lg;
int x;
int y;
} quad_node_t, quad_tree_t;

利用已知的加油站数据可建立一个这样的四叉树,只需要做特殊的搜索即可

y
^
|
|

|

|LG | GG |
----P-------------------> x

|LL | GL |

|
|
|

如上图,P点将平面划分为4块,每一步搜索都将区域缩小4倍,搜索是lg(n)复杂度的
搜索结束的条件:
1. 刚好找到对应坐标的点
2. 没有找到对应条件的点,只需要记录最后的两个节点即可,比较这两个点哪个最近
3. 设待搜索的点p坐标为x,y,考虑(x+1,y),(x-1,y),(x,y+1),(x,y-1)四个点?

// 搜索距离最近点
void quad_tree_min_distance_search(quad_tree_t *r, int x, int Y)
{
}

评论 (0) • 链接 • 2013-04-24

如果不考虑路径的话,可以 用KNN算法,先对所有加油站的坐标,建立kd树,然后找到汽车所在位置的最近邻居

评论 (2) • 链接 • 2013-04-19
  • 0
    我一直觉得这是一个比较经典的问题, 应该有比较成熟的算法. 可惜不是搞算法的, 不了解.
    楼主能否给出一些伪码来描述? 多谢! – brayden 2013-04-19
  • 0
    找到了: http://grunt1223.iteye.com/blog/921371
    已知1000个Point(包含X与Y坐标)结构的数组,给定一个Point,要求查找数组中与之最接近(比如:欧氏距离最短)的点。 – brayden 2013-04-19

为点添加属性, 例如:
原始数据:xxx加油站.
处理后数据:亚洲->中国->江苏省->南京市->玄武区->xxx加油站.
算法中只需逐级确定汽车所在地区, 最后计算最小一级区域及相邻区域(可能汽车在边界处)中的加油站到汽车的距离即可.
这就是用数据换性能.
如果这些加油站每次都重新生成, 那就只能每个点依次计算距离, 因为预处理消耗的时间肯定比直接计算距离多.

评论 (5) • 链接 • 2013-04-19
  • 0
    @brayden 假设地图是一个矩形, 把这个矩形再分解为4个小矩形(第一层), 这4个小矩形每个还可以再分解为4个更小的矩形(第二层), 以此类推. 先在第一层判断, 最多要判断3次, 最少只需判断1次;进入第二层, 以此类推. 假设有N层, 最坏情况下需要判断 3*N 次. 根据矩形左下角的坐标和矩形的长和宽可以判断点是否在矩形内. – hccloud 2013-04-19
  • 0
    @brayden 貌似忘了最底层没有加油站的情况, 如果出现这种情况, 那就要你在一楼写的那样向外拓展了. 如果数据做的比较大的话, 可能载入读取数据的时候会慢点, 要在数据和性能之间平衡下. – hccloud 2013-04-19
  • 0
    @hccloud 呵呵, 如果是矩形的话, 你需要考虑的就多了, 你看看我的答案里, 最后一步扩大25倍.
    因为如果车落在靠此矩形边界的话, 那么临近矩形里的加油站很可能更近! – brayden 2013-04-19
  • 0
    @brayden 嗯, 楼主的这个问题往深处思考确实可以好好研究一番. 可以把临近矩形中的部分点归入这个矩形. 如果矩形中一个点都没有的话, 也可以把附近矩形中的点归入这个矩形. 就像问路一样, 虽然这个矩形没有加油站, 但是你可以知道离矩形地方最近的加油站有哪些. – hccloud 2013-04-19
显示更多隐藏的评论
  • 社区维基

    0 票

  • 尹泩
    16
  •  

高等数学里的权图最短路径

评论 (0) • 链接 • 2013-04-13

同意@dewnet的 思路,最先考虑会是用圆处理。这个问题可以理解为地图上已有n个点,然后再加入1个点,找离这个点最近的点。考虑到是实际的加油站问题。我在想是否可以采 用近似处理。先处理有n个点的地图,取1km为最小单位。用二维数组来存储这个地图,确定原点,然后将加油站就近原则处理成整数下标,如在x,y附近有一 加油站,附近取实际坐标的向下取整或向上取整(取一个),置(x,y)为1.处理完地图后,新加入的点,同样就近处理为(m,n),然后慢慢试探 (m+k,n),(m,n+k),(m+k,n+k),(m-k,n),(m,n-k),(m-k,n-k)是否有加油站,k由1步长1递增。试探即判断 是否为1。这样近似处理,问题简单很多,然后也符合实际。
1km为最小单位,2g内存电脑能处理1024*1024(km)2的地图。
请大家指教。

评论 (1) • 链接 • 2013-04-18
  • 0
    还不错. 几点意见:
    1. 精度的问题, 在北京城区还好, 如果是比较偏远的地区, 几十平方公里内才一个加油站的, 误差会很大;
    2. 此算法最终需要给出最近的加油站的 具体位置坐标. 而现在你的算法只能 大致知道最近的加油站的范围. – brayden 2013-04-19
  • 社区维基

    0 票

  • 灵均
    21
  •  

我对这个题目的理解是:
1 对地形没有限制,也就是说只需要求最短距离;
2 在1的基础上,也就是说这个题目的关键就是体现在 高效率 上;

关键就在如何确定最小选择域了;感觉也只能通过启发式方法来探索和逼近啊~

评论 (0) • 链接 • 2013-04-18

这题目,又是面试游戏开发岗位,就是想让你回答A-star吧...如果真的强调很多加油站很多路啥的,就回答个贪婪算法嘛

评论 (0) • 链接 • 2013-04-17

我是这样想的,不知道编程复杂不:
先取一个加油站为原点(0,0)建立一个平面坐标轴,这样就可以让所有的加油站有一个固定的坐标(x,y),新加的汽车坐标(a,b)。
以(a,b)为圆心,汽车(a,b)到原点(0,0)的距离为半径作圆,计算在圆以内的所有加油站到汽车的距离。

评论 (4) • 链接 • 2013-04-17
  • 0
    我得支持他!!! – 风飘无痕 2013-04-17
  • 0
    如果是没有路限制的话,只有求距离,我也同意你这个! – 灵均 2013-04-18
  • 0
    怎么知道哪些加油站在圆内,不还是得把所有的距离都算一遍吗…… – 灵剑2012 2013-04-18
  • 0
    最简单的计算方式, 是遍历所有的加油站在的点, 时间复杂度O(N).
    你的方法仍然是O(N)的, 不过平均情况下降低了系数. – brayden 2013-04-19

Voronoi diagram的内容,先构建加油站的V图,然后判断车在图中的哪个区域
wiki Voronoi diagram

评论 (0) • 链接 • 2013-10-16
posted on 2014-12-02 15:38  风云逸  阅读(183)  评论(0)    收藏  举报