h.hofstede (h.hofstede@hogeland.nl)

Grafen doorzoeken.
       
Als je een grote graaf wilt doorzoeken  (op zoek naar een knooppunt of verbindingslijn met een bepaalde eigenschap) dan is het wel handig als je dat op een systematische en efficinte manier kunt doen. Dat gaat uiteindelijk toch vaak met een computer, dus het is wel handig om een soort zoek-algoritme te hebben.

Nou zijn er daarvoor twee soorten op de markt:  Diepte-eerst  (Depth-First-Search; DFS) en Breedte-eerst (Breadth-First Search; BFS).De Engelse benamingen zijn zo algemeen dat ik ze overneem, en het zal hebben over DFS- en BFS-Search.

We nemen aan dat de grafen die we onderzoeken samenhangend zijn (als dat niet zo is moeten we de afzonderlijke componenten apart onderzoeken)  en geen lussen hebben  (als die er wel zijn laten we ze gewoon weg!).
       
Depth-First-Search (DFS).
       
Het DFS-algoritme is een beetje verschillend voor gerichte en ongerichte grafen, dus die bekijken we apart.
       
1.  Ongerichte grafen.

De knooppunten noemen we Zoon (Z) of Vader (V) waarbij we onthouden welke vader bij welke zoons hoort.
In het begin noemen we alle verbindingslijnen bruikbaar maar in de loop van de zoektocht kunnen die veranderen in  onbruikbaar of onderzocht.

De zoekmethode werkt als volgt:

1. Kies een willekeurig beginknooppunt  en geef het nummer 1 .
2. Kies een willekeurige bruikbare verbindingslijn vanaf dat knooppunt.
Als je bij een knooppunt komt dat al eerder een nummer kreeg is, dan ga je terug en noem je de gebruikte verbindingslijn onbruikbaar en begint weer bij 2.
Als het knooppunt nog geen nummer heeft, dan geeft je het het volgende nummer, en je onthoudt wat de vader was (dat was het knooppunt waar je vandaan kwam).
Maak de verbindingslijn V-Z gericht (van V naar Z) en die verbindingslijn noemen we vanaf nu onderzocht.

3.

Herhaal stap 2  vanaf het nieuwe knooppunt.  Net zolang totdat er geen verbindingslijnen meer te vinden zijn.

4.

Als er geen verbindingslijnen meer te vinden zijn, dan ga je terug naar de vader van dit knooppunt en begint vanaf daar weer met stap 2.  Als je al bij het beginknooppunt bent (dus als er gaan vader meer is), ben je klaar.
       
In een stroomdiagram zou dat er z uitzien: 
(bij het paarse blokje gaan we een stapje "dieper" de graaf in, bij het blauwe blokje nemen we een stapje "terug")
       

       
Hieronder zie je in 12 plaatjes het algoritme in werking (leesvolgorde).
Elke keer als een stippellijn verschijnt is een verbindingslijn gekozen die naar een knooppunt leidde dat al een nummer had.
Waarschijnlijk snap je nu ook waarom het Depth-First heet:  we gaan eerst zo diep mogelijk de graaf in!
       

       
De rode verbindingslijnen leveren uiteindelijk een gerichte graaf met alle knooppunten op, en die wordt wel een DFS-boom van de graaf genoemd. Je ziet dat hij bestaat uit "routes van familieleden". Twee routes in dit geval. Bij het volgen van een route ga je steeds van vader naar zoon.

Deze zoekmethode heet ook wel een driekleuren-algoritme:  er zijn op elk moment drie soorten knooppunten: 
  witte:  nog niet bezocht
  grijze:  wel bezocht maar nog niet afgesloten
  zwarte:  afgesloten.
Dan ziet het er z uit:
       

       
2.  Gerichte grafen.
       
Daarvoor kun je bijna hetzelfde algoritme gebruiken, met n klein verschilletje.
Het kan namelijk gebeuren dat je vanaf een knooppunt geen nieuwe knooppunten meer kunt bereiken, maar dat de graaf toch nog niet "klaar" is.
Kijk maar naar het graafje hiernaast.
Als je aan de linkerkant begint zul je nooit de rechterkant bereiken.  Na drie knooppunten wil het niet verder.
In die gevallen moet je gewoon ergens bij een nieuwe willekeurige nog niet bezochte knoop opnieuw beginnen. Ofwel  na de STOP  uit het stroomdiagram moet je kijken of alle knopen een nummer hebben, en als dat niet zo is er eentje zonder nummer kiezen en doorgaan.

       
In zo'n geval zal het eindresultaat niet uit n boom bestaan maar uit verschillende losse bomen. Dat heeft een bos.

Mooi meegenomen:
Bij grafen die uit verschillende losse componenten bestaan vind je op deze manier meteen hoeveel zulke componenten er zijn  (namelijk gewoon door te kijken hoe vaak je "opnieuw" moest beginnen).
       
Cykels vinden.
       
Deze DFS-zoektocht levert meteen bij gerichte grafen een mooie manier op om cykels te vinden als die er zijn.
Stel dat de nieuwe lijn die je onderzoekt  van knooppunt x naar knooppunt y loopt. Dan zijn er 3 mogelijkheden:
y is n van de zonen van x   Dan heet de lijn xy een  vooruit-lijn (forward-edge). 
Het nummer van x is dan kleiner dan dat van y.
x is n van de zonen van y.  Dan heet lijn xy een terug-lijn (back-edge).  Het nummer van y is dan kleiner dan dat van x
x en y zijn geen familie van elkaar.  Het nummer van  y is dan kleiner dan dat van x  immers y was al eerder bereikt. 
Dat noemen we een kruis-lijn (cross-edge).
       

       
En nu hebben we meteen ontdekt of er cykels zijn:
       

er is een cykel   ⇔  er is een terug-lijn geweest bij de DFS

       
Voorbeeld.
       
Hieronder zie je een graaf, met daaronder het DFS-algoritme in werking (in leesvolgorde, grijs-wit-zwart zoals hierboven omschreven)
Rode verbindingslijnen komen in de uiteindelijke boom/bos.
Blauwe lijnen zijn terug-lijnen en geven cykels.
Groene lijnen zijn vooruit-lijnen.
Paarse lijnen zijn kruis-lijnen.

Uiteindelijk levert het een bos (van twee bomen) en een cykel op.
       
       
Tuurlijk, ik weet het:  die cykel en dat bos had je z in n oogopslag ook wel gezien. Maar bedenk je steeds dat die algoritmes zijn bedoeld om door een computer uitgevoerd te worden.  Vraag je maar eens af of je in een gerichte graaf met zo'n 1000 knooppunten ook zo snel een cykel  kunt vinden en kunt zien uit hoeveel bomen het bos bestaat......
       
       
  OPGAVEN
       
1. Je hebt 3 kannen met inhoud 8, 5 en 3 liter.
De grootste kan zit vol met 8 liter water, en door water heen en weer te gaan gieten wil je graag die 8 liter verdelen in tweemaal 4 liter. De kannen hebben geen maatstreepjes, dus je moet een kan steeds helemaal leeggieten of helemaal vullen.

Geef een "watertoestand" aan met de drie hoeveelheden in de drie kannen (volgorde 8-5-3).
De begintoestand is dus (8,0,0), en de eindtoestand moet (4,4,0) worden.

Hieronder zie je het begin van een graaf waarin alle mogelijke toestanden als knooppunten staan aangegeven.
Verbind in deze graaf de toestanden die in elkaar over kunnen gaan en los daarna met het  DFS-algoritme dit gietprobleem op.
       
 

       
     
       
     

h.hofstede (h.hofstede@hogeland.nl)