我對這個問題很開心,謝謝! :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