Algorithm 解谜算法

Algorithm 解谜算法,algorithm,path,collision-detection,collision,Algorithm,Path,Collision Detection,Collision,我正在尝试做一个游戏,玩家必须在游戏板上自始至终找到自己的路 正如你所看到的,这个游戏板包含了一堆红色的圆形障碍物。为了赢得比赛,玩家必须清除最少的障碍物。所以我的问题是,我如何以编程的方式找出要移除的障碍的最小数量,以释放路径?自由路径将被视为圆圈之间的空间,不重叠也不接触 所以我真正需要的是要删除的最小数量的圆,我不需要实际的路径。有没有一个简单的方法可以做到这一点 为了补充对这个游戏板的理解,每个圆都有相同的半径,并且它受到黑线的限制 此外,不必沿直线移动。一个选项是首先删除重叠或接触次数

我正在尝试做一个游戏,玩家必须在游戏板上自始至终找到自己的路

正如你所看到的,这个游戏板包含了一堆红色的圆形障碍物。为了赢得比赛,玩家必须清除最少的障碍物。所以我的问题是,我如何以编程的方式找出要移除的障碍的最小数量,以释放路径?自由路径将被视为圆圈之间的空间,不重叠也不接触

所以我真正需要的是要删除的最小数量的圆,我不需要实际的路径。有没有一个简单的方法可以做到这一点

为了补充对这个游戏板的理解,每个圆都有相同的半径,并且它受到黑线的限制


此外,不必沿直线移动。

一个选项是首先删除重叠或接触次数最多的圆。每次删除后,请检查其是否为解决方案,如果不是,请继续删除

var circle;
circle = findMostOverlapCircle();
while(circle != null) {
    circle.remove();
    circle = findMostOverlapCircle();
}

这是一个图论
最大流量
问题

假设每个圆都是图中的一个节点。另外引入两个特殊节点:
TOP
BOTTOM
。如果圆与上侧/下侧相交,则将圆与这些节点连接。如果圆相交,则将与圆相对应的节点相互连接

现在,您需要在这个图中找到一个最小切割,顶部作为源,底部作为汇,反之亦然。你可以用它来解决它。它说明的是
最小切割问题
相当于最大流量问题。您可以在上找到有关如何解决最大流量问题的详细信息

由于我们只能通过每个节点一次,因此我们应该将节点转换为容量为1的有向边,每个圆有入节点和出节点。最大流算法将解决结果图上的问题,并考虑到我们正在删除圆而不是圆之间的连接这一事实。对于这个问题,最好是删除图中的节点而不是边,因为我们总是可以通过删除节点来删除任何边。另外,删除节点可能会导致删除多条边


顺便说一下,类似的问题也可以在上找到。尝试在评委身上解决这项任务是一个好主意,然后您将确保您的解决方案是正确的。

对于图形翻译,类似的方法可能会奏效

如果两个圆重叠,在它们之间画一堵墙(蓝线)。不要忘了添加顶部和底部边框。这将创建两个区域。这些将是图形的所有节点

接下来我们必须找到边,从一个节点到另一个节点的成本。以两个相邻的区域(共用一堵墙)为例,然后通过蛮力,或者你想出的任何聪明的方法,确定从这个区域移动到另一个区域的成本。如果删除一个圆,则意味着删除所有指向该圆的墙。如果这使两个区域成为一个区域,则边的代价为1。如果需要删除两个圆(它们拥有的所有墙)以合并两个区域,则成本为2。等等

绘制了一些边(绿色)。我们必须从起点区域到终点区域。你现在得到了一个每日加权图

我认为这可以改进很多,但我把它作为一个练习=)

在这种情况下,最小值为3

警告,这幅画是手工绘制的,我确实忘记了一些墙壁、边缘和区域。仅供说明之用。

为了把列奥尼德写的东西形象化,我画了下面的图表


好的,所以我决定在pygame中对此进行可视化

结果比我想象的要困难得多

