Longest common subsequence

In questo problema si vogliono cercare le massime sottosequenze comuni di massima lunghezza tra due sequenze e sapendo che:

Per esempio, .

Sottostruttura ottima

Di una sequenza lunga , un carattere può essere incluso o meno portando a alternative.

Si dice prefisso di la sequenza lunga per cui , e .

Data quindi una sequenza , la sottostruttura ottima diventa:

  • Se , allora e
  • Se , allora:
    • Se , allora
    • Se , allora

che riduce il problema a alternative, perchè per ogni prefisso degli se ne possono scegliere .

Ricorsione

La lunghezza delle sottosequenze massime appartenenti a si può trovare come:

Bottom-up

Sia una matrice contenete le lunghezze delle sottosequenze in , e una matrice contenente la miglior direzione presa dall'algoritmo partendo da :

  • , se quindi si va a
  • , se e si riduce a
  • , se e si riduce a
LCS(X, Y)
	m = X.length
	n = Y.length
	for i = 0 to m
		c[i, 0] = 0
	for j = 1 to n
		c[0, j] = 0
	for i = 1 to m
		for j = 1 to n
			if X[i] == Y[j]
				c[i, j] = c[i-1, j-1] + 1
				b[i, j] = ↖
			else
				if c[i-1, j] >= c[i, j-1]
					c[i, j] = c[i-1, j]
					b[i, j] = ↑
				else
					c[i, j] = c[i, j-1]
					b[i, j] = ←
	return b, c

La complessità è data dalla soluzione di tutti i sottoproblemi, quindi .

Si può quindi calcolare e visitare la soluzione con la stessa complessità di LCS:

print_LCS(X, Y)
	b, c = LCS(X, Y)
	print_LCS_aux(X, b, X.length, Y.length)

print_LCS_aux(X, b, i, j)
	if i > 0 and j > 0
		if b[i, j] == ↖
			print_LCS_aux(X, b, i-1, j-1)
			print(X[i])
		else
			if b[i, j] == ↑
				print_LCS_aux(X, b, i-1, j)
			else
				print_LCS_aux(X, b, i, j-1)

dove print_LCS_aux ha complessità perchè nel caso peggiore deve decrementare sia che .

Per esempio, dati e le tabelle e ricavate saranno:

Ottimizzazione

Un'ottimizzazione consiste nel ridurre lo spazio consumato rimuovendo , perchè dipende dai vicini:

print_LCS_aux(X, c, i, j)
	if i > 0 and j > 0
		if c[i, j] == c[i-1, j]
			print_LCS_aux(X, c, i-1, j)
		else if c[i, j] == c[i, j-1]
			print_LCS_aux(X, c, i, j-1)
		else
			print_LCS_aux(X, c, i-1, j-1)
			print(X[i])

Dall'esempio precedente si nota che è importante l'ordine dei controlli, infatti se il vicino verso fosse controllato per primo, dalla cella si passerebbe a .

Inoltre con un vettore di elementi è anche possibile rimuovere se interessa solo la lunghezza.

Top-down

LCS(X, Y)
	m = X.length
	n = Y.length
	c = []
	for i = 1 to m*n
		push(c, -1)
	return LCS_aux(X, Y, c, m, n)

LCS_aux(X, Y, c, i, j)
	if c[i, j] == -1
		if i == 0 or j == 0
			c[i, j] = 0
		else
			if X[i] == Y[j]
				c[i, j] = LCS_aux(X, Y, c, i-1, j-1) + 1
			else
				c[i, j] = max(LCS_aux(X, Y, c, i-1, j), LCS_aux(X, Y, c, i, j-1))
	return c[i, j]

La complessità di LCS_aux è perchè al massimo riempie , mentre con LCS diventa .