首先请注意,对于一般矩阵,输出可能由多个封闭回路组成;例如矩阵的边界可以形成三个不同的回路,其中一个回路位于另一个回路内部。
要提取这些回路,第一步是构建所有“墙壁”的地图:每当一个单元格的内容与同一行中下一个单元格的内容不同时,就会出现垂直墙壁;相反,当该内容与下一行中同一单元格的内容不同时,就会出现水平墙壁。
data = [[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 1, 1, 1, 1, 0, 0, 0, 0, 0 ],
[ 0, 1, 0, 0, 1, 0, 1, 1, 0, 0 ],
[ 0, 1, 0, 0, 1, 0, 1, 1, 1, 0 ],
[ 0, 1, 1, 1, 1, 0, 0, 1, 1, 0 ],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]]
rows = len(data)
cols = len(data[0])
walls = [[2*(data[r][c] != data[r][c+1]) + (data[r][c] != data[r+1][c])
for c in range(cols-1)]
for r in range(rows-1)]
在上面的示例中,我使用了两个位:
0x01
用于标记水平墙壁,
0x02
用于标记垂直墙壁。对于给定的单元格
(r,c)
,墙是该单元格的右侧和底部墙壁。
为了简化起见,我还假设有趣的区域没有接触矩阵的限制;这可以通过添加额外的零行和列或通过在返回虚拟元素的矩阵访问函数中包装来解决。
要构建边界列表,只需从墙上的任意点开始移动,并按照墙壁的方向移动,处理它们时从地图中删除墙壁。当您无法再移动时,已完成一个循环(因为在从内/外标志矩阵构建的图中,所有顶点的度数保证是偶数,所以您保证会完成循环)。
同时使用奇偶填充规则填充所有这些周期也保证能够复制原始矩阵。
在以下代码中,我使用
r
和
c
作为行/列索引,而使用
i
和
j
来表示边界上的点...例如对于单元格
(r=3, c=2)
,模式如下:
其中红色墙对应于位
0x02
,绿色墙对应于位
0x01
。
walls
矩阵比原始数据矩阵少一行和一列,因为假定最后一行或列上不可能出现墙壁。
result = []
for r in range(rows-1):
for c in range(cols-1):
if walls[r][c] & 1:
i, j = r+1, c
cycle = [(i, j)]
while True:
if i < rows-1 and walls[i][j-1] & 2:
ii, jj = i+1, j
walls[i][j-1] -= 2
elif i > 0 and walls[i-1][j-1] & 2:
ii, jj = i-1, j
walls[i-1][j-1] -= 2
elif j < cols-1 and walls[i-1][j] & 1:
ii, jj = i, j+1
walls[i-1][j] -= 1
elif j > 0 and walls[i-1][j-1] & 1:
ii, jj = i, j-1
walls[i-1][j-1] -= 1
else:
break
i, j = ii, jj
cycle.append((ii, jj))
result.append(cycle)
基本上,代码从边界的一个点开始,检查是否可以沿着墙壁向上、向下、向左或向右移动。当无法再移动时,一个循环已经完成,可以添加到最终结果中。
该算法的复杂度为O(rows*cols),即与输入大小成比例,并且它是最优的(在大O意义下),因为您不能在不读取输入的情况下计算结果。这很容易理解,因为while循环的主体不能进入比地图中的墙壁总数更多的次数(每次迭代都会删除一堵墙)。
编辑:
该算法可以修改为仅生成简单循环作为输出(即只访问每个顶点一次的路径)。
result = []
index = [[-1] * cols for x in range(rows)]
for r in range(rows-1):
for c in range(cols-1):
if walls[r][c] & 1:
i, j = r+1, c
cycle = [(i, j)]
index[i][j] = 0
while True:
if i > 0 and walls[i-1][j-1] & 2:
ii, jj = i-1, j
walls[i-1][j-1] -= 2
elif j > 0 and walls[i-1][j-1] & 1:
ii, jj = i, j-1
walls[i-1][j-1] -= 1
elif i < rows-1 and walls[i][j-1] & 2:
ii, jj = i+1, j
walls[i][j-1] -= 2
elif j < cols-1 and walls[i-1][j] & 1:
ii, jj = i, j+1
walls[i-1][j] -= 1
else:
break
i, j = ii, jj
cycle.append((ii, jj))
ix = index[i][j]
if ix >= 0:
result.append(cycle[ix:])
for i_, j_ in cycle[ix:]:
index[i_][j_] = -1
cycle = cycle[:ix+1]
index[i][j] = len(cycle)-1
在处理过程中,如果遇到相同的顶点两次,则通过添加单独的循环来实现此操作(index
表存储给定i,j点在当前构建的循环中的基于0的索引)。