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)