通过算法寻找卡坦岛游戏中最长的道路
我正在为课程编写一个《卡坦岛定居者》克隆版。额外的积分功能之一是自动确定哪个玩家拥有最长的道路。我考虑过,深度优先搜索的一些细微变化似乎可以起作用,但我无法弄清楚如何处理循环检测,如何处理玩家的两个初始道路网络的连接,以及其他一些细节。我怎样才能通过算法做到这一点?
对于那些不熟悉游戏的人,我将尝试简洁抽象地描述问题:我需要在无向循环图中找到最长的可能路径。
I'm writing a Settlers of Catan clone for a class. One of the extra credit features is automatically determining which player has the longest road. I've thought about it, and it seems like some slight variation on depth-first search could work, but I'm having trouble figuring out what to do with cycle detection, how to handle the joining of a player's two initial road networks, and a few other minutiae. How could I do this algorithmically?
For those unfamiliar with the game, I'll try to describe the problem concisely and abstractly: I need to find the longest possible path in an undirected cyclic graph.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
这应该是一个相当容易实现的算法。
首先,将道路分成不同的组,每个组中的所有路段都以某种方式连接。有多种方法可以做到这一点,但这里有一个:
注意:根据官方的卡坦岛规则,如果另一场比赛建立,一条路可能会被破坏两个部分之间的连接处的解决方案。你需要检测到这一点,而不是越过定居点。
请注意,在上面和下面的步骤中,仅考虑当前玩家分段。您可以忽略其他部分,就好像它们根本不在地图上一样。
这将为您提供一组或多组,每组包含一个或多个路段。
好的,对于每一组,执行以下操作:
现在,从您选择的路段中,进行递归分支深度优先搜索,跟踪到目前为止您找到的当前道路的长度。始终标记路段,并且不要分支到已标记的路段。这将使算法在“吃掉自己的尾巴”时停止。
每当您需要回溯时,因为没有更多分支,请记下当前长度,如果它比“先前的最大值”长,则将新长度存储为最大值。
对所有组都这样做,你就会得到最长的路。
This should be a rather easy algorithm to implement.
To begin with, separate out the roads into distinct sets, where all the road segments in each set are somehow connected. There's various methods on doing this, but here's one:
Note: As per the official Catan Rules, a road can be broken if another play builds a settlement on a joint between two segments. You need to detect this and not branch past the settlement.
Note, in the above, and following, steps, only consider the current players segments. You can ignore those other segments as though they weren't even on the map.
This gives you one or more sets, each containing one or more road segments.
Ok, for each set, do the following:
Now, from the segment you picked, do a recursive branching out depth-first search, keeping track of the length of the current road you've found so far. Always mark road segments as well, and don't branch into segments already marked. This will allow the algorithm to stop when it "eats its own tail".
Whenever you need to backtrack, because there are no more branches, take a note of the current length, and if it is longer than the "previous maximum", store the new length as the maximum.
Do this for all the sets, and you should have your longest road.
简单的多项式时间深度优先搜索不太可能起作用,因为该问题是 NP 困难的。您将需要花费指数时间才能获得最佳解决方案的东西。由于问题很小,所以在实践中应该没有问题。
最简单的解决方案可能是动态编程:保留一个表 T[v, l],为每个节点 v 和每个长度 l 存储长度为 l 且以 v 结尾的路径集。显然 T[v, 1] = { [v]} 则可以填写 T[v, l],其中 l > 1 通过收集来自 T[w, l-1] 的所有路径(其中 w 是 v 的邻居,但尚未包含 v),然后附加 v。这与 Justin L. 的解决方案类似,但避免了一些重复工作。
A simple polynomial-time depth-first search is unlikely to work, since the problem is NP-hard. You will need something that takes exponential time to get an optimal solution. Since the problem is so small, that should be no problem in practice, though.
Possibly the simplest solution would be dynamic programming: keep a table T[v, l] that stores for each node v and each length l the set of paths that have length l and end in v. Clearly T[v, 1] = {[v]} and you can fill out T[v, l] for l > 1 by collecting all paths from T[w, l-1] where w is a neighbor of v that do not already contain v, and then attaching v. This is similar to Justin L.'s solution, but avoids some duplicate work.
我建议进行广度优先搜索。
对于每个玩家:
选择一个起始节点。如果它有零个或多个连接的邻居,则跳过并以广度优先的方式找到从它开始的所有玩家路径。问题是:一旦遍历到一个节点,该节点就将“停用”该轮中剩余的所有搜索。也就是说,它可能不再被遍历。
如何实现这一点?这是可以使用的一种可能的广度优先算法:
执行此操作一次后,移动到下一个激活的节点并重新开始该过程。当所有节点都停用时停止。
您现在拥有的最大值是给定玩家的最长道路长度。
请注意,这效率稍低,但如果性能不重要,那么这会起作用:)
重要说明,感谢 Cameron MacFarland
Ruby 伪代码(假设每个节点都有一个
get_neighbors
函数)I would suggest a breadth-first search.
For each player:
Pick a starting node. Skip if it has zero or more than one connected neighbors and find all of the player's paths starting from it in a breadth-first manner. The catch: Once a node has been traversed to, it is "deactivated" for the all searches left in that turn. That is, it may no longer be traversed.
How can this be implemented? Here's one possible breadth-first algorithm that can be used:
After you do this once, move onto the next activated node and start the process all over again. Stop when all nodes are deactivated.
The maximum you have now is your longest road length, for the given player.
Note that this is slightly inefficient, but if performance doesn't matter, then this would work :)
IMPORTANT NOTE, thanks to Cameron MacFarland
Ruby pseudocode (assumes an
get_neighbors
function for each node)有点晚了,但仍然相关。我用java实现了它,请参阅
此实现支持 Catan 的大多数变体。边缘可以自行决定是否要连接到另一个边缘,请参阅
已实现 SidePiece 接口的道路、船舶、桥梁。一条路有作为实施
Little late, but still relevant. I implemented it in java, see here. Algorithm goes as follows:
This implementation supports most variants of Catan. The edges can decide for themselves if they want to connect to another, see
A road, ship, bridge have SidePiece interface implemented. A road has as implementation
我会做什么:
抱歉使用了 prolog 式的术语:)
What I'd do:
Sorry for the prolog-ish terminology :)
这是我的版本,如果有人需要的话。用
Typescript
编写。最长的RoadLengthForPlayer(玩家:PlayerColors):数字{
让最长的路= 0
Here is my version if anyone needs it. Written in
Typescript
.longestRoadLengthForPlayer(player: PlayerColors): number {
let longestRoad = 0
您可以使用 Dijkstra,只需更改条件即可选择最长的路径。
http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
它很高效,但并不总能找到实际上最短/最长的路径,尽管它在大多数情况下都有效。
You could use Dijkstra and just change the conditions to choose the longest path instead.
http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
It's efficient, but won't always find the path that in reality is shortest/longest although it will work most of the times.