Algorithmik mit Python Prof. Dr. Tobias Häberlein Hochschule Albstadt-Sigmaringen Studiengang Kommunikations- und Softwaretechnik“ ” Leipzig, 04.04.2011 T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 1 / 57 Überblick 1 2 3 4 5 Implementierung von Algorithmen Sortieren Insertion-Sort Quicksort Suchen Heaps Skip-Listen Bloomfilter Suchmaschinen Graphen Grundlagen Kürzeste Wege Minimaler Spannbaum Schwere Probleme Lösung des Traveling Salesman Problems Greedy-Heuristiken zur Lösung des TSP T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 2 / 57 Implementierung von Algorithmen Rekursion 1 Eine Funktion heißt rekursiv, wenn Sie sich selbst ein oder mehrmals aufruft. 2 3 4 5 Pro: Oft einfacher zu implementieren, als iterative Ansätze. Con: I. A. mehr Speicherverbrauch T. Häberlein HS AlbSig 1 2 3 4 5 def facIter(n): erg = 1 for i in range(1,n+1) erg = erg*i return erg def facRec(n): if n==0: return 1 else: return n*fac(n-1) Algorithmik mit Python Leipzig, 04.04.2011 3 / 57 Implementierung von Algorithmen Rekursive vs. Iterativ? Kochrezept“ ” 1 2 Rekursionsabbruch: Was ist der einfache“ Fach ” (Größe n = 0 oder n = 1). Rekursionsschritt: I I Gedankentrick: Angenommen, Aufgabe für alle kleineren“ ” Probleme gelöst . . . . . . wie kann man dann aus den Lösungen der kleineren Aufgaben, die Lösung der Gesamtaufgabe konstruieren. T. Häberlein HS AlbSig 1 2 3 4 5 6 7 8 9 10 def rekAlg(x): if len(x) is kleingenug: return loesung(x) else: ... y1 = rekAlg(x1) y2 = rekAlg(x2) ... loesung = kombiniere(y1,y return loesung Algorithmik mit Python Leipzig, 04.04.2011 4 / 57 Implementierung von Algorithmen Aufgaben: Rekursion (1) Aufgabe 1 1 Definieren Sie die Funktion sum(n), die die Summe der Zahlen von 1 bis n berechnen soll, rekursiv. 2 Definieren Sie die Funktin len(lst), die die Länge der Liste lst berechnen soll, rekursiv. Aufgabe 2 Implementieren Sie die Funktion ins(x,lst), die die Liste aller möglichen Einfügungen des Elements x in die Liste lst zurückliefert. Beispielanwendung: >>> ins(1,[2,3,4,5]) >>> [[1,2,3,4,5], [2,1,3,4,5], [2,3,1,4,5], [2,3,4,1,5], [2,3,4,5,1] Tipp: Das geht über eine rekursive Implementierung. Es empfiehlt sich auch die Verwendung einer Listenkomprehension. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 5 / 57 Implementierung von Algorithmen Aufgaben: Rekursion (2) ... und noch etwas schwieriger: Aufgabe 3 Implementieren sie eine rekursive Funktion perms(lst), die die Liste aller Permutationen der als Argument übergebenen Liste lst zurückliefert. Tipp: Verwenden Sie die eben definierte Funktion ins. Beispielanwendung: >>> perms([1,2,3]) >>> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] Aufgabe 4 Implementierung Sie die rekursive Funktion choice(lst,k), die eine Liste aller k-elementigen Teil mengen“ der Elemente aus lst zurückliefert. ” Beispielanwendung: >>> choice([1,2,3],2) T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 6 / 57 Implementierung von Algorithmen Rekursiv gehts einfacher Die rekursive Implementierung vieler Probleme ist viel einfacher! Beispiel: Zeichnen der Striche auf einem Lineal Rekursive Implementierung: 1 2 from graphics import * linealCanv = GraphWin("Ein Lineal",1000,50) 3 4 5 6 7 def strich(x,h): '''Zeichne Strich an Position x mit Laenge h''' l = Line(Point(x,0),Point(x,h)) l.draw(linealCanv) 8 9 10 11 12 def lineal(l,r,h): ''' Zeichne Lineal zwischen Pos l und r laengster Strich (in der Mitte) hat Hoehe h''' ... T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 7 / 57 Implementierung von Algorithmen Aufgabe 5 Zeichnen Sie durch eine rekursiv definierte Python-Funktion und unter Verwendung der graphics-Bibliothek folgenden Stern: Aufgabe 6 Schreiben Sie eine rekursive Prozedur baum(x,y,b,h) zum Zeichnen eines (binären) Baumes derart, dass die Wurzel sich bei (x,y) befindet, der Baum b breit und h hoch ist. Definieren Sie hierzu eine Python-Prozedur line(x1,y2,x2,y2), die eine Linie (x1,y2) zu (x2,y2) zeichnet. Beispiel für die Ausgabe von baum(0,0,16,4). 1 2 3 4 (0,0) 16 T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 8 / 57 Implementierung von Algorithmen Destruktiv vs. Nicht-Destruktiv sorted(list) ist list.sort() ist destruktiv: >>> l = list('hallo') >>> l.sort() # l wird veraendert >>> l ['a', 'h', 'l', 'l', 'o'] nicht-destruktiv: >>> l = list('hallo') >>> sorted(l) # l unveraendert ['a', 'h', 'l', 'l', 'o'] Vor-/Nachteile Pro Nicht-Destruktiv: Jeder destruktive Update verändert internen Zustand des Programms. Viele Zustände ⇒ viele Abfragen ⇒ viele mögliche Fehler Wenige Zustände ⇒ besserer Überblick ⇒ weniger mögliche Fehler Con Nicht-Destruktiv: ... ? ... T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 9 / 57 Sortieren Insertion-Sort Insertion-Sort – Funktionsweise So sortiert der Kartenspieler: Sukzessive werden Karten vom Stapel in die schon sortierten Karten auf der Hand eingefügt. 1. [53,6,63,94,56,8,72,44,70] 5. [6,53,56,63,94,8,72,44,70] 2. [6,53,63,94,56,8,72,44,70] 6. [6,8,53,56,63,94,72,44,70] 3. [6,53,63,94,56,8,72,44,70] 7. [6,8,53,56,63,72,94,44,70] 4. [6,53,63,94,56,8,72,44,70] 8. [6,8,44,53,56,63,72,94,70] Ergebnis: [6,8,44,53,56,63,70,72,94] T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 10 / 57 Sortieren Insertion-Sort Insertion-Sort – Implementierung Wir teilen auf ins das Einfügen: 1 2 3 def insAtRightPos(alst, key): return [x for x in alst if x <=key] +[key] + [x for x in alst if x>key] und das eigentliche Sortieren – rekursiv implementiert. 1 2 3 def insertionSort(alst): if len(alst)<=1: return alst else: return ... Aufgabe 7 Ersetzen sie die ...“-Stelle in obigem Listing durch den notwendigen ” rekursiven Aufruf. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 11 / 57 Sortieren Insertion-Sort Insertion-Sort – Implementierung in-place 1 2 3 4 5 6 7 8 def insertionSort(lst): for j in range(1,len(lst)): key = lst[j] i = j-1 while i >= 0 and lst[i] > key: lst[i+1] = lst[i] i = i -1 lst[i+1] = key Zwar schneller, aber . . . . . . schwieriger zu implementieren! T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 12 / 57 Sortieren Quicksort Quicksort – Funktionsweise + Implementierung Wähle beliebiges Element lstj mit 0 ≤ j ≤ n − 1 aus (Pivot-Element). Zerteile lst in lstl (alle Elemente < lstj ) und lstr (alle Elemente ≥ lstj ) lstl und lstr werden rekursiv sortiert. Die rekursiv sortierten Teillisten werden einfach zusammengehängt. 1 2 3 4 5 6 def quicksort(lst): if len(lst)<=1: return lst # Rekursionsabbruch pivot = lst[0] lst_l = [a for a in lst[1:] if a <= pivot] lst_r = [a for a in lst[1:] if a > pivot] return ... Aufgabe 8 Vervollständigen Sie die Implementierung von Quicksort an der ...“-Stelle. ” T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 13 / 57 Sortieren Quicksort Quicksort – Implementierung in-place Schneller, aber schwieriger zu implementieren! Die in-place-Partitionierung 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def partitionIP(lst,l,r): pivot=lst[l] i=l-1 Das eigentliche j=r+1 in-place-Sortieren. while True: while True: 1 def quicksortIP(lst,l,r): j=j-1 2 if r>l: if lst[j]<=pivot: break 3 i= partitionIP(lst,l,r) while True: 4 quicksortIP(lst,l,i) i=i+1 5 quicksortIP(lst,i+1,r) if lst[i]>=pivot: break if i<j: lst[i],lst[j]=lst[j],lst[i] else: return j T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 14 / 57 Suchen Heaps Die Heap-Struktur Ein Max-Heap ist . . . . . . ein fast vollständiger Binärbaum. Max-Heap-Eigenschaft: Der Schlüssel eines Knotens ist größer als die Schlüssel seiner beiden Kinder Ein Max-Heap: Ein Min-Heap: 23 13 18 21 9 2 7 4 3 19 6 23 5 29 95 33 64 38 77 98 71 39 76 82 99 Der Max-Heap kann repräsentiert werden als: [None,23,18,21,9,7,19,5,2,4,3,6] T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 15 / 57 Suchen Heaps Heap – Aufgaben Implementierung Aufgabe 9 1 Implementieren Sie die Funktion leftChild(heap,i), die den Wert des linken Kindes von heap[i] zurückgibt. Gibt es kein solches, soll None zurückgeliefert werden. 2 Implementieren Sie die Funktion rightChild(heap,i) entsprechend. 3 Implementieren Sie eine Funktion father(heap,i), die als Argument einen Heap heap und einen Index i übergeben bekommt und den Wert des Vaters von heap[i] zurückliefert. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 16 / 57 Suchen Heaps Heaps – Einfügen Für viele Anwendungen wichtig: Effizientes Extrahieren des größten (kleinsten) Elements. Optimal dafür: Heaps! Einfügen: 13 13 23 29 95 33 77 23 64 38 98 71 39 76 82 13 47 29 95 33 38 77 23 64 47 71 39 76 82 98 29 95 33 47 38 77 64 71 39 76 82 98 Aufgabe 10 Vervollständigen Sie den folgenden Code zur Implementierung der Einfüge-Operation in Heaps. 1 2 3 def insertH(heap, x): heap.append(x) ... T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 17 / 57 Suchen Heaps Heaps – Min-Extrakt 13 1. 23 29 95 33 47 38 77 39 76 72 29 47 38 77 76 47 72 64 71 39 23 3. 23 33 82 72 2. 95 64 71 82 29 95 33 38 77 39 23 82 5. 47 29 38 72 33 76 23 4. 95 64 71 77 71 39 T. Häberlein HS AlbSig 76 29 64 82 33 95 72 47 38 77 64 71 39 Algorithmik mit Python 76 82 Leipzig, 04.04.2011 18 / 57 Suchen Heaps Heaps – Min-Extrakt-Implementierung 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def minExtrakt(heap): returnVal=heap[1] n=len(heap)-1 heap[1]=heap[n] # letztes Element an die Wurzel del(heap[n]) n-=1 # n soll weiterhin auf das letzte Element zeigen i=1 while i<=n/2: j=2*i if j<n and heap[j]>heap[j+1]: j+=1 # waehle kleineres der beiden if heap[i]<=heap[j]: break heap[i],heap[j]=heap[j],heap[i] i=j return returnVal Laufzeit: O(log n) T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 19 / 57 Suchen Heaps Heaps – build-Heap-Implementierung Hintere Hälfte der Liste (also lst[len(lst)/2:]): Sammlung von len(lst)/2 Heaps; noch über den vorderen Teil der Liste laufen und alle verletzten Heap-Bedingungen wiederherstellen. 1 2 3 def buildHeap(lst): # Es muss lst[0]==None gelten for i in range(len(lst)/2,0,-1): minHeapify(lst,i) Laufzeit: O(n) T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 20 / 57 Suchen Heaps Heaps – in Python Die Standard-Modul heapq implementiert Heaps. heapq.heapify(lst): Transformiert die Liste lst in-place in Min-Heap; heapq.heappop(lst): Enfernt kleinestes Element aus Heap lst; heapq.heappush(lst,x): Fügt ein neues Element x in Heap lst ein; T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 21 / 57 Suchen Skip-Listen Skip-Listen Sind ähnlich zu verketteten Listen, . . . . . . außer, dass ein Element mehrere Vorwärtszeiger haben kann. Beispiel: 7 13 19 30 34 32 44 39 62 76 81 91 93 Für Skip-Liste muss gelten: Wkeit, dass zufällig gewählter Eintrag i Vorwärtszeiger hat: 1 2 3 p i−1 · (1 − p) 4 5 Randomisierte Impl. ⇒ T. Häberlein HS AlbSig 6 from random import random p = ... # Wkeit mit 0<p<1 def randHeight(): i=0 while random()<=p: i+=1 return min(i,MaxHeight) Algorithmik mit Python Leipzig, 04.04.2011 22 / 57 Suchen Skip-Listen Skip-Listen – Implementierung 1 2 3 class SLEntry(object): def __init__(self, key, ptrs=[], val=None): self.key = key ; self.ptrs = ptrs ; self.val = val 4 5 6 7 8 9 class SkipList(object): def __init__(self): self.tail = SLEntry(Infty) self.head = SLEntry(None,[self.tail]*(MaxHeight+1)) self.height = -1 ⇒ Eine leere Skipliste hat ein tail – Ein Ende mit key=∞ head – Ein Kopf mit allen Vorwärtszeigern auf tail T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 23 / 57 Suchen Skip-Listen Skip-Listen – Suche Suche nach einem Eintrag mit Schlüssel key: 1 2 3 4 5 6 7 8 9 class SkiptList(object): ... def search(self, key): x = self.head for i in range(self.height,-1,-1): while x.ptrs[i].key < key: x = x.ptrs[i] x = x.ptrs[0] if x.key == key: return x.val else: return None T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 24 / 57 Suchen Skip-Listen Skip-Listen – Einfügen Wähle Höhe i durch Zufallsentscheidung (randHeight) Es müssen i Zeiger anderer Elemente umgebogen“ werden. ” updatePtrs[3] updatePtrs[2] updatePtrs[1] 13 19 7 30 updatePtrs[0] 34 32 39 44 62 76 81 91 93 Der i-te Vorwärtszeiger von updatePtrs[i] muss umgebogen“ werden. ” 13 7 19 30 34 32 39 44 79 62 76 81 91 93 Aufgabe 11 Implementieren Sie eine Methode insert(key,val) der Klasse SkipList T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 25 / 57 Suchen Skip-Listen Skip-Listen – Aufgaben (1) Aufgabe 12 Implementieren Sie die Funktion __str__, so dass Skip-Listen folgendermaßen ausgegeben werden: >>> print skiplist >>> [ (30|1), (33|4), (40|3), (77|1), (98|1), (109|1), (193|3) ] Ausgegeben werden soll also der Schlüssel jedes Elements zusammen mit der Höhe des Elements. Aufgabe 13 1 Schreiben Sie eine Methode keys(), die eine Liste der in der Skip-Liste gespeicherten Schlüsselwerte zurückliefert. 2 Schreiben Sie eine Methode vals(), die eine Liste der in der Skip-Liste gespeicherten Werte zurückliefert. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 26 / 57 Suchen Skip-Listen Skip-Listen – Aufgaben (2) Aufgabe 14 Oft wird eine effiziente Bestimmung der Länge einer Skip-Liste benötigt. Erweitern Sie die Klasse SkipList um ein Attribut length, passen Sie entsprechend die Methoden insert und delete an und geben Sie eine Implementierung der Methode __len__ an, so dass die len-Funktion auf Skip-Listen anwendbar ist. Aufgabe 15 1 Schreiben Sie eine Funktion numHeights(h), die die Anzahl der Elemente mit Höhe n zurückliefert. 2 Schreiben Sie eine Funktion avgHeight(s), die die durchschnittliche Höhe eines Elementes der Skip-Liste s berechnet. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 27 / 57 Suchen Bloomfilter Bloomfilter – Grundlegendes Sehr platz- und zeiteffiziente Möglichkeit des Membership-Tests. Bloomfilter sind probabilistisch: Möglichkeit falsch-positiver Antworten. I I Datensatz in Bloomfilter ⇒ Antwort immer korrekt! Datensatz nicht in Bloomfilter ⇒ Antwort nicht immer korrekt! Eine Anwendung – unter vielen: w ∈ S? nein Bloomfilter ja y ∈ S? nein langsamer Speicher ja x ∈ S? T. Häberlein HS AlbSig nein ja Algorithmik mit Python z ∈ S? Leipzig, 04.04.2011 28 / 57 Suchen Bloomfilter Bloomfilter – Funktionsweise Bsp: h0 (eine) = 3, h0 (Einführung) = 1, h0 (Informatik) = 6 h1 (eine) = 1, h1 (Einführung) = 8, h1 (Informatik) = 7 0 1 2 3 4 5 6 7 8 9 false false false false false false false false false false Einfügen von eine ) ne ei h 1( ne ei h 0( ) 0 1= 2 3= 4 5 6 7 8 9 false true false true false false false false false false ) ) ng ng Einfügen von ru ru h h ü ü nf nf Einführung Ei Ei ( ( 0 1 h h 0 1= 2 3 4 5 6 7 8= 9 false true false true false false false false true false k) k) ti ati a m Einfügen von r rm fo nfo n I I Informatik h 0( h 1( 0 1 2 3 4 5 6= 7= 8 9 false true false true false false true true true false T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 29 / 57 Suchen Bloomfilter Bloomfilter – Implementierung 1 2 3 4 5 class BloomFilter(object): def __init__(self, h, m): self.k = len(h) ; self.h = h self.A = [False]*m self.m = m 6 7 8 9 10 def insert(self,x): ... # Siehe Aufgabe def elem(self,x): ... # Siehe Aufgabe Aufgabe 16 Implementieren Sie insert und elem. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 30 / 57 Suchen Suchmaschinen Suchmaschinen Aufbau einer Suchmaschine Web Datenbank Crawler Dateisystem Indexer Index Suchanfrage Bearbeitung GUI Der invertierte Index . . . das Herz“ jeder Suchmaschine! ” Liste aller Wörter Algorithmik mit Python [10,...] T. Häberlein HS AlbSig [430,102,344,982, ...] [101,72,...] ... Hashtabelle Heap Heapsort Hornerschema Insertion Sort ... Leipzig, 04.04.2011 31 / 57 Suchen Suchmaschinen Implementierung 1 1 2 3 4 5 6 class Index(object): 2 def __init__(self, path=''): 3 self.docId = 0 4 self.ind = {} 5 self.docInd = {} 6 if path!='': self.crawl(path)7 8 1 2 3 4 5 6 7 8 9 10 11 def crawl(self, path): def tupl(x,y): return (x,y) for dirpath, dirnames, filenames \ in os.walk(path): for file in filenames: f = os.path.join(dirpath, file) if isTxt(f): self.addFile(f) def addFile(self, file): def tupl(x,y): return (x,y) self.docInd[self.docId] = file fileHandle = open(file) fileCont = fileHandle.readlines() ; fileHandle.close() fileCont = map(tupl, xrange(0,len(fileCont)), fileCont) words = [(word.lower(),pos) for (pos,line) in fileCont for word in line.split() if len(word) >=3 and word.isalpha() ] for word,pos in words: self.toIndex((word,pos), self.docId) self.docId+=1 T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 32 / 57 Suchen Suchmaschinen Aufgaben Aufgabe 17 Implementieren Sie die fehlende Methode toIndex((word,pos),docId) T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 33 / 57 Graphen Grundlagen Repräsentation von Graphen Der folgende Graph . . . 1 mit: 2 5 3 4 G = (V , E ) mit V = {1, 2, 3, 4, 5}, E = {(1, 2), (2, 2), (2, 3), (1, 3), (1, 4), (3, 4), (4, 5)} . . . kann repräsentiert werden als . . . Adjazenzliste Adjazenzmatrix 0 1 1 0 1 1 0 0 0 0 0 0 1 0 0 T. Häberlein HS AlbSig 1 0 1 0 0 0 0 0 1 0 1 2 3 4 5 Algorithmik mit Python {2, 3, 4} {2, 3} {4} {5} {1} Leipzig, 04.04.2011 34 / 57 Graphen Grundlagen Repräsentation in Python 1 2 3 4 5 6 class Graph(object): def __init__(self,n): self.vertices = [] self.numNodes = n for i in range(0,n+1): self.vertices.append({}) self.vertices ist die Adjazenzliste... . . . deren Einträge dict-Objekte sind . . . . . . die adjanzente Knoten (inkl. evtl. Gewichte) enthalten. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 35 / 57 Graphen Grundlagen Wichtige Methoden (1) Aufgabe 18 Implementieren Sie die Graph-Methoden addEdge, isEdge, G, V, E und füllen sie hierzu die Lücken in folgendem Listing: 1 2 3 4 5 6 7 8 9 10 11 12 class Graph(object): ... def addEdge(self,i,j,weight=None): ... def isEdge(self,i,j): ... def G(self,i): ... def V(self): ... def E(self): ... T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 36 / 57 Graphen Grundlagen Wichtige Methoden (2) Aufgabe 19 Erweitern Sie die Klasse Graph um die Methode Graph.w(i,j), die das Gewicht der Kante (i, j) zurückliefert (bzw. None, falls die Kante kein Gewicht besitzt). Aufgabe 20 Erweitern Sie die Klasse Graph um die folgenden Methoden: 1 Eine Methode Graph.isPath(vs), die eine Knotenliste vs übergeben bekommt und prüft, ob es sich hierbei um einen Pfad handelt. 2 Eine Methode Graph.pathVal(vs), die eine Knotenliste vs übergeben bekommt. Handelt es sich dabei um einen gültigen Pfad, so wird der Wert“ dieses Pfades (d. h. die Summe der Gewichte der Kanten des ” Pfades) zurückgeliefert. Andernfalls soll der Wert ∞ (in Python: float('inf')) zurückgeliefert werden. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 37 / 57 Graphen Kürzeste Wege Algorithmus von Warshall Berechnet die kürzesten Wege zwischen allen Knotenpaaren. Berechnungsschema: 1 2 3 Berechne W0 : alle kürzesten Wege mit keinen Zwischenknoten“ ” Aus Wk−1 berechnet Wk : alle kürzesten Wege mit Zwischenknoten ∈ {1, . . . , i} Lösung: Wn Schritt von Wk−1 nach Wk : Pfad mit Knoten aus {1, . . . , k} k i j Pfad mit Knoten aus {1, . . . , k − 1} Es gilt: Wk [i, j] := min{ Wk−1 [i, j], Wk−1 [i, k] + Wk−1 [k, j] } T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 38 / 57 Graphen Kürzeste Wege Warshall Implementierung 1 2 3 4 def warshall(graph): n = graph.numNodes+1 W = [ [graph.w(i,j) for j in graph.V()] for i in graph.V() ]# W_0 ... W: Adjazenzmatrix des Graphen, also W0 . Aufgabe 21 Vervollständigen Sie die Implementierung des Warshall-Algorithmus, d. h. ersetzen sie die ...-Stelle durch Code, der Sukzessive W1 , W2 , . . . , Wn berechnet. Aufgabe 22 Was ist die Laufzeit des Warshall-Algorithmus? D. h. wie viele Berechnungsschritte – in der O-Notation – benötigt der Warshall-Algorithmus zur Berechnung der kürzesten Wege eines Graphen mit n Knoten? T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 39 / 57 Graphen Kürzeste Wege Der Dijkstra-Algorithmus Edsger Dijkstra (1930 - 2002) Berechnet nicht alle Abstände zwischen allen Knoten, sondern . . . . . . berechnet – ausgehend von einem Knoten v – die Abstände l[u] (Länge der kürzesten Wege) zu allen Knoten u ∈ V . T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 40 / 57 Graphen Kürzeste Wege Der Dijkstra-Algorithmus – Funktionsweise l[c]=11 l[u]=0 l[u]=0 a 3 Dijkstra ist greedy: Knoten werden sukzessive abgehakt“: ” I I Es kommt immer derjenige Knoten an die Reihe, der momentan den geringsten l-Wert hat. Es wird versucht, die Abstände aller seiner Nachbarn zu verbessern. 2 11 c 4 4 5 u 2 3 7 1 5 b 10 d 2 e l[c]=11 l[u]=0 3 2 11 c 4 4 5 u 10 d 2 l[d]=4 e f 1 g 8 l[c]=9 3 2 4 5 4 u 10 l[b]=14 d l[f ]=2 2 2 l[d]=4 f 3 4 l[g ]=5 5 4 u l[f ]=2 2 f 3 7 1 5 10 d 2 l[d]=4 l[a]=11 a 1 8 l[e]=6 g l[g ]=3 W = {a, b, c, e} T. Häberlein HS AlbSig 8 l[e]=7 11 c 2 e g 8 l[e]=7 l[c]=9 l[g ]=3 Algorithmik mit Python 3 4 l[u]=0 11 c 3 7 e 2 g l[c]=11 l[u]=0 2 l[g ]=3 5 b e W = {a, b, c, d, e} l[u]=0 11 c d 10 b W = {a, b, c, d, e, g } a 1 5 a 3 l[e]=7 f 3 7 4 5 l[f ]=2 2 u W = {a, b, c, d, e, f , g } 5 b 4 l[f ]=2 2 7 11 c l[d]=4 W = {a, b, c, d, e, f , g , u} a 3 b g 8 2 a f 5 4 u l[f ]=2 2 f 3 7 1 5 b 10 l[b]=13 d 2 l[d]=4 e 8 l[e]=6 g l[g ]=3 W = {a, b} Leipzig, 04.04.2011 41 / 57 Graphen Kürzeste Wege Der Dijkstra-Algorithmus – Implementierung 1 2 3 4 5 6 7 8 9 10 def dijkstra(u,graph): n = graph.numNodes l = { u : 0 } ; W = graph.V() F = [] ; k = {} for i in range(0,n): lv,v = min([ (l[lk],lk) for lk in l.keys() if lk in W ]) W.remove(v) if v!=u: F.append(k[v]) ... return l,F W: Menge der noch zu bearbeitenden Knoten k[v]: Vorgängerknoten von v auf kürzestem Weg nach v (vorläufig). F: Vorgängerknoten von v auf kürzestem Weg nach v (final). Aufgabe 23 Vervollständigen Sie die Implementierung des Dijkstra-Algorithmus. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 42 / 57 Graphen Minimaler Spannbaum Minimaler Spannbaum – Kruskal-Algorithmus Spannbaum = ˆ Teilgraph GT = (VT , ET ) eines ungerichteten zusammenhängenden Graphen G = (V , E ), der ein Baum (also kreisfrei und zusammenhängend) ist. a d b f g c e h a d b c f g e h a d b c f h g e Anwendungen: I I Möglichst preisgünstiges zusammenhängiges Netzwerk. Vermeidung von redundanten Sendepfaden. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 43 / 57 Graphen Minimaler Spannbaum Kruskal-Algorithmus – Funktionsweise Algorithmus ist greedy: In jedem Schritt wird immer genau eine Kante hinzugenommen, für die gilt: I minimales Gewicht + ohne dass Kreis entsteht. . . . solange, dass bis dies nicht mehr geht. ⇒ Minimaler Spannbaum gefunden. 3 1 2 2 2 3 4 2 4 5 1 3 1 1 3 5 8 7 1 1 2 2 1 9 3 2 3 4 4 2 5 1 1 6 3 1 2 3 2 2 1 4 2 1 3 3 5 8 7 1 1 1 9 3 2 2 2 3 4 2 3 1 3 3 5 1 8 7 1 1 9 6 T. Häberlein HS AlbSig 2 3 2 2 1 4 2 1 5 1 8 5 3 7 1 1 9 3 6 4 5 4 1 1 6 4 5 1 1 3 3 3 5 8 7 1 1 1 9 6 Algorithmik mit Python 2 3 2 2 1 4 2 4 5 1 1 3 5 8 7 3 1 1 9 6 Leipzig, 04.04.2011 44 / 57 Graphen Minimaler Spannbaum Kruskal-Algorithmus – Implementierung 1 2 3 4 5 6 7 8 9 def kruskal(graph): allEdges = [(graph.w(i,j),i,j) for i,j in graph.E_undir()] allEdges.sort(reverse=True) # absteigend spannTree = [] while len(spannTree) < len(graph.V())-1 and allEdges!=[]: (w,i,j) = allEdges.pop() if not buildsCircle(spannTree,(i,j)): spannTree.append((i,j)) return spannTree Aber: 1 Sortieren aller Kanten:O(|E | log |E |) ⇒ ineffizienter als Verwendung eines Heap: O(|E | + |V | log |E |) 2 Implementierung von buildsCircle? T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 45 / 57 Graphen Minimaler Spannbaum Union-Find-Datenstruktur 1 2 3 4 5 6 7 8 9 1 2 3 4 5 7 8 9 3 4 5 7 8 3 4 5 (a):find(3) ∪ find(6) 6 1 Bietet effiziente Implementierung der Mengenoperationen . . . 1 2 . . . Vereinigung“ (zweier Mengen) ” union(x,y) – x und y eindeutige Repräsentanten einer Menge . . . Suche“ eines Elementes in ” einer Menge – find(x) – liefert eindeutigen Repräsentanten der Menge, die x enthält. 2 6 1 9 2 7 6 2 3 5 8 9 2 5 (e):find(5) ∪ find(6) 7 3 4 8 6 9 (f):find(1) ∪ find(3) 2 5 1 7 3 4 8 6 Gleichzeitig: Effizienter Test, ob durch das Hinzufügen einer Kante (i, j) ein Kreis entsteht. (d):find(4) ∪ find(8) 7 4 6 1 (c):find(7) ∪ find(9) 8 9 1 (b):find(8) ∪ find(9) 9 (g):find(2) ∪ find(1) 5 2 1 7 3 4 6 8 9 (h):find(6) ∪ find(7) 7 4 5 2 1 3 8 9 6 T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 46 / 57 Graphen Minimaler Spannbaum Union-Find im Kruskal-Algorithmus Kante {i, j} hinzufügen? Zwei Fälle: 1 Falls find(i)==find(j): Nicht hinzufügen! Denn: i und j befinden sich in derselbsen Zusammenhangskomponente. ⇒ i und j verbunden. ⇒ Es würde ein Kreis entstehen. I I 2 Falls find(i)!=find(j): Hinzufügen! Denn: i und j bisher nicht verbunden. ⇒ Es entsteht kein Kreis I I T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 47 / 57 Graphen Minimaler Spannbaum Union-Find Implementierung self.parent: Speichert für jeden Knoten den Elternknoten. 1 self.parent[i]== 0 gdw. i hat 2 keinen Elternknoten. 3 class UF(object): def __init__(self,n): self.parent = [0]*n 4 find(x): Liefert Wurzel des Baumes, der x enthält. union(x,y): fügt zwei Bäume zusammen, indem die Wurzel des einen Baumes (der y enthält) als Kind unter die Wurzel des anderen Baumes (der x enthält) gehängt wird. T. Häberlein HS AlbSig 5 6 7 8 def find(self,x): while self.parent[x] > 0: x = self.parent[x] return x 9 10 11 def union(self,x,y): self.parent[y] = x Algorithmik mit Python Leipzig, 04.04.2011 48 / 57 Graphen Minimaler Spannbaum Union-Find – Aufgaben/Verbesserungen Aufgabe 24 Implementieren Sie für die Klasse UF die str-Funktion, die ein Objekt der Klasse in einen String umwandelt. Beispiel-Ausgabe: >>> >>> >>> >>> uf = UF(10) uf.union(1,2) ; uf.union(1,3) ; uf.union(5,6) ; uf.union(8,9) str(uf) '{1, 2, 3} {4} {5, 6} {7} {8, 9} ' Eine sehr nützliche Verbesserung: Balancierung! Aufgabe 25 Verbessern Sie die Union-Find-Impl. indem Sie auf die Balancierung der Bäume achten. find(x) sollte nur dann als Kind unter die Wurzel von find(y) gehängt werden, wenn Höhe(find(x)) < Höhe(find(y)); andernfalls: find(y) unter die Wurzel von find(x) hängen. Tipp: (Negative) Höhe in der Wurzel speichern. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 49 / 57 Graphen Minimaler Spannbaum Union-Find – Aufgaben/Verbesserungen Eine weitere sehr nützliche Verbesserung: Pfad-Komprimierung! Aufgabe 26 find(x) findet immer den Pfad von x zur Wurzel. ⇒ füge danach Direktkante von x zur Wurzel ein. . . . und auch Direktkanten für alle Knoten auf dem Pfad. Implementieren Sie diese Verbesserung. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 50 / 57 Graphen Minimaler Spannbaum Kruskal-Algorithmus – Aufgabe Aufgabe 27 Implementieren Sie den Kruskal-Algorithmus unter Verwendung der Union-Find-Datenstruktur. Aufgabe 28 Man kann den minimalen Spannbaum auch finden, indem man genau umgekehrt wie der Kruskal-Algorithmus vorgeht: Man beginne mit allen im Graphen enthaltenen Kanten und entfernt Kanten mit dem momentan höchsten Gewicht – nur dann aber, wenn man dadurch den Graphen nicht auseinander bricht. Geben Sie eine Implementierung des umgekehrten“ Kruskal-Algorithmus ” an. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 51 / 57 Schwere Probleme Lösung des Traveling Salesman Problems Das TSP-Problem Hamburg Bremen TSP = ˆ Travelling Salesman Problem. Gegeben: n Städte Gesucht: Kürzeste Rundtour, die jede Stadt genau einmal besucht. Berlin Hannover Bielefeld Dortmund Bochum Duisburg Essen Wuppertal Düsseldorf Köln Bonn Leipzig Dresden Frankfurt am Main Mannheim Nürnberg Stuttgart München Lösung des TSP für die 20 größten Deutschen Städte. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 52 / 57 Schwere Probleme Lösung des Traveling Salesman Problems TSP-Lösung durch Ausprobieren Lösung des TSP = ˆ Permutation der n Städte. ⇒ Durchprobieren aller Permutationen perms(graph.V()) Aufgabe 29 Implementieren Sie den Brute-Force-Lösungsansatz Durchprobieren aller ” Permutationen“, um die optimale Lösung des TSP zu finden und vervollständigen Sie hierzu den folgenden Code: 1 2 3 def TSPBruteForce(graph): nodeList = graph.V()[1:] return ... Tipp: Verwenden Sie graph.pathVal um die Länge eines Pfades zu bestimmen. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 53 / 57 Schwere Probleme Lösung des Traveling Salesman Problems TSP-Lösung durch Dynamische Programmierung Fürs TSP gilt das sog. (Bellmannsche) Optimalitätsprinzip: Eine optimale Lösung setzt sich zusammen aus . . . . . . kleineren“ optimalen Lösungen. ” ⇒ Lösung durch Dynamische Programmierung möglich: Zuerst: Lösungen der kleinen“ Teilprobleme berechnen . . . ” . . . und Zwischenergebnisse in Tabelle speichern. Bei Berechnung der größeren Teilprobleme: Auf Tabelle zurückgreifen. Fürs TSP gilt: T (i, S): Wert der kürzesten Tour, startend bei Knoten i, die alle Knoten aus S genau einmal besucht und bei Knoten 1 endet Dann gilt: T (i, S) = min w (i, j) + T (j, S \ {j}) j∈S T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 54 / 57 Schwere Probleme Lösung des Traveling Salesman Problems TSP-Lösung durch Dynamische Programmierung (2) Die Formel T (i, S) = min w (i, j) + T (j, S \ {j}) j∈S Entspricht in Python (T ist Dict-Objekt): T[(i,S)] = min( graph.w(i,j)+T[(j,diff(S,[j]))] for j in S) 1 2 3 4 5 6 def tsp(graph): n = graph.numNodes T = {} for i in range(1,n+1): T[(i,())] = graph.w(i,1) for k in range(1,n-1): ... Aufgabe 30 Vervollständigen Sie die Implementierung und ersetzen Sie die ... durch den entsprechenden Code. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 55 / 57 Schwere Probleme Greedy-Heuristiken zur Lösung des TSP Nearest-Neighbor-Heuristik Von der aktuellen Stadt aus . . . . . . wählt man einfach immer die nächste aus. Aufgabe 31 Implementieren Sie die Nearest-Neighbor-Heuristik für das TravelingSalesman-Problem und testen Sie diese durch Berechnung der kürzesten Tour durch die . . . 1 . . . größten 20 deutschen Städte. 2 . . . größten 40 deutschen Städte. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 56 / 57 Schwere Probleme Greedy-Heuristiken zur Lösung des TSP Insertion-Heuristiken Man beginnt mit sehr kurzer (2 Städte) Tour . . . . . . und fügt sukzessive weitere Knoten hinzu. Folgende Strategien: Nearest Insertion“: Als nächtes wird derjenige Knoten hinzugefügt, ” der zur momentanen Tour den geringsten Abstand hat. ”Farthest Insertion”: Als nächtes wird derjenige Knoten hinzugefügt, der zur momentanen Tour den größten Abstand hat. ”Random Insertion”: Als nächtes wird zufällig ein noch nicht in der Tour befindlicher Knoten zur Tour hinzugfügt. Aufgabe 32 Implementieren Sie die Nearest/Farthest/Random-Insertion-Heuristik. T. Häberlein HS AlbSig Algorithmik mit Python Leipzig, 04.04.2011 57 / 57