Najniższy wspólny przodek

Z Wikipedii, wolnej encyklopedii
Skocz do: nawigacja, szukaj
Niniejszy artykuł jest częścią cyklu teoria grafów.




Najważniejsze pojęcia
graf
drzewo
podgraf
cykl
klika
stopień wierzchołka
stopień grafu
dopełnienie grafu
obwód grafu
pokrycie wierzchołkowe
liczba chromatyczna
indeks chromatyczny
izomorfizm grafów
homeomorfizm grafów


Wybrane klasy grafów
graf pełny
graf spójny
drzewo
graf dwudzielny
graf regularny
graf eulerowski
graf hamiltonowski
graf planarny


Algorytmy grafowe
A*
Bellmana-Forda
Dijkstry
Fleury'ego
Floyda-Warshalla
Johnsona
Kruskala
Prima
przeszukiwanie grafu
wszerz
w głąb
najbliższego sąsiada


Zagadnienia przedstawiane jako problemy grafowe
problem komiwojażera
problem chińskiego listonosza
problem marszrutyzacji
problem kojarzenia małżeństw


Inne zagadnienia
kod Graya
diagram Hassego
kod Prüfera


Najniższy wspólny przodek (ang. lowest common ancestor, LCA) – najniższym wspólnym przodkiem dwóch wierzchołków d oraz e w ukorzenionym drzewie T jest taki rodzic wierzchołków d oraz e, którego poziom w drzewie T jest największy.

Algorytm[edytuj]

Zdefiniujmy tablicę p:

p[i, v] — wierzchołek u leżący na ścieżce pomiędzy v i korzeniem, taki że odległość (u, v) = 2i Jeśli taki wierzchołek nie istnieje, to p[i,v]=korzeń.

Rekurencyjnie można ją obliczyć ze wzoru

Za pomocą tak utworzonej tablicy możemy analogicznie do techniki wyszukiwania binarnego odnaleźć LCA dwóch wierzchołków.

Przykładowy kod w języku c++ obliczający LCA na podstawie tablicy p.

#include <cstdio>
#include <vector>

#define MAX 1000
#define LOGN 16

using namespace std;

vector<int> V[MAX];
int P[LOGN][MAX], pre[MAX], treeSize[MAX];
bool vis[MAX];

int n, from, to, counter=0;

bool children(int u, int v)
{
	return (pre[u]>=pre[v] && pre[u] < pre[v]+treeSize[v]);
}

int lca(int u, int v)
{
	if(children(u, v)) return v;
	if(children(v, u))return u;

	int i=u, j=LOGN-1;
	while(j>=0) {
		if(children(v, P[j][i]))
			j--;
		else
			i = P[j][i];
	}
	
	return P[0][i];
}

void dfs(int v)
{
	counter++;
	pre[v]=counter;
	for(vector<int>::iterator it = V[v].begin(); it != V[v].end(); ++it) 
		if(!vis[*it]) {
			P[0][*it]=v;
			vis[*it]=true;
			dfs(*it);
		}
	treeSize[v]=counter+1-pre[v];
}

int main()
{
	scanf("%d", &n);
	for(int i = 0; i < n-1; ++i) {
		scanf("%d %d", &from, &to);
		V[from].push_back(to);
		V[to].push_back(from);
	}

	for(int i = 0; i <= n; ++i) {
		pre[i]=treeSize[i]=0;
		vis[i]=false;
	}

        for(int i = 0; i < LOGN; ++i)
          for(int j = 0; j < MAX; ++j)
             P[i][j]=0;

	vis[1]=true;
	dfs(1);

	P[0][1]=1;
	for(int i = 1; i < LOGN; ++i) 
		for(int j = 1; j < MAX; ++j)
			P[i][j]=P[i-1][P[i-1][j]];

	//lca(7, 4);
	return 0;
}

Powyższy algorytm ma złożoność obliczeniową rzędu - gdzie to ilość wierzchołków w drzewie, a to ilość zapytań o najniższego wspólnego przodka. Innym sposobem obliczania dla dwóch wierzchołków drzewa ich najniższego wspólnego przodka jest Algorytm Tarjana. Istnieje także algorytm opublikowany w 2000 roku o złożoności obliczeniowej .

Bibliografia[edytuj]