优化二维网格连接算法

时间:2017-04-01 20:34:03

标签: arrays algorithm search graph kruskals-algorithm

摘要:我正在寻找一种最佳算法,以确保二进制值的二维网格连接。我有一个相当复杂的算法,它可以在有效的线性时间内完成,但只有在执行某些预处理步骤时才能实现。以下内容详细介绍了算法及其运行时间。我还整理了一个Unity应用程序,它提供了下面提到的所有步骤(以及其他一些步骤)的详细可视化,可以找到here

我有一组脚本,使用称为行进方块的算法在程序上生成地形。其中一个步骤是将所有区域连接在一起。具体来说,我有一个0(地板)和1(墙)的网格,并希望确保每0个都可以达到0。我优化了:

  • 需要完成的隧道挖掘量。即,应将最小化为1的1的数量。
  • 渐近运行时间。我试图使网格中的图块数量成线性,或者尽可能接近线性。

通过将房间(0s的连通区域)视为顶点和潜在隧道作为边缘,我们可以使用最小生成树算法作为我们的主力。我将从0和1的未连接网格的起点描述算法。

输入:

一个2d字节数组,0或1,表示地形(0:floor,1:wall)。

e.g。以下有四个房间' (连接的地板组件)。

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 1 1 0 0 0 1 1 
1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 1 1 0 0 1 1 1 1 
1 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 
1 1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 
1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 0 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 
1 1 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 
1 1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 
1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 

输出

相同的网格,这样每个房间都可以从任何其他房间到达 对网格造成的损害最小(最少1s翻到0)。在这里,我们共雕刻了三条隧道:

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 1 1 0 0 0 1 1 
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 1 1 1 
1 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 
1 1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 
1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 
1 1 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 
1 1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 
1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 

基本算法概述:

以下是该算法的高级描述,没有几个关键的优化,以说明高层次的想法:

  1. 在网格上运行BFS以查找房间(楼层的连接组件),仅存储边缘瓷砖(即与墙砖相邻的地砖),因为两个房间之间的最短路径将始终位于两个边缘瓷砖之间。

  2. 对于每对房间,进行双循环以找到欧氏距离最短的一对瓷砖。这对通过挖掘它们之间的直线路径在两个房间之间形成一条潜在的隧道。

  3. 将(1)中的房间视为顶点,将(2)中的对视为图中的边,权重为欧氏距离。在此图上运行Kruskal的最小生成树算法,以获取要挖掘的隧道列表,以最小化需要更改的切片数量。

  4. 这保证了连接性,并且它保证了更改的瓦片的绝对最小数量(警告:如果我们考虑将房间连接到另一个房间而不是另一个房间之间的隧道的可能性,则这是错误的)。问题是它的扩展性很差:步骤(2)在网格中的瓦片数量上按比例缩放。

    优化算法

    瓶颈在于步骤(2)。我们会仔细检查每对房间的每一对瓷砖,以确保我们获得绝对最小的连接。如果我们接受一点点错误(即次优连接),我们可以大大加快速度。基本的想法是跳过与我们刚刚计算的距离成比例的多个瓦片:如果我们计算瓦片A和瓦片B之间的大距离,那么我们可能无法接近最佳连接,所以我们可以跳过检查附近的瓷砖。此跳过的任何错误都将与最佳连接的长度成比例。

    为了在视觉上解释这一点,假设X和Y表示正在检查的当前一对图块,并且我们当前正在左边的房间上循环X.

    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
    1 X 0 0 0 0 0 0 1 1 1 1 1 1 1 
    1 1 0 0 0 0 0 0 0 1 1 1 1 1 1
    1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 
    1 0 0 0 0 0 0 1 1 1 1 0 0 0 1 
    1 1 0 0 0 0 1 1 0 0 0 0 0 1 1 
    1 1 1 1 1 1 1 0 0 0 0 Y 1 1 1 
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
    

    这些距离为11,所以让我们跳过11个图块(用破折号标记):

    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
    1 0 - - - - - - 1 1 1 1 1 1 1 
    1 1 0 0 0 0 0 - - 1 1 1 1 1 1
    1 1 0 0 0 0 - - 1 1 1 1 1 1 1 
    1 0 0 0 0 X - 1 1 1 1 0 0 0 1 
    1 1 0 0 0 0 1 1 0 0 0 0 0 1 1 
    1 1 1 1 1 1 1 0 0 0 0 Y 1 1 1 
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
    

    大多数比较都相距甚远,因此这大大缩短了第2步的运行时间,并且在实践中产生的误差最小。

    这里忽略的一个问题是,这假设边缘图块是有序的:这需要额外的预处理步骤。

    因此,这是优化的算法:

    1. 按照上一算法执行BFS。

    2. 对每个房间进行排序。这可以通过沿边缘区块执行深度优先来完成。这将给我们沿着房间边缘的大致连续路径。有一些(字面)拐角情况,路径可以跳跃,但路径在合理的近似内是连续的。

    3. 在前一算法的步骤2中执行双循环,但这次不是按一个图块递增,而是按最后计算的距离递增。

    4. 像以前一样执行Kruskal的算法。请注意,Kruskal要求我们对图中的边进行排序:由于图完成(每对房间都有一个潜在的隧道),标准排序成为算法中的新瓶颈。由于我们按距离排序,这是一个浮点数,我们可以通过截断浮点数并将它们转换为整数来实现更快的排序。同样,这会产生最小的误差(例如,Kruskal可能选择距离为4.6的隧道,距离为4.2的隧道),但提供了显着的加速。

    5. 运行时分析

      设n表示网格中的tile数。设m表示网格中的房间数(连通分量)。注意,通常,在最坏的情况下,m = O(n)。

      算法分为四个步骤。 (1)和(2)在内存和时间都是O(n),因为它们是一个BFS和DFS,它最多处理一次地图中的每个图块。

      (3)有点棘手。我们在每个房间进行双循环,然后找到连接。对于双循环,这是O(m ^ 2),乘以每对房间完成的平均工作量。对每对房间平均工作进行严格的分析限制并不是一件简单的事情。但凭经验,对各种配置进行测试,随着n变大,平均工作会收敛到单一的比较。这是因为房间之间的平铺距离随着网格的增长而增长。

      因此,总共为(3)完成的工作是O(m ^ 2),存储O(1)。

      对于(4),由Kruskal的运行时给出,它是O(m ^ 2 logm ^ 2),以天真地排序边缘和O(m ^ 2 a(m ^ 2))通过UnionFind数据结构运行边缘,其中a是反ackermann函数(实际上是常数)。如果我们截断边长并使用整数排序算法,我们可以将排序降低到O(m ^ 2)。储存量为O(平方公尺)。

      总而言之,运行时由Kruskal算法支配,由O(m ^ 2 a(m ^ 2))或有效地O(m ^ 2)给出。鉴于在最坏的情况下m = O(n),这不是非常好的性能。但是我们可以在网格上做一个最终的预处理步骤,将其降低到O(n),即以有机方式限制房间数量。

      在执行任何其他步骤之前,我们可以使用填充算法以线性时间填充小房间:具体来说,我们可以填写任何大小小于sqrt(n)的房间。由于网格中最多只有sqrt(n)个大小至少为sqrt(n)的房间,因此m = O(sqrt(n)),使得整个算法在网格的大小上呈线性。

      结论

      有可能比这更好吗?显然,我们不能渐近地比线性时间和存储更好,但是为了实现这些数字,隧道长度中的一定数量的粗略量化的次优性被接受,并且它需要修改原始网格(即,限制数量)房间)。

0 个答案:

没有答案