Deep Dive In Graph Traversals

Der er over 2,07 mia. Månedlige aktive Facebook-brugere over hele verden fra 3. kvartal 2017. Det vigtigste aspekt af Facebook-netværket er det sociale engagement mellem brugerne. Jo flere venner en bruger har, jo mere engagerende bliver samtalerne via kommentarer til indlæg, beskeder osv. Hvis du har brugt Facebook ret regelmæssigt, skal du vide om funktionen Venneranbefaling.

Facebook anbefaler et sæt mennesker, som vi kan tilføje som venner. Oftest er det folk, vi aldrig har hørt om før. Men alligevel mener Facebook, at vi skal tilføje dem. Spørgsmålet er: hvordan kommer Facebook med et sæt anbefalinger til en bestemt person?

En måde at gøre dette på er baseret på gensidige venner. f.eks: - Hvis en bruger A og C ikke kender hinanden, men de har en gensidig ven B, skal sandsynligvis også A og C være venner. Hvad hvis A og C har 2 fælles venner og A og D har 3 fælles venner? Hvordan bliver bestillingen til forslag?

I dette tilfælde virker det temmelig indlysende at foreslå D over C til A, fordi de har flere gensidige venner og er mere tilbøjelige til at få forbindelse.

Imidlertid har to mennesker muligvis ikke altid gensidige venner, men de har muligvis fælles forbindelser i 2. grad eller 3. grad.

Nth-gradstilslutninger

  • A og B er venner. (0 grad)
  • A og B er 1.-grad venner betyder, at de har en gensidig ven.
  • A og B er 2.-grad venner, hvis de har en ven, der er en 1.-grad ven med den anden person. f.eks: - A - C - D - B, så er A og B 2.-grad venner.
  • Tilsvarende er A og B venner i Nth-grad, hvis de har N-forbindelser imellem. f.eks: - A - X1 - X2 - X3… .. - XN - B.

Ser vi på denne tilgang til anbefalingen, er vi nødt til at kunne finde den grad af venskab, som to givne brugere deler på Facebook.

Indtast grafversioner

Nu hvor vi ved, hvordan venneanbefalinger kan gøres, lad os omformulere dette problem, så vi kan se på det fra et algoritmisk perspektiv.

Lad os forestille os en ikke-rettet graf over alle brugere på Facebook, hvor knudepunkter V repræsenterer brugerne og kanter E repræsenterer venskaber. Med andre ord: hvis brugere A og B er venner på Facebook, er der en kant mellem toppunktene A og B. Udfordringen er at finde ud af, hvor stor en forbindelse der er mellem to brugere.

Mere formelt er vi nødt til at se den korteste afstand mellem to noder i en ikke-rettet, uvægtet graf.

Overvej to højdepunkter i denne ikke-rettede graf A og C. Der er to forskellige stier til at nå C:

1. A → B → C og
2. A → G → F → E → D → C

Vi ønsker helt klart at gå den mindste vej, når vi prøver at se graden af ​​forbindelse mellem to mennesker på det sociale netværk.

Så langt så godt.

Lad os se på kompleksiteten af ​​dette problem, før vi fortsætter. Som nævnt tidligere har Facebook omkring 2,07 milliarder brugere fra 3. kvartal 2017. Det betyder, at vores graf vil have omkring 2,07 milliarder knuder og mindst (2,07 milliarder - 1) kanter (hvis hver person har mindst en ven).

Dette er en enorm skala at løse dette problem på. Derudover så vi også, at der muligvis er flere stier, der kan nås fra en given kilde-toppunkt til et destinations-toppunkt i grafen, og vi ønsker, at den korteste vej skal løse vores problem.

Vi vil se på to klassiske graftraversalgoritmer for at løse vores problem:

1. Dybde Første søgning og
2. Bredde første søgning.

Dybde Første søgning

Forestil dig, at du sidder fast i en labyrint som denne.

Du er nødt til at komme ud på en eller anden måde. Der kan være flere ruter fra din udgangsposition til udgangen. Den naturlige tilgang til at komme ud af labyrinten er at prøve alle stier.

Lad os sige, at du har to valg på det punkt, hvor du i øjeblikket står. Naturligvis ved du ikke, hvilken der fører ud af labyrinten. Så du beslutter dig for at tage det første valg og gå videre i labyrinten.

Du fortsætter med at gøre træk, og du fortsætter med at komme videre, og du rammer en blindgyde. Nu vil du ideelt set prøve en anden sti, så du går tilbage til et tidligere kontrolpunkt, hvor du har taget et af valgene og derefter prøv en ny, dvs. en anden sti denne gang.

Du fortsætter med at gøre dette, indtil du finder udgangen.

Rekursivt afprøvning af en bestemt sti og backtracking er de to komponenter, der danner Depth First Search-algoritmen (DFS).

Hvis vi modellerer labyrintproblemet som en graf, ville verticerne repræsentere individets position på labyrinten, og dirigerede kanter mellem to noder ville repræsentere en enkelt bevægelse fra en position til en anden position. Ved hjælp af DFS vil den enkelte prøve alle mulige ruter, indtil udgangen er fundet.

Her er en prøve-pseudokode for den samme.

1 procedure DFS (G, v):
2 etiket v som opdaget
3 for alle kanter fra v til w i G.adjacentEdges (v) gør
4 hvis toppunkt w ikke er mærket som opdaget da
5 rekursivt kalde DFS (G, w)

For en dybere dybning i denne algoritme, tjek: -

Tidskompleksitet: O (V + E)

Bredde første søgning

