我对这个问题很开心,谢谢! :P
我在C#中有一个解决方案。找到这些周期的算法非常短(约10条线),但周围存在很多混乱(例如Node和Edge类的实现)。
我已经使用了字母“e”代表边的变量命名约定,字母“a”是边开始的节点,“b”是它链接到的节点。这些公约,这是算法:
public static IEnumerable<Cycle> FindAllCycles()
{
HashSet<Node> alreadyVisited = new HashSet<Node>();
alreadyVisited.Add(Node.AllNodes[0]);
return FindAllCycles(alreadyVisited, Node.AllNodes[0]);
}
private static IEnumerable<Cycle> FindAllCycles(HashSet<Node> alreadyVisited, Node a)
{
for (int i = 0; i < a.Edges.Count; i++)
{
Edge e = a.Edges[i];
if (alreadyVisited.Contains(e.B))
{
yield return new Cycle(e);
}
else
{
HashSet<Node> newSet = i == a.Edges.Count - 1 ? alreadyVisited : new HashSet<Node>(alreadyVisited);
newSet.Add(e.B);
foreach (Cycle c in FindAllCycles(newSet, e.B))
{
c.Build(e);
yield return c;
}
}
}
}
它有一个优化重用一些Hashsets,这可能会造成混乱。我已经包含了下面的代码,它产生完全相同的结果,但是这个实现没有优化,所以你可以更容易地找出它的工作原理。
private static IEnumerable<Cycle> FindAllCyclesUnoptimized(HashSet<Node> alreadyVisited, Node a)
{
foreach (Edge e in a.Edges)
if (alreadyVisited.Contains(e.B))
yield return new Cycle(e);
else
{
HashSet<Node> newSet = new HashSet<Node>(alreadyVisited);
newSet.Add(e.B);//EDIT: thnx dhsto
foreach (Cycle c in FindAllCyclesUnoptimized(newSet, e.B))
{
c.Build(e);
yield return c;
}
}
}
这使用节点,边缘和周期以下的实施方式。他们非常直截了当,尽管我在确保一切不可变和成员尽可能无障碍方面投入了很多精力。
public sealed class Node
{
public static readonly ReadOnlyCollection<Node> AllNodes;
internal static readonly List<Node> allNodes;
static Node()
{
allNodes = new List<Node>();
AllNodes = new ReadOnlyCollection<Node>(allNodes);
}
public static void SetReferences()
{//call this method after all nodes have been created
foreach (Edge e in Edge.AllEdges)
e.A.edge.Add(e);
}
//All edges linking *from* this node, not to it.
//The variablename "Edges" it quite unsatisfactory, but I couldn't come up with anything better.
public ReadOnlyCollection<Edge> Edges { get; private set; }
internal List<Edge> edge;
public int Index { get; private set; }
public Node(params int[] nodesIndicesConnectedTo)
{
this.edge = new List<Edge>(nodesIndicesConnectedTo.Length);
this.Edges = new ReadOnlyCollection<Edge>(edge);
this.Index = allNodes.Count;
allNodes.Add(this);
foreach (int nodeIndex in nodesIndicesConnectedTo)
new Edge(this, nodeIndex);
}
public override string ToString()
{
return this.Index.ToString();
}
}
public sealed class Edge
{
public static readonly ReadOnlyCollection<Edge> AllEdges;
static readonly List<Edge> allEdges;
static Edge()
{
allEdges = new List<Edge>();
AllEdges = new ReadOnlyCollection<Edge>(allEdges);
}
public int Index { get; private set; }
public Node A { get; private set; }
public Node B { get { return Node.allNodes[this.bIndex]; } }
private readonly int bIndex;
internal Edge(Node a, int bIndex)
{
this.Index = allEdges.Count;
this.A = a;
this.bIndex = bIndex;
allEdges.Add(this);
}
public override string ToString()
{
return this.Index.ToString();
}
}
public sealed class Cycle
{
public readonly ReadOnlyCollection<Edge> Members;
private List<Edge> members;
private bool IsComplete;
internal void Build(Edge member)
{
if (!IsComplete)
{
this.IsComplete = member.A == members[0].B;
this.members.Add(member);
}
}
internal Cycle(Edge firstMember)
{
this.members = new List<Edge>();
this.members.Add(firstMember);
this.Members = new ReadOnlyCollection<Edge>(this.members);
}
public override string ToString()
{
StringBuilder result = new StringBuilder();
foreach (var member in this.members)
{
result.Append(member.Index.ToString());
if (member != members[members.Count - 1])
result.Append(", ");
}
return result.ToString();
}
}
然后来说明如何使用这种简单的API,我已经实现了你的两个例子。 基本上归结为,通过指定它们链接到哪个节点来创建所有节点,然后调用SetReferences(),以及....设置一些引用。之后,调用可公开访问的FindAllCycles()应该返回所有的周期。我排除了任何代码来重置静态成员,但这很容易实现。它应该只清除所有静态列表。
static void Main(string[] args)
{
InitializeExampleGraph1();//or: InitializeExampleGraph2();
Node.SetReferences();
var allCycles = FindAllCycles().ToList();
}
static void InitializeExampleGraph1()
{
new Node(1, 2);//says that the first node(node a) links to b and c.
new Node(2);//says that the second node(node b) links to c.
new Node(0, 3);//says that the third node(node c) links to a and d.
new Node(0);//etc
}
static void InitializeExampleGraph2()
{
new Node(1);
new Node(0, 0, 2);
new Node(0);
}
我必须指出,在这些例子中,边缘的索引没有对应于您的图像索引,但这是可以避免用一个简单的查找。 结果:allCycles是第一个例子:
{3, 2, 0}
{5, 4, 2, 0}
{3, 1}
{5, 4, 1}
allCycles是第二个例子:
{1, 0}
{2, 0}
{4, 3, 0}
我希望你满意这个解决方案,您使用它。我几乎没有对代码进行评论,所以我知道它可能很难理解。在这种情况下,请问,我会评论它!
在第二个例子中也不会解决2-3-1和3-1-2? – 2011-01-07 15:05:35
@msalvadores,没有当然没有。一个循环是一个节点链,与排列无关。如果你想在其他地方启动这个循环,那不会改变循环,对吗?这就像是说,如果你把玻璃从一边移到另一边的桌子上,它就不会再是同一个玻璃了...... – JBSnorro 2011-01-07 15:19:34
我认为这是可以讨论的。根据您所穿过的边缘的顺序,循环可能会有所不同。但这一切都归结于您的应用程序需要什么。如果你的情况2-3-1与3-1-2相同,那么就足够公平了。我可能需要重新安排我的anwser,因为我将所有相同周期的排列都给回了。 – 2011-01-07 15:43:46