Praktikum Spezifikation und Verifikation 1 Spannende Graphentheorie

Werbung
Technische Universität München
Institut für Informatik
Prof. Tobias Nipkow, Ph. D.
Martin Strecker
WS 2003/2004
11. Dezember 2003
Aufgabenblatt 6
Praktikum Spezifikation und Verifikation
1
Spannende Graphentheorie
In dieser Aufgabe geht es darum, für einen Graphen eine Menge aufspannender Bäume zu
berechnen, d.h. eine Menge von Bäumen,
• deren Knotenmenge die Knotenmenge des Graphen ist und
• deren Kantenmenge eine Teilmenge der Kantenmenge des Graphen ist.
Ein Vorteil der Repräsentation eines Graphens durch aufspannender Bäume ist, daß sich
die Bäume leichter traversieren lassen (keine Zyklen möglich). Der unten vorgestellte Algorithmus ist zudem Grundlage einiger interessanter Algorithmen, z.B. zur Berechnung stark
zusammenhängender Teilgraphen.
1.1
Graphen
Die hier betrachteten gerichteten Graphen werden dargestellt als Listen von Paaren (Knoten, Liste der Nachfolgeknoten). Der in Abb. ?? gezeigte Graph wird also repräsentiert
durch [(0, [6, 9]), (1, [0, 8]), (2, [4, 7]), (4, [3, 7, 9]), (5, [8]), (6, [1,
5])].
0
9
1
6
8
5
2
4
3
Abbildung 1: Beispielgraph
types ’v graph = "(’v × ’v list) list"
7
Die folgenden Funktionen berechnen jeweils die Menge (repräsentiert als Liste) der Knoten
und der Kanten des Graphen und der Nachfolgeknoten eines gegebenen Knoten:
constdefs
vertices :: "’a graph ⇒ ’a list"
"vertices g == remdups ((map fst g) @ (flatten (map snd g)))"
edges :: "’a graph ⇒ (’a × ’a) list"
"edges g == flatten (map ( λ (v, vs). (map ( λ v’. (v, v’)) vs)) g)"
succs :: "[’a graph, ’a] ⇒ ’a list"
"succs g x == (if_none (assoc g x) [])"
Aufgabe: Zeigen Sie folgendes Lemma:
lemma succs_vertices: "set (succs g x) ⊆ set (vertices g)"
.
.
.
1.2
Bäume
Die folgende Definition für den Datentyp der Bäume geht über die bisher verwendeten
Datentypdefinitionen hinaus, da er gegenseitig induktiv ist (die induktiven Datentypen
Liste und Baum wechseln sich gegenseitig ab). Dies schlägt sich unter anderem darin
nieder, wie primitive Rekursion und Induktion durchgeführt werden. Sollten Sie mehr
Information hierzu benötigen, so lesen Sie den Abschnitt 3.4 im Tutorial.
datatype ’a tree = Node ’a "’a tree list"
consts
tr_vertices :: "’a tree ⇒ ’a list"
tr_list_vertices :: "’a tree list ⇒ ’a list"
primrec
"tr_vertices (Node x trs) = x # (tr_list_vertices trs)"
"tr_list_vertices [] = []"
"tr_list_vertices (t#ts) = (tr_vertices t) @ (tr_list_vertices ts)"
consts
tr_top :: "’a tree ⇒ ’a"
primrec
"tr_top (Node x trs) = x"
consts
tr_edges :: "’a tree ⇒ (’a × ’a) list"
tr_list_edges :: "’a tree list ⇒ (’a × ’a) list"
primrec
"tr_edges (Node x trs) =
(map ( λ t. (x, tr_top t)) trs) @ (tr_list_edges trs)"
2
"tr_list_edges [] = []"
"tr_list_edges (t#ts) = (tr_edges t) @ (tr_list_edges ts)"
1.3
Tiefensuche in Graphen
Die Funktion dfsm führt Tiefensuche in einem Graphen durch und markiert dabei die
bereits besuchten Knoten. Sie gibt ein Paar zurück, dessen erste Komponente eine Liste
der aufspannenden Bäume des Graphen ist und dessen zweite Komponente eine Liste der
bei der Traversierung markierten Knoten ist.
Etwas mehr im Detail ist das Vorgehen folgendes: Die Funktion hat Parameter g (den
Graphen), ms (die Liste der bereits markierten Knoten des Graphen) und eine Arbeitsliste
xs der noch zu besuchenden Knoten. Ist die Arbeitsliste abgearbeitet, wird eine leere
Baumliste und die Menge der markierten Knoten zurückgegeben. Ist noch mindestens
ein Knoten x zu besuchen und ist dieser in der Menge der Knoten des Graphen (siehe
weiter unten), so wird geprüft, ob der Knoten bereits markiert ist. Ist das der Fall, so
werden die restlichen noch zu besuchenden Knoten xs abgearbeitet. Ist der Knoten jedoch
nicht markiert, werden zuerst rekursiv alle Nachfolgeknoten von x aufgesucht. Man erhält
eine Liste von von x aus erreichbaren Teilbäumen tsx und die jetzt insgesamt besuchten
Knoten msx zurück. Mit dieser neuen Markierung arbeitet man in einem zweiten Aufruf
alle weiteren Knoten in xs ab und erhält wieder eine Baumliste und eine neue Markierung.
Die Funktion gibt die (geeignet kombinierten) Teilbaumlisten und die neue Markierung
zurück.
Aufgabe: Machen Sie sich das Vorgehen des Algorithmus an dem Beispiel aus Abb. ??
klar. Die durchgezogenen Kanten sind die Kanten von aufspannenden Bäumen des Graphen. Gibt es auch andere aufspannende Bäume, und wenn ja, welche berechnet der
Algorithmus?
consts
dfsm :: "((’a graph × ’a list) × ’a list) ⇒ (’a tree list × ’a list)"
recdef dfsm
"measure ( λ (g, ms). card (set (vertices g) - set ms))
<*lex*> measure length"
"dfsm ((g, ms), []) = ([], ms)"
"dfsm ((g, ms), x#xs) =
(if ( ¬ x mem (vertices g))
then ([], ms)
else (if (x mem ms)
then dfsm ((g, ms), xs)
else
(let
(tsx, msx) = dfsm ((g, (x # ms)), succs g x);
(tsxs, msxs) = dfsm ((g, msx @ x # ms), xs)
3
in ((Node x tsx) # tsxs, msxs))))"
( hints recdef_simp: mem_set dfsm_card
intro: psubset_card_mono)
Zur leichteren Handhabung verwenden wir folgende Simplifikationsregeln und außerdem
das Induktionsprädikat dfsm_induct_simp anstelle des von Isabelle generierten.
lemma dfsm_Nil: "dfsm ((g, ms), []) = ([], ms)"
by simp
lemma dfsm_Cons: "dfsm ((g, ms), x # xs) =
(if ( ¬ x mem (vertices g))
then ([], ms)
else if x mem ms
then dfsm ((g, ms), xs)
else (let (tsx, msx) = dfsm ((g, x # ms), succs g x);
(tsxs, msxs) = dfsm ((g, msx @ x # ms), xs)
in ((Node x tsx) # tsxs, msxs)))"
by (simp add: dfsm_card mem_set measure_def inv_image_def lex_prod_def)
Die Funktion dfs, an der wir primär interessiert sind, ruft dfsm mit einem Graphen, einer
leeren Liste von Markierungen und der Liste aller Knoten als Arbeitsliste auf:
constdefs
dfs :: "’a graph ⇒ ’a tree list"
"dfs g == fst (dfsm ((g, []), vertices g))"
Aus den Isabelle-Definitionen kann ML-Code generiert werden, dessen Ausführung wesentlich effizienter ist als die Vereinfachung durch den Isabelle-Simplifier.
Aufgabe: Um den generierten Code auszuprobieren, gehen Sie so vor:
• Starten Sie eine Isabelle-Shell mit isabelle (Kleinbuchstaben!). Üblicherweise werden in dem generierten Code spezielle Funktionen verwendet, die in einer StandardML Laufzeitumgebung nicht vorhanden sind.
• Laden Sie die ML-Datei mit use "dfs.ML";
• Rufen Sie die Funktion dfs mit verschiedenen Eingaben auf, z.B.
dfs [(0, [1]), (1, [2]), (2, [0])] ;
1.4
Beweise
Im folgenden sollen drei Aussagen gezeigt werden, aus denen die Hauptaussage folgt: dfs
berechnet eine Liste aufspannender Bäume, d.h. die Knoten der Bäume sind gleich der
Knoten des Graphen und die Kanten der Bäume sind eine Untermenge der Kanten des
Graphen.
4
Die erste Aussage stellt eine Beziehung zwischen den Knoten der Bäume und den Knoten
des Graphen her.
lemma marking_vertices:
"set xs ⊆ set (vertices g) −→
(let (ts, ms’) = dfsm ((g, ms), xs)
in set ms’ - set ms ⊆ set (tr_list_vertices ts)
∧ set (tr_list_vertices ts) ⊆ set (vertices g)
∧ tr_top ‘ set ts ⊆ set xs )"
.
.
.
Die folgenden Beweise haben eine ähnliche Struktur. Beachten Sie insbesondere, wie geschachtelte let -Ausdrücke mithilfe von subgoal_tac aufgelöst werden.
Aufgabe: Zeigen Sie: dfsm markiert zusätzlich zu den bereits markierten Knoten doch
diejenigen der Arbeitsliste. Damit der Beweis gelingt, muß angenommen werden, daß in
der Arbeitsliste nur Knoten des Graphen vorkommen:
lemma marking_increases:
"set xs ⊆ set (vertices g) −→
(let (ts, ms’) = dfsm ((g, ms), xs)
in set ms ∪ set xs ⊆ set ms’)"
.
.
.
Aufgabe: Zeigen Sie: Die Kanten der Bäume ergeben eine Teilmenge der Kanten des
Graphen:
lemma tr_list_edges_edges: "set xs ⊆ set (vertices g) −→
(let (ts, ms’) = dfsm ((g, ms), xs)
in set (tr_list_edges ts) ⊆ set (edges g))"
.
.
.
Aufgabe: Kombinieren Sie jetzt die obigen Lemmas zum Beweis der Hauptaussage:
theorem spanning_trees: "let ts = dfs g in
set (tr_list_vertices ts) = set (vertices g) ∧
set (tr_list_edges ts) ⊆ set (edges g)"
.
.
.
Abgabe: 7. Januar 2004
5
Herunterladen