根据一个坐标查找其所属区域的一些优化想法

今日下午,听到同事讨论一个问题感觉十分有趣。晚上下班回家后,又想起了这个问题,心里稍微有一些想法,遂将其记录下来。

这个问题大致如下:

假设有一个规则的矩形地图,其大小为n*m(即由n*m个格子组成)。在这地图上,又有k个区域,其中每个区域里格子是连续的,并且同一个格子仅会唯一的属于一个区域(每一个格子需要记录所属区域)。

问:是否有方法,能够使用尽可能少的内存,并且还能较为快速的根据一个给定格子的xy坐标,找出格子所属的对应区域?

需要说明的是,地图上的各区域的形状未定,因此实现上不能对其有任何假设。

 

如果不考虑内存,那么直观的方法就是:使用一个n*m的二维数组A,其中A[i][j]记录的是格子grid(x=i,y=j)所属的区域id。这种方法最简单也最高效,仅通过两次计算偏移值就能计算得出结果。

但是为了优化内存,还是不得不考虑其它的一些方法。本着一个指导思想是:在给定的特殊的条件(假设)下,通常都会有针对该条件的优化方法。而对于这个问题来说,这个特殊条件(特殊假设)就是在同一个区域里,格子一般是连续的!

如同,连续的整数在一定情况下是可以被压缩存储的。比如列表l=[1,4,7,8,9,10,11,12,17],可以被压缩成[1,4,(7,12),17]。

 

假设地图M上有三个区域A、B、C,各个区域分布如下:

/* 地图M:
 +---+---+---+---+---+---+
 | A | A | A | A | C | C |
 +---+---+---+---+---+---+
 | A | B | B | B | C | C |
 +---+---+---+---+---+---+
 | A | A | B | B | B | C |
 +---+---+---+---+---+---+
 | A | A | B | B | C | C |
 +---+---+---+---+---+---+
 | A | A | A | B | C | C |
 +---+---+---+---+---+---+
*/

 对地图上的每个格子编号之后,可以得到M中各区域拥有的格子数据如下:

/*
区域A: la=[0, 1, 2, 3, 6, 12, 13, 18, 19, 24, 25, 26]
区域B: lb=[7, 8, 9, 14, 15, 16, 20, 21, 27]
区域C: lc=[4, 5, 10, 11, 17, 22, 23, 28, 29]
*/

对上述格子编号列表进行压缩,得到的结果是:

/*
区域A: la=[(0, 3), 6, (12, 13), (18, 19), (24, 26)]
区域B: lb=[(7, 9), (14, 16), (20, 21), 27]
区域C: lc=[(4, 5), (10, 11), 17, (22, 23), (28, 29)]
*/

如果要查找一个格子g是否属于区域A,可以对列表区域A的所属格子列表la,执行一个稍微变种的二分查找算法。当然前提是需要列表la是已排序的!二分查找算法十分高效,倘若列表la有1500个元素,执行二分查找只需要比较11次即可。

因此算法基本描述是这样的:

对于给定的地图M,以及区域K,先按照上述方法生成每个区域所属的格子列表,并对其先压缩再排序,得到列表lk。当需要查找格子g所属的区域时,遍历所有区域的压缩排序后的列表lk,查找该格子是否在其中。

 

不过,算法还是可以被继续优化的!

首先想到的一点就是,为什么一次查找操作需要遍历所有区域的列表呢?我何不先大致的猜测出这个格子可能存在于哪几个区域,然后只管遍历它们就好了。其它的区域完全可以被忽略,从而节省不需要的遍历操作。

这当然是可以的。不过需要稍微多存一些辅助数据!

方法就是,对于一个不规则的区域k,可以用一个完全包含k中所有格子的规则区域k1来作为粗略的估算值。如下所示:

/* 区域K真实值:
 +---+---+---+---+---+---+
 |   |   |   |   |   |   |
 +---+---+---+---+---+---+
 |   | K | K | K |   |   |
 +---+---+---+---+---+---+
 |   |   | K | K | K |   |
 +---+---+---+---+---+---+
 |   |   | K | K |   |   |
 +---+---+---+---+---+---+
 |   |   |   | K |   |   |
 +---+---+---+---+---+---+
 
 区域K粗略的估算值:
 +---+---+---+---+---+---+
 |   |   |   |   |   |   |
 +---+---+---+---+---+---+
 |   | K | K | K | * |   |
 +---+---+---+---+---+---+
 |   | * | K | K | K |   |
 +---+---+---+---+---+---+
 |   | * | K | K | * |   |
 +---+---+---+---+---+---+
 |   | * | * | K | * |   |
 +---+---+---+---+---+---+
 */

对于区域K,取其横纵坐标的最大值来估算得到K1。存储K1只需要一个四元组即可(即存的是矩形的两个对角坐标),上图所示即为K1=(1, 1, 4, 4)。

这么做的好处是:能够十分快速的计算出一个格子是否位于其中(判断格子g的坐标是否在矩形K1的两个对角坐标中间即可)。

 

如此一来,对于地图M,我们便可以计算出M中各区域的估算值:

/*
地图M中各区域的估算字典D: 
D = { A:(0, 0, 3, 4), B:(1, 1, 4 ,4), C:(4, 0, 5, 4)}
*/

 

因此,进一步的被优化的查找算法描述如下:

当需要查找格子g所属的区域时,首先遍历区域的粗略估算值,得出格子g可能的所属区域集合。然后才是遍历这些可能的区域的压缩排序后的列表lk,查找该格子是否在其中。

 

更复杂的优化:

比如一个连续的列表可以被优化成只存首尾元素,类似的一个矩形区域也可以只存对角坐标。基于这个思想或许能再进一步优化内存,或是查找效率!

posted @ 2018-01-17 01:46  adinosaur  阅读(473)  评论(0编辑  收藏  举报