Ich löse ein Problem in einem gerichteten azyklischen Graphen.
Aber ich habe Probleme, meinen Code auf einigen gerichteten azyklischen Graphen zu testen. Die Testgraphen sollten groß sein und (offensichtlich) azyklisch.
Ich habe viel versucht, Code zum Generieren azyklischer gerichteter Graphen zu schreiben. Aber ich bin jedes Mal gescheitert.
Gibt es eine vorhandene Methode zum Generieren azyklischer gerichteter Graphen, die ich verwenden könnte?
Wie können Sie C und C++ verwenden, aber keinen Code anzeigen?
– sehen
8. Oktober 2012 um 22:29 Uhr
Ich würde eine große Open-Source-Netzliste nehmen und Schleifen (falls vorhanden) mit einem einfachen DFS-basierten Schleifendetektor unterbrechen.
– Bobah
8. Oktober 2012 um 22:30 Uhr
„Ich leide“ – hoffentlich tut es nicht weh. Entschuldigung, ich musste mich lustig machen, aber dein Englisch ist wirklich ok. Siehe die Funktion directed_acyclic_graph hier: condor.depaul.edu/rjohnson/source/graph_ge.c
– Flavius
8. Oktober 2012 um 22:31 Uhr
ArjunShankar
Ich habe ein C-Programm entwickelt, das dies tut. Der Schlüssel liegt darin, die Knoten zu „ranken“, und nur Ziehe Kanten von Knoten mit niedrigerem Rang zu Knoten mit höherem Rang.
Das Programm, das ich geschrieben habe, druckt in die DOT-Sprache.
Hier ist der Code selbst, mit Kommentaren, die erklären, was er bedeutet:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MIN_PER_RANK 1 /* Nodes/Rank: How 'fat' the DAG should be. */
#define MAX_PER_RANK 5
#define MIN_RANKS 3 /* Ranks: How 'tall' the DAG should be. */
#define MAX_RANKS 5
#define PERCENT 30 /* Chance of having an Edge. */
int main (void)
{
int i, j, k,nodes = 0;
srand (time (NULL));
int ranks = MIN_RANKS
+ (rand () % (MAX_RANKS - MIN_RANKS + 1));
printf ("digraph {\n");
for (i = 0; i < ranks; i++)
{
/* New nodes of 'higher' rank than all nodes generated till now. */
int new_nodes = MIN_PER_RANK
+ (rand () % (MAX_PER_RANK - MIN_PER_RANK + 1));
/* Edges from old nodes ('nodes') to new ones ('new_nodes'). */
for (j = 0; j < nodes; j++)
for (k = 0; k < new_nodes; k++)
if ( (rand () % 100) < PERCENT)
printf (" %d -> %d;\n", j, k + nodes); /* An Edge. */
nodes += new_nodes; /* Accumulate into old node set. */
}
printf ("}\n");
return 0;
}
Und hier ist die Grafik, die aus einem Testlauf generiert wurde:
Wie haben Sie sich den Graphen so vorgestellt?
– LaLa
24. Oktober 2016 um 6:59 Uhr
@LaLa wie ich in meiner Antwort erwähnt habe, druckt das Programm in meiner Antwort die Ausgabe in die DOT-Sprache. Dieses Dateiformat ist maschinenlesbar und kann mit jedem Programm visualisiert werden, das die Sprache versteht (ich glaube, es gibt mehr als eines). Ich habe die verwendet dot mitgeliefertes Programm GraphWiz Paket.
Ein ähnlicher Ansatz wäre, eine willkürliche Reihenfolge Ihrer Knoten zu nehmen und dann Kanten von Knoten zu betrachten x zu j nur wenn x. Diese Einschränkung sollte auch Ihre DAGness durch Konstruktion erhalten. Der Speichervergleich wäre eine willkürliche Möglichkeit, Ihre Knoten zu ordnen, wenn Sie Strukturen verwenden, um Knoten darzustellen.
Der Pseudocode schlägt vor, dass die Anzahl potenzieller DAGs bei gegebenen N Knoten ist
2^(n*(n-1)/2),
weil dort sind
n*(n-1)/2
geordnete Paare (“N wähle 2”), und wir können wählen, ob wir den Rand zwischen ihnen haben möchten oder nicht.
Können alle Dags als Adjazenzmatrix eines unteren Dreiecks bis zur Permutation von Scheitelpunkten dargestellt werden?
– Andreas Tomazos
8. Oktober 2012 um 22:45 Uhr
Ja. Entsprechend en.wikipedia.org/wiki/Directed_acyclic_graph, können Sie die Knoten topologisch sortieren. Wenn Sie die Matrixzeilen in der Reihenfolge des Toposorts ausschreiben, muss diese aufgrund des Toposorts niedriger dreieckig sein.
– dyoo
8. Oktober 2012 um 23:09 Uhr
Ein Beweis mit besserer Erklärung zu SO: stackoverflow.com/questions/777613/…
– dyoo
8. Oktober 2012 um 23:23 Uhr
Ich sehe jetzt, es muss einen Scheitelpunkt in einem DAG ohne eingehende Kante geben. Platzieren Sie eine davon in der letzten Zeile der n-Matrix. Die Spalte ganz rechts muss aus Nullen bestehen. Entfernen Sie den Scheitelpunkt aus dem Diagramm, was zu n-1 Scheitelpunkt DAG führt. Durch Induktion funktioniert es für n-1 Submatrix.
– Andreas Tomazos
9. Oktober 2012 um 14:53 Uhr
Sie erzwingen keine Struktur, die eine einzelne verbundene DAG sicherstellt. Dein maybePutAnEdgeBetween Verfahren müsste dies erleichtern.
– Domi
18. Januar 2014 um 6:11 Uhr
rici
Um also zu versuchen, all diese vernünftigen Antworten zusammenzufassen:
(Im Folgenden habe ich V für die Anzahl der Scheitelpunkte im generierten Graphen und E für die Anzahl der Kanten verwendet, und wir nehmen an, dass E ≤ V(V-1)/2.)
Ich persönlich denke, die nützlichste Antwort ist ein Kommentar von Flavius, der auf den Code hinweist http://condor.depaul.edu/rjohnson/source/graph_ge.c. Dieser Code ist wirklich einfach und wird bequem durch einen Kommentar beschrieben, den ich reproduziere:
To generate a directed acyclic graph, we first
generate a random permutation dag[0],...,dag[v-1].
(v = number of vertices.)
This random permutation serves as a topological
sort of the graph. We then generate random edges of the
form (dag[i],dag[j]) with i < j.
Tatsächlich generiert der Code die Anforderungsanzahl von Kanten, indem er wiederholt Folgendes ausführt:
Generieren Sie zwei Zahlen im Bereich [0, V);
reject them if they’re equal;
swap them if the first is larger;
reject them if it has generated them before.
The problem with this solution is that as E gets closes to the maximum number of edges V(V-1)/2, then the algorithm becomes slower and slower, because it has to reject more and more edges. A better solution would be to make a vector of all V(V-1)/2 possible edges; randomly shuffle it; and select the first (requested edges) edges in the shuffled list.
The reservoir sampling algorithm lets us do this in space O(E), since we can deduce the endpoints of the kth edge from the value of k. Consequently, we don’t actually have to create the source vector. However, it still requires O(V2) time.
Alternatively, one can do a Fisher-Yates shuffle (or Knuth shuffle, if you prefer), stopping after E iterations. In the version of the FY shuffle presented in Wikipedia, this will produce the trailing entries, but the algorithm works just as well backwards:
// At the end of this snippet, a consists of a random sample of the
// integers in the half-open range [0, V(V-1)/2). (They still need to be
// converted to pairs of endpoints).
vector<int> a;
int N = V * (V - 1) / 2;
for (int i = 0; i < N; ++i) a.push_back(i);
for (int i = 0; i < E; ++i) {
int j = i + rand(N - i);
swap(a[i]a[j]); a.resize(E);
Dies erfordert nur O(E) Zeit, aber es erfordert O(N).2) Platz. Tatsächlich kann dies mit einigen Tricks zum O(E)-Raum verbessert werden, aber ein SO-Code-Snippet ist zu klein, um das Ergebnis zu enthalten, also werde ich einen einfacheren im O(E)-Raum und O(E log E) bereitstellen ) Zeit. Ich gehe davon aus, dass es eine Klasse DAG mit mindestens:
class DAG {
// Construct an empty DAG with v vertices
explicit DAG(int v);
// Add the directed edge i->j, where 0 <= i, j < v
void add(int i, int j);
};
Jetzt geht es hier:
// Return a randomly-constructed DAG with V vertices and and E edges.
// It's required that 0 < E < V(V-1)/2.
template<typename PRNG>
DAG RandomDAG(int V, int E, PRNG& prng) {
using dist = std::uniform_int_distribution<int>;
// Make a random sample of size E
std::vector<int> sample;
sample.reserve(E);
int N = V * (V - 1) / 2;
dist d(0, N - E); // uniform_int_distribution is closed range
// Random vector of integers in [0, N-E]
for (int i = 0; i < E; ++i) sample.push_back(dist(prng));
// Sort them, and make them unique
std::sort(sample.begin(), sample.end());
for (int i = 1; i < E; ++i) sample[i] += i;
// Now it's a unique sorted list of integers in [0, N-E+E-1]
// Randomly shuffle the endpoints, so the topological sort
// is different, too.
std::vector<int> endpoints;
endpoints.reserve(V);
for (i = 0; i < V; ++i) endpoints.push_back(i);
std::shuffle(endpoints.begin(), endpoints.end(), prng);
// Finally, create the dag
DAG rv;
for (auto& v : sample) {
int tail = int(0.5 + sqrt((v + 1) * 2));
int head = v - tail * (tail - 1) / 2;
rv.add(head, tail);
}
return rv;
}
Andreas Tomassos
Sie könnten einen zufälligen gerichteten Graphen erzeugen und dann eine Tiefensuche nach Zyklen durchführen. Wenn Sie einen Zyklus finden, unterbrechen Sie ihn, indem Sie eine Kante löschen.
Ich denke, das ist der schlimmste Fall O (VE). Jede DFS nimmt O (V) und jede entfernt mindestens eine Kante (also max E)
Wenn Sie den gerichteten Graphen generieren, indem Sie alle möglichen V ^ 2-Kanten gleichmäßig zufällig auswählen und DFS in zufälliger Reihenfolge ausführen und eine zufällige Kante löschen, erhalten Sie eine gleichmäßige Verteilung (oder zumindest eine nahe daran) über alle möglichen Dags.
Dadurch erhalten Sie einen DAG mit möglicherweise mehr als einer Komponente. Sie können eine disjunkte Datenstruktur verwenden, um Ihnen die Komponenten zu geben, die dann zusammengeführt werden können, indem Sie Kanten zwischen den Komponenten erstellen.
Bearbeiten: Ich habe diesen Beitrag ursprünglich gefunden, als ich mit einem Planungsproblem namens Flexible Job Shop Scheduling Problem mit Sequenzierungsflexibilität arbeitete, bei dem Jobs (die Reihenfolge, in der Operationen verarbeitet werden) durch gerichtete azyklische Graphen definiert werden. Die Idee war, einen Algorithmus zu verwenden, um mehrere zufällige gerichtete Graphen (Jobs) zu generieren und Instanzen des Scheduling-Problems zu erstellen, um meine Algorithmen zu testen. Der Code am Ende dieses Beitrags ist eine Basisversion des Codes, den ich zum Generieren der Instanzen verwendet habe. Der Instanzgenerator ist zu finden hier.
Ich habe nach Python übersetzt und einige Funktionalitäten integriert, um einen transitiven Satz des zufälligen DAG zu erstellen. Auf diese Weise hat der erzeugte Graph die minimale Anzahl von Kanten bei gleicher Erreichbarkeit.
Der transitive Graph kann unter visualisiert werden http://dagitty.net/dags.html indem Sie die Ausgabe einfügen Modellnummer (rechts).
Python-Version des Algorithmus
import random
class Graph:
nodes = []
edges = []
removed_edges = []
def remove_edge(self, x, y):
e = (x,y)
try:
self.edges.remove(e)
# print("Removed edge %s" % str(e))
self.removed_edges.append(e)
except:
return
def Nodes(self):
return self.nodes
# Sample data
def __init__(self):
self.nodes = []
self.edges = []
def get_random_dag():
MIN_PER_RANK = 1 # Nodes/Rank: How 'fat' the DAG should be
MAX_PER_RANK = 2
MIN_RANKS = 6 # Ranks: How 'tall' the DAG should be
MAX_RANKS = 10
PERCENT = 0.3 # Chance of having an Edge
nodes = 0
ranks = random.randint(MIN_RANKS, MAX_RANKS)
adjacency = []
for i in range(ranks):
# New nodes of 'higher' rank than all nodes generated till now
new_nodes = random.randint(MIN_PER_RANK, MAX_PER_RANK)
# Edges from old nodes ('nodes') to new ones ('new_nodes')
for j in range(nodes):
for k in range(new_nodes):
if random.random() < PERCENT:
adjacency.append((j, k+nodes))
nodes += new_nodes
# Compute transitive graph
G = Graph()
# Append nodes
for i in range(nodes):
G.nodes.append(i)
# Append adjacencies
for i in range(len(adjacency)):
G.edges.append(adjacency[i])
N = G.Nodes()
for x in N:
for y in N:
for z in N:
if (x, y) != (y, z) and (x, y) != (x, z):
if (x, y) in G.edges and (y, z) in G.edges:
G.remove_edge(x, z)
# Print graph
for i in range(nodes):
print(i)
print()
for value in G.edges:
print(str(value[0]) + ' ' + str(value[1]))
get_random_dag()
Unten sehen Sie in der Abbildung den zufälligen DAG mit vielen redundanten Kanten, die vom obigen Python-Code generiert wurden.
Ich habe den Code angepasst, um den gleichen Graphen (gleiche Erreichbarkeit) zu generieren, aber mit der geringstmöglichen Anzahl von Kanten. Dies wird auch als transitive Reduktion bezeichnet.
def get_random_dag():
MIN_PER_RANK = 1 # Nodes/Rank: How 'fat' the DAG should be
MAX_PER_RANK = 3
MIN_RANKS = 15 # Ranks: How 'tall' the DAG should be
MAX_RANKS = 20
PERCENT = 0.3 # Chance of having an Edge
nodes = 0
node_counter = 0
ranks = random.randint(MIN_RANKS, MAX_RANKS)
adjacency = []
rank_list = []
for i in range(ranks):
# New nodes of 'higher' rank than all nodes generated till now
new_nodes = random.randint(MIN_PER_RANK, MAX_PER_RANK)
list = []
for j in range(new_nodes):
list.append(node_counter)
node_counter += 1
rank_list.append(list)
print(rank_list)
# Edges from old nodes ('nodes') to new ones ('new_nodes')
if i > 0:
for j in rank_list[i - 1]:
for k in range(new_nodes):
if random.random() < PERCENT:
adjacency.append((j, k+nodes))
nodes += new_nodes
for i in range(nodes):
print(i)
print()
for edge in adjacency:
print(str(edge[0]) + ' ' + str(edge[1]))
print()
print()
Ergebnis:
Dietmar Kühl
Erstellen Sie ein Diagramm mit n Knoten und eine Kante zwischen jedem Knotenpaar n1 und n2 wenn n1 != n2 und n2 % n1 == 0.
“eine Kante zwischen jedem Knotenpaar n1 und n2, wenn n1 != n2 und n2 % n1 == 0” – Das würde auf deterministische Weise einen Graphen erzeugen.
– ArjunShankar
8. Oktober 2012 um 23:23 Uhr
Ja es würde. Die Frage war über das Generieren von DAGs. Dies ist ein Rezept zum Erstellen von DAGs. Es gab keine Aussage über Zufälligkeit. Außerdem gibt es einen entscheidenden Vorteil deterministischer Graphen zum Testen: Sie sind deterministisch!
– Dietmar Kühl
8. Oktober 2012 um 23:27 Uhr
Seit ich mich mit dieser Frage befasse, trägt sie den Titel „zufällig“. Ich sehe jetzt, dass dies das Ergebnis einer Bearbeitung ist. Zu den Vorteilen der Verwendung von Graphen, die im Wesentlichen Untergraphen voneinander sind, um Code zu testen: Ich glaube nicht, dass ich damit einverstanden bin. Ich würde lieber nur ein paar tausend Pseudozufallsdiagramme generieren und sie aufbewahren, damit ich Fehler/Regressionen besser finden kann. Wie auch immer, ich werde den Punkt nicht bestreiten. Dies ist immer noch eine ziemlich gültige Antwort.
– ArjunShankar
8. Oktober 2012 um 23:32 Uhr
13839000cookie-checkGenerieren eines zufälligen DAGyes
Wie können Sie C und C++ verwenden, aber keinen Code anzeigen?
– sehen
8. Oktober 2012 um 22:29 Uhr
Ich würde eine große Open-Source-Netzliste nehmen und Schleifen (falls vorhanden) mit einem einfachen DFS-basierten Schleifendetektor unterbrechen.
– Bobah
8. Oktober 2012 um 22:30 Uhr
„Ich leide“ – hoffentlich tut es nicht weh. Entschuldigung, ich musste mich lustig machen, aber dein Englisch ist wirklich ok. Siehe die Funktion
directed_acyclic_graph
hier: condor.depaul.edu/rjohnson/source/graph_ge.c– Flavius
8. Oktober 2012 um 22:31 Uhr