Forestil dig en smitsom sygdom, der gradvist spreder sig over en region. Hver dag inficerer de mennesker, der har sygdommen, nye mennesker, de kommer i fysisk kontakt med. På denne måde foretager sygdommen en slags bredde-første-søgning (BFS) over befolkningen. "Køen" er det sæt mennesker, der netop er blevet inficeret. Grafen er det fysiske kontaktnetværk i regionen.

Forestil dig, at du er nødt til at simulere spredningen af ​​sygdommen gennem dette netværk. Rodenoden i søgningen er patient nul, den første kendte syge. Du starter med bare dem med sygdommen og ingen andre.

Nu gentager du de mennesker, de er i kontakt med. Nogle vil fange sygdommen. Nu gentages over dem alle. Giv også de mennesker, de er i kontakt med sygdommen, medmindre de allerede har haft den. Fortsæt, indtil du har inficeret alle, eller du har inficeret dit mål. Så er du færdig. Sådan fungerer bredde-første-søgning.

BFS-søgerealgoritmen udforsker toppunkt lag for lag, der starter ved det allerførste toppunkt og bevæger sig først til det næste lag, når alle vertikater på det aktuelle lag er blevet behandlet.

Her er en prøve-pseudokode til BFS.

1 procedure BFS (G, v):
2 q = kø ()
3 q.enqueue (v)
4 mens q ikke er tom:
5 v = q.dequeue ()
6, hvis v ikke besøges:
7 mark v som besøgt (// Behandle noden)
8 for alle kanter fra v til w i G.adjacentEdges (v) gør
9 q.enqueue (w)

Se denne artikel for en dybere forståelse af BFS.

Tidskompleksitet: O (V + E)

Korteste stier

Lad os komme videre og løse vores oprindelige problem: at finde den korteste sti mellem to givne hjørner i en ikke-rettet graf.

Ser vi på tidskompleksiteterne for de to algoritmer, kan vi ikke rigtig finde ud af forskellen mellem de to for dette problem. Begge algoritmer finder en sti (eller rettere den korteste sti) til vores destination fra den givne kilde.

Lad os se på følgende eksempel.

Antag, at vi vil finde ud af den korteste sti fra knudepunktet 8 til 10. Lad os se på de knudepunkter, som DFS og BFS udforsker, før vi når destinationen.

DFS

  • Process 8 → Process 3 → Process 1.
  • Backtrack til 3.
  • Proces 6 → Proces 4.
  • Backtrack til 6.
  • Proces 7.
  • Backtrack til 6 → Backtrack to 3 → Backtrack til 8.
  • Proces 10.

I alt 7 noder behandles her, inden destinationen nås. Lad os nu se på, hvordan BFS gør tingene.

BFS

  • Proces 8 → Enqueue 3, 10
  • Proces 3 → Enqueue 1,6
  • Proces 10.

Woah, det var hurtigt! Blot 3 noder måtte behandles, og vi var på vores destination.

Forklaringen på denne speedup, som vi kan se i BFS og ikke i DFS, er fordi DFS tager en bestemt sti og går helt til slutningen, dvs. indtil den rammer en blindgyde og derefter backtracks.

Dette er den største undergang af DFS-algoritmen. Det kan være nødvendigt at udvide 1000'erne af niveauer (i et kæmpe netværk som Facebook, bare fordi det valgte en dårlig sti at behandle i starten) før den når stien, der indeholder vores destination. BFS står ikke over for dette problem, og det er derfor meget hurtigere for vores problem.

Selv hvis DFS finder ud af destinationen, kan vi ikke være sikker på, at den sti, der er taget af DFS, er den korteste. Der kan også være andre stier.

Det betyder, at DFS under alle omstændigheder for problemet med korteste stier skulle spænde over hele grafen for at få den korteste sti.

I tilfælde af BFS sikrer den første forekomst af destinationsnoden imidlertid, at det er den, der er den korteste afstand fra kilden.

Konklusion

Indtil videre diskuterede vi problemet med Friends-henstilling fra Facebook, og vi kogte det ned til problemet med at finde graden af ​​forbindelser mellem to brugere i netværksgrafen.

Derefter diskuterede vi to interessante Graph Traversal-algoritmer, der er meget almindeligt anvendte. Endelig kiggede vi på, hvilken algoritme der er bedst til at løse vores problem.

Breadth First Search er den algoritme, du vil bruge, hvis du skal finde den korteste afstand mellem to noder i en ikke-rettet, uvægtet graf.

Lad os se på dette sjove problem for at skildre forskellen mellem de to algoritmer.

Hvis vi antager, at du har læst problemklaringen omhyggeligt, så lad os prøve at modellere dette som et grafproblem i første omgang.

Lad alle mulige strenge blive knudepunkter i grafen, og vi har en kant mellem to hjørner, hvis de har en enkelt mutation imellem.

Let, ikke?

Vi får en startstreng (læse kildevertex) fx: - “AACCGGTT” og vi skal nå destinationsstrengen (læse destinationsvertex) “AACCGGTA” i et minimum antal mutationer (læse minimum antal trin), så alle mellemliggende strenge (noder) skal høre til den givne ordbank.

Prøv at løse dette problem på egen hånd, før du ser på løsningen herunder.

Hvis du prøver at løse det ved hjælp af DFS, vil du helt sikkert komme med en løsning, men der er en testtilstand (er), der vil overskride den tildelte tidsbegrænsning på LeetCode-platformen. Det er på grund af det tidligere beskrevne problem med hensyn til, hvorfor DFS tager så lang tid (proces 7 noder i modsætning til 3 i BFS) for at nå destinationens toppunkt.

Håber, at du fik hovedideen bag de to vigtigste grafkrydsninger, og forskellen mellem dem, når applikationen er korteste stier i en ikke-rettet uvægtet graf.

Anbefal (❤) dette indlæg, hvis du mener, at dette kan være nyttigt for nogen!