与其他建议一样,使用最大流量。从源到汇的流量瓶颈是流量最密集的地方。如果我们在这个密集的瓶颈处将图形切成两半(即在最小切割处),那么我们有最小的圈数。 maxflow=min cut就是这样

以下是我采取的步骤:

  • 创建pygame世界,我可以随机生成圆圈

  • 使用函数计算圆之间的所有碰撞:

  • 这涉及到按x坐标对圆进行排序。现在,为了找到圆[0]的所有碰撞,我一直沿着数组移动,测试碰撞,直到我发现一个圆的x值大于圆[0]的x值的2*半径,然后我可以弹出圆[0]并重复这个过程

  • 创建源节点和接收节点,计算出它们需要连接到哪些圆
  • 步骤4-5在“findflow()函数”中执行

  • 创建表示带有节点的圆的基本无向networkX图。仅当节点对应的圆发生碰撞时才连接节点

  • 这就是开始变得困难的地方。。我从我的无向图创建一个新的有向图。因为我需要计算通过圆(即节点)而不是边的流,所以我需要将每个节点拆分为两个节点,中间有一条边

    假设我将节点X连接到节点Y(YX)(在原始图中)

    我将X更改为Xa和Xb,以便Xa连接到Xb(Xa->Xb) 也可以将Y更改为(Ya->Yb)

    我还需要添加(Yb->Xa)和(Xb->Ya)来表示X和Y之间的原始连接

  • 无向图中的所有边的容量均为1(例如,您只能穿过一个圆一次)

  • 现在,我在新的有向图上应用networkx.ford_fulkerson()算法。这将为我找到流程值和流程图。
  • flowValue是一个整数。它是最小切割(或从源到汇的最大流量)这是我们一直在寻找的答案。它表示我们需要删除的最小圈数

    奖金分配:

    我想
    __author__ = 'Robert'
    import pygame
    import networkx
    
    class CirclesThing():
        def __init__(self,width,height,number_of_circles):
            self.removecircles = False #display removable circles as green.
            self.width = width
            self.height = height
    
            self.number_of_circles = number_of_circles
            self.radius = 40
    
            from random import randint
            self.circles = sorted(set((randint(self.radius,width-self.radius),randint(2*self.radius,height-2*self.radius)) for i in range(self.number_of_circles)))
    
            self.sink = (self.width/2, self.height-10)
            self.source = (self.width/2, 10)
    
            self.flowValue,self.flowGraph = self.find_flow()
    
            self.seen = set()
            self.seen.add(self.source)
            self.dfs(self.flowGraph,self.source)
    
            self.removable_circles = set()
            for node1 in self.flowGraph:
                if node1 not in self.seen or node1==self.source:
                    continue
                for node2 in self.flowGraph[node1]:
                    if self.flowGraph[node1][node2]==1:
                        if node2 not in self.seen:
                            self.removable_circles.add(node1[0])
    
    
        def find_flow(self):
            "finds the max flow from source to sink and returns the amount, along with the flow graph"
            G = networkx.Graph()
            for node1,node2 in self.get_connections_to_source_sink()+self.intersect_circles():
                G.add_edge(node1,node2,capacity=1)
    
            G2 = networkx.DiGraph()
            for node in G:
                if node not in (self.source,self.sink):
                    G2.add_edge((node,'a'),(node,'b'),capacity=1) #each node is split into two new nodes. We add the edge between the two new nodes flowing from a to b.
    
            for edge in G.edges_iter():
                if self.source in edge or self.sink in edge:
                    continue #add these edges later
                node1,node2 = edge
                G2.add_edge((node1,'b'),(node2,'a'),capacity=1) #if we flow through a circle (from node1a to node1b) we need to be able to flow from node1b to all node1's children
                G2.add_edge((node2,'b'),(node1,'a'),capactiy=1) #similarly for node2..
    
            for node in G[self.source]:
                G2.add_edge(self.source,(node,'a'))
            for node in G[self.sink]:
                G2.add_edge((node,'b'),self.sink)
    
            flowValue, flowGraph = networkx.ford_fulkerson(G2,self.source,self.sink)
    
            return flowValue, flowGraph
    
    
        def dfs(self,g,v):
            "depth first search from source of flowGraph. Don't explore any nodes that are at maximum capacity. (this means we can't explore past the min cut!)"
            for node in g[v]:
                if node not in self.seen:
                    self.seen.add(node)
                    if g[v][node]!=1 or v==self.source:
                        self.dfs(g,node)
    
        def display(self):
            self.draw_circles()
            self.draw_circles(circle_radius=5, circle_colour=(255,0,0))
            if not self.removecircles:
                lines = self.intersect_circles()
                self.draw_lines(lines)
            self.draw_source_sink()
    
        def draw_circles(self,circle_radius=None,circle_colour=(0,0,255),circles=None):
            if circle_radius is None:
                circle_radius = self.radius
            if circles is None:
                circles = self.circles
    
            circle_thickness = 2
            for pos in circles:
                cc = circle_colour if pos not in self.removable_circles else (100,200,0) #change colour of removable circles
                ct = circle_thickness if pos not in self.removable_circles else 4 #thicken removable circles
                if pos not in self.removable_circles or not self.removecircles:
                    pygame.draw.circle(screen, cc, pos, circle_radius, ct)
    
        def intersect_circles(self):
            colliding_circles = []
            for i in range(len(self.circles)-1):
                for j in range(i+1,len(self.circles)):
                    x1,y1 = self.circles[i]
                    x2,y2 = self.circles[j]
                    if x2-x1>2*self.radius+5: #add 5 to make a more obvious gap visually
                        break #can't collide anymore.
                    if (x2-x1)**2 + (y2-y1)**2 <= (2*self.radius)**2+5:
                        colliding_circles.append(((x1,y1),(x2,y2)))
            return colliding_circles
    
        def draw_lines(self,lines,line_colour=(255, 0, 0)):
            for point_pair in lines:
                point1,point2 = point_pair
                try:
                    tot = self.flowGraph[(point1,'b')][(point2,'a')] + self.flowGraph[(point2,'b')][(point1,'a')] #hack, does anything flow between the two circles?
                except KeyError:
                    tot = 0
                thickness = 1 if tot==0 else 3
                lc = line_colour if tot==0 else (0,90,90)
                pygame.draw.line(screen, lc, point1, point2, thickness)
    
        def draw_source_sink(self):
            self.draw_circles(circles=(self.sink,self.source),circle_radius=15,circle_colour=(0,255,0))
    
            bottom_line = ((0,self.height-3*self.radius),(self.width,self.height-3*self.radius))
            top_line = ((0,3*self.radius),(self.width,3*self.radius))
    
            self.draw_lines([top_line, bottom_line],line_colour=(60,60,60))
    
            if not self.removecircles:
                self.draw_lines(self.get_connections_to_source_sink(),line_colour=(0,255,0))
    
        def get_connections_to_source_sink(self):
            connections = []
            for x,y in self.circles:
                if y<4*self.radius:
                    connections.append((self.source,(x,y)))
                elif y>height-4*self.radius:
                    connections.append((self.sink,(x,y)))
            return connections
    
        def get_caption(self):
            return "flow %s, circles removes %s" %(self.flowValue,len(self.removable_circles))
    
    
    
    time_per_simulation = 5 #5 seconds
    width, height = 1400, 600
    background_colour = (255,255,255)
    screen = pygame.display.set_mode((width, height))
    
    screen.fill(background_colour)
    from pygame.locals import USEREVENT
    pygame.time.set_timer(USEREVENT+1,time_per_simulation*1000)
    
    simulations = 0
    simulations_max = 20
    
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == USEREVENT+1:
                if simulations % 2 ==0:
                    world = CirclesThing(width,height,120) #new world
                else:
                    world.removecircles = True #current world without green circles
    
                screen.fill(background_colour)
                world.display()
                pygame.display.set_caption(world.get_caption())
                pygame.display.flip()
    
                if simulations>=2*simulations_max:
                    running = False
                simulations+=1
    
                if False:
                    pygame.image.save(screen,'sim%s.bmp'%simulations)