生成由 10 个正交连接点形成的所有可能形状的最有效方法是什么?

What is the most efficient way to generate all possible shapes formed by 10 orthogonally connected points?

我需要在 2D space 中生成 10 个点的所有有效组合。

在这种情况下,有效的形状意味着:

  1. 所有点都相互连接,不允许对角线连接。
  2. 如果我们围绕点绘制外边界,则形状内部不应有空点。

以下是一些有效和无效组合的示例:

我尝试了几种算法,但它们都效率低下并且需要太多时间来计算。生成和存储所有有效组合的最有效方法是什么?

出于衍生式设计目的,我需要它。组合代表建筑平面图中房间的形状。 首选语言是 Python.

我不确定你想做什么,但我认为你可以通过以下方式生成所有由 10 个点组成的形状:

  • 从任意一个像素开始,标记它并尝试在四个主要方向上移动;

  • 在下一个像素处,标记它并尝试在四个主要方向上移动,前提是像素未标记;

  • 继续递归,直到达到允许的像素数。如果你到达了初始像素的邻居,你就有了一个有效的形状;

  • 当递归回溯时,取消标记您要离开的像素。

如果你不想复制90°旋转对称的副本,只在一个方向移动第一个就足够了。

作为一个可能有用的优化,您可以通过避免移动使得 return 不可能(曼哈顿距离太大)来避免远离初始像素的路径。

下面的代码打印了通过正交连接 n 个点可以构成的所有可能结构。对于您的用例来说,它非常有效,对于 n=10,它在我的笔记本电脑上运行了大约 4 秒,并进行了有效性检查(房间内没有间隙)。基于 n=8、9、10、11 的 运行 时间,似乎时间复杂度为 O(4^n),这在给定四个基本方向的情况下是有道理的。

想法是先放置一个点,然后反复考虑我们可以放置点的位置,以便仍然满足任何约束。在每一步,我们都会考虑结构中之前没有考虑过的任何点的外边界,并考虑用我们剩下的任意数量的点填充这些空间的所有可能组合。

房间内没有缝隙的约束通过深度优先搜索验证,该搜索来自刚选择的点旁边的空单元格。如果在 DFS 期间的任何时候它到达墙壁或已经走得足够远,那么它是有效的。

这是代码(yield fromf-string 需要 Python 3.6+):

# -*- coding: utf-8 -*-
"""
02 Aug 2019
Generate all possible floorplans.

"""

# Import statements
from __future__ import print_function, division
import sys
from argparse import ArgumentParser
from pprint import pprint
from itertools import combinations

def neighbors(row, col):
    """Generate all neighbors of a point"""
    result = []
    for dir_row, dir_col in [(-1, 0), (0, -1), (0, 1), (1, 0)]:
        new_row, new_col = row+dir_row, col+dir_col
        result.append((new_row, new_col))
    return result

def generate_structures(n):
    """Main generator to generate structures that can be formed from n orthogonally connected
    points."""
    # Setup the grid
    # The first point is assumed to be the left-most point, and among the left-most,
    # it is the top-most. This prevents duplicate structures, since all structures have a
    # unique left-most top-most point.
    grid = [['.']*n for _ in range(2*n-2)]
    for r in range(len(grid)):
        grid[r].append('X')
        grid[r].insert(0, 'X')
    grid.append(['X']*(n+2))
    grid.insert(0, ['X']*(n+2))
    row, col = n-1, 1
    for r in range(row):
        grid[r][1] = 'X'
    grid[row][col] = 'O'
    yield from _generate_structures(grid, n-1, [(row, col)], (row, col), n-1)

def _generate_structures(grid, n, last_points, source, max_dist):
    """Recursive function to generate all possible structures with n points remaining,
    where last_points are the points last added.

    The source and max_dist are used in validation check, to make it slightly faster.
    """
    if n == 0:
        # No more points to be placed, return the current structure
        yield grid
        raise StopIteration
    def get_expansion(points, charset='.'):
        """Get all points around selected points, with the given character set."""
        expansion = set()
        for row, col in points:
            if grid[row][col] != 'O':
                # Only expand from points
                continue
            for new_row, new_col in neighbors(row, col):
                if grid[new_row][new_col] not in charset:
                    continue
                expansion.add((new_row, new_col))
        expansion = list(sorted(expansion))
        return expansion
    expansion = get_expansion(last_points)
    # For all possible number of points that can be assigned to the boundary
    # of the last added points (1 to n)
    for i in range(1, n+1):
        # Consider all the possible combinations (number of empty space choose
        # number of points)
        for combination_indices in combinations(range(len(expansion)), i):
            includeds = [False]*len(expansion)
            included_points = []
            for included_index in combination_indices:
                includeds[included_index] = True
            for included, (row, col) in zip(includeds, expansion):
                if included:
                    grid[row][col] = 'O'
                    included_points.append((row, col))
                else:
                    grid[row][col] = 'X'
            to_be_checked = get_expansion(included_points, '.X')
            if valid(grid, to_be_checked, source, max_dist-n+1):
                # If valid (there are no isolated empty cells, for example),
                # then continue expanding the boundary.
                yield from _generate_structures(grid, n-len(included_points), included_points,
                                                source, max_dist)
            for row, col in expansion:
                grid[row][col] = '.'

def valid(grid, points, source, max_dist):
    """Check the points given, whether they are isolated.

    The source and max_dist helps in early termination of the check, by checking whether
    we have been far enough that it is impossible for walls to form.
    """
    def distance(point):
        return abs(point[0]-source[0]) + abs(point[1]-source[1])
    if len(points) == 0:
        return True
    for check_point in points:
        stacked = set()
        stack = []
        if check_point in stacked:
            continue
        stacked.add(check_point)
        stack.append(check_point)
        current_is_valid = False
        while len(stack) > 0:
            row, col = stack.pop()
            if (row == 0 or col == 0 or row == len(grid)-1 or col == len(grid[0])-1 or
                    distance((row, col)) >= max_dist):
                # From this check_point it is valid, check other points
                current_is_valid = True
                break
            cur_neighbors = []
            for new_row, new_col in neighbors(row, col):
                if grid[new_row][new_col] == 'O' or (new_row, new_col) in stacked:
                    # Skip selected points and points that are already in the stack
                    continue
                stacked.add((new_row, new_col))
                cur_neighbors.append((new_row, new_col))
            # Prioritize the furthest point from the origin
            cur_neighbors = sorted(cur_neighbors, key=distance)
            stack.extend(cur_neighbors)
        if not current_is_valid:
            return False
    return True

def simplify(structure):
    """Removes empty rows and columns, compacting the display
    """
    structure = structure[:]
    for i in range(len(structure)):
        structure[i] = list(''.join(structure[i]).replace('X', '.'))
    for row_idx in reversed(range(len(structure))):
        row = structure[row_idx]
        if len(row) == row.count('.'):
            # All empty, remove
            structure[row_idx:row_idx+1] = []
    for col_idx in reversed(range(len(structure[0]))):
        empty = True
        for row_idx in range(len(structure)):
            if structure[row_idx][col_idx] != '.':
                empty = False
                break
        if empty:
            for row in structure:
                row[col_idx:col_idx+1] = []
    return structure

def main():
    parser = ArgumentParser(description='Generate all possible floorplans')
    parser.add_argument('-n', type=int, help='The number of points')
    args = parser.parse_args()

    max_n = args.n

    with open(f'floorplans-{max_n}.txt', 'w') as outfile:
        for struct_id, structure in enumerate(generate_structures(max_n)):
            structure = simplify(structure)
            outfile.write(f'Structure ID: {struct_id}\n')
            outfile.write('\n'.join(''.join(row) for row in structure))
            outfile.write('\n\n')

if __name__ == '__main__':
    main()

对于 n=10,有 39,425 个不同的平面图。

对于 n=5,它打印以下 63 个结构:

Structure ID: 0
.O
.O
.O
OO

Structure ID: 1
.OO
.O.
OO.

Structure ID: 2
..O
.OO
OO.

Structure ID: 3
.OOO
OO..

Structure ID: 4
.O.
.OO
OO.

Structure ID: 5
..O
..O
OOO

Structure ID: 6
..OO
OOO.

Structure ID: 7
...O
OOOO

Structure ID: 8
OOOOO

Structure ID: 9
OOOO
...O

Structure ID: 10
OOO.
..OO

Structure ID: 11
OOO
..O
..O

Structure ID: 12
..O.
OOOO

Structure ID: 13
..O
OOO
..O

Structure ID: 14
OOOO
..O.

Structure ID: 15
OO..
.OOO

Structure ID: 16
OO.
.OO
..O

Structure ID: 17
OO
.O
OO

Structure ID: 18
OO.
.O.
.OO

Structure ID: 19
OO
.O
.O
.O

Structure ID: 20
OO.
.OO
.O.

Structure ID: 21
.O.
.O.
OOO

Structure ID: 22
.OO
OOO

Structure ID: 23
.O..
OOOO

Structure ID: 24
.O.
OOO
..O

Structure ID: 25
.O
.O
OO
.O

Structure ID: 26
.OO
OO.
.O.

Structure ID: 27
.O.
OO.
.OO

Structure ID: 28
.O
OO
.O
.O

Structure ID: 29
..O
OOO
.O.

Structure ID: 30
OOOO
.O..

Structure ID: 31
OOO
.OO

Structure ID: 32
OOO
.O.
.O.

Structure ID: 33
.O.
OOO
.O.

Structure ID: 34
O.O
OOO

Structure ID: 35
O...
OOOO

Structure ID: 36
O..
OOO
..O

Structure ID: 37
O..
OO.
.OO

Structure ID: 38
O.
OO
.O
.O

Structure ID: 39
O..
OOO
.O.

Structure ID: 40
O..
O..
OOO

Structure ID: 41
O.
O.
OO
.O

Structure ID: 42
O.
O.
O.
OO

Structure ID: 43
O
O
O
O
O

Structure ID: 44
O.
O.
OO
O.

Structure ID: 45
O..
OOO
O..

Structure ID: 46
O.
OO
OO

Structure ID: 47
O.
OO
O.
O.

Structure ID: 48
.O
.O
OO
O.

Structure ID: 49
.OO
OO.
O..

Structure ID: 50
..O
OOO
O..

Structure ID: 51
OOOO
O...

Structure ID: 52
OOO
O.O

Structure ID: 53
OO.
OOO

Structure ID: 54
OO
OO
.O

Structure ID: 55
OO
O.
OO

Structure ID: 56
OO
O.
O.
O.

Structure ID: 57
.O.
OOO
O..

Structure ID: 58
.O
OO
OO

Structure ID: 59
.O
OO
O.
O.

Structure ID: 60
OOO
OO.

Structure ID: 61
OOO
O..
O..

Structure ID: 62
OO
OO
O.