Você está na página 1de 23

Aula 23 – Cobertura por Vértices e Caixeiro Viajante

25089/1001525 – Projeto e Análise de Algoritmos


2019/2 - Turma A
Prof. Dr. Murilo Naldi

naldi@dc.ufscar.br
Agradecimentos


Aos professores Mário Felice e Diego Furtado por ceder
parte do material utilizado.

Material inspirado nas aulas do prof. Tim Roughgarden
Estratégias para Atacar Problemas NP-Completos

Temos três estratégias principais para lidar com
problemas NP-Completos (ou NP-Difíceis):
– 1) focar em casos particulares do problema que
sejam tratáveis

por exemplo, atacando o problema em grafos
particulares como árvores
– 2) usar heurísticas que não garantem a solução
ótima mas executam em tempo polinomial

algoritmos gulosos costumam ser úteis aqui
– 3) usar algoritmos exatos cujo pior caso é
exponencial, mas procurar fazer melhor que a
simples força bruta
Cobertura por Vértices


Entrada: um grafo não orientado G = (V, E)

Solução: um conjunto de vértices S contido em V tal
que S tenha cardinalidade mínima e toda aresta em
E tenha pelo menos uma ponta em S
Aplicação: Cobertura por Vértices


Intuição:
– podemos pensar que cada aresta é um corredor que
queremos monitorar e cada vértice é uma encruzilhada
na qual podemos colocar uma câmera ou alarme
– também podemos pensar que cada vértice é uma
pessoa e cada aresta uma habilidade compartilhada por
duas pessoas. Neste cenário queremos montar o menor
time que cubra todas as habilidades.

se te incomoda que cada habilidade seja
compartilhada apenas por duas pessoas, este é um
bom ponto. Tanto que existe uma generalização deste
problema chamada Cobertura por Conjuntos.
Versão de decisão


Vamos focar na versão de decisão do problema, em que é
dado um parâmetro k>0 e queremos saber se existe
solução de tamanho no máximo k

Neste caso, força bruta envolveria testar todas as
combinações de conjuntos de vértices com cardinalidade k
– (n escolhe k) = n!/(n-k)!k! = Θ(nk) para k constante
tecnicamente Θ(nk) é polinomial, mas na prática é

inviável para n grande e k maior do que 4 ou 5, no


máximo

Para tentar fazer melhor, vamos analisar a estrutura de
uma solução ótima, de modo semelhante ao que fazemos
em algoritmos de programação dinâmica
Um Lema de Subestrutura da Solução


Dado um grafo G e uma aresta (u, v). Seja Gu o
grafo G menos o vértice u e todas as arestas
incidentes em u. Gv é definido de modo semelhante.
– Como, em qualquer solução do problema a aresta
(u, v) deve estar coberta, temos que G tem uma
cobertura de tamanho k se, e somente se, Gu ou
Gv tem uma cobertura de tamanho k-1.
Um Lema de Subestrutura da Solução


Demonstração:
– (<--) Supondo, sem perda de generalidade, que
Gu tem uma cobertura de tamanho k-1

então essa cobertura junto do vértice u tem
tamanho k e cobre todas as arestas de G, já
que as únicas arestas faltando em Gu são as
que incidem em u

portanto, são cobertas por ele.
Um Lema de Subestrutura da Solução

Demonstração:
– (-->) Digamos que G tem uma cobertura de tamanho
k.
– Nessa cobertura a aresta (u, v) deve estar coberta.
Supomos, sem perda de generalidade, que ela é
coberta pelo vértice v.
– Removendo o vértice v da cobertura temos uma
conjunto de vértices de cardinalidade k-1 que cobre
todas as arestas de G que não incidem em v, já que a
cobertura original cobria todas as arestas e as únicas
arestas que v podia cobrir eram as que incidiam nele.

Portanto, o conjunto obtido pela remoção de v é
uma cobertura de Gv com tamanho k-1.
Algoritmo de busca recursivo

coberturaVertices(G=(V,E), int k) {
se |E| = 0 devolva { }
se k = 0 devolva Falha
pegue uma aresta (u, v) em E
Su = coberturaVertices(Gu, k-1)
se Su é válida
devolva Su U {u}
Sv = coberturaVertices(Gv, k-1)
se Sv é válida
devolva Sv U {v}
devolva Falha
}
Eficiência


T(k) = 2T(k-1) + O(m)
– resolvendo a recorrência usando método da
substituição ou da árvore de recorrência, e
lembrando que a recursão termina quando k=0,
chegamos a

T(k) = O(m * 2k)
Traveling Salesman Problem (TSP)


Problema do Caixeiro Viajante

Entrada: um grafo completo não-orientado G = (V, E)
com custos c(e) não negativos nas arestas

Solução: um circuito de custo mínimo que visita
cada vértice exatamente uma vez
TSP


Algoritmo de busca por força bruta leva tempo
Θ(n!)
– para perceber isso, considere que o algoritmo
terá que fixar um vértice
– a partir dele testar cada um dos n-1 possíveis
vizinhos
– a partir dessa escolha testar cada um dos n-2
vizinhos que sobraram

e assim por diante
Subestrutura ótima


Vamos buscar um lema de subestrutura ótima
– que nos permitirá entender melhor as
dificuldades inerentes do problema
– além de obter um algoritmo um pouco melhor
que a busca por força bruta
Subestrutura ótima


Primeiro, vamos estabelecer uma relação com o
problema de caminhos mínimos
– observe que se encontrarmos caminhos de custo
mínimo que vão do vértice 1 até cada um dos
demais vértices (Pi, para todo i)

sendo que cada caminho Pi passa por todos os
vértices
– nós conseguimos obter um circuito de custo mínimo

para isso basta escolher o menor entre todos os
{Pi + c(i, 1)} para todo i=2, ..., n
– será que conseguimos calcular o valor desses
caminhos em função de subproblemas menores?
Subestrutura ótima – Primeira Tentativa


Baseada no formato que usamos para o algoritmo
de caminhos mínimos de Bellman-Ford
– Para cada limite de arestas i em {0, 1, ..., n-1} e
destino j em {1, 2, 3, ..., n}, seja
– Lij o comprimento do caminho mais curto de 1
até j que usa no máximo i arestas
● Note que Lnj, para os vários js, não permitem
resolver o TSP, pois embora esses caminhos
possam usar até n-1 arestas, eles
provavelmente usam menos. Portanto, não
passam por todos os vértices.
Subestrutura ótima – Segunda Tentativa


Para cada limite de arestas i em {0, 1, ..., n-1} e
destino j em {1, 2, 3, ..., n}, seja
– Lij o comprimento do caminho mais curto de 1
até j que usa exatamente i arestas e não repete
vértices
– Note que, embora Lnj, para os vários js, nos
deem exatamente a informação que queremos,
nós não conseguimos obter a solução de um
problema maior a partir de um subproblema
menor.
Subestrutura ótima – Segunda Tentativa


Para perceber isso, considere a seguinte tentativa
de recorrência
– Lij = min(k != 1, j) {Li-1,k + c(k, j)}

Embora essa recorrência pareça correta, observe
que não temos informação suficiente para garantir
que o caminho mínimo até k não passa por j.
– Portanto, não temos como garantir que Lij
corresponda ao custo de um caminho que não
repete vértices
Subestrutura ótima – Terceira Tentativa


Para garantir que não vamos repetir visitas a
vértices, precisamos armazenar quem são os
vértices já visitados
– Para cada destino j em {1, 2, 3, ..., n} e cada
subconjunto S contido em {1, 2, ..., n} que
contém 1 e j, seja
● LS,j o comprimento do caminho mais curto de
1 até j que passa exatamente uma vez por
cada vértice de S
Subestrutura ótima – Terceira Tentativa


Subestrutura ótima
– Seja P um caminho mínimo de 1 até j que passa
exatamente uma vez por todos os vértices de S
– Se a última aresta no caminho é (k, j) então P' é a
parte de P que vai de 1 até k
– Temos que P' é um caminho mínimo de 1 até k
que passa exatamente uma vez por todos os
vértices de S-{j}
– Demonstração de corretude por contradição
supondo que existe P* melhor que P' e mostrando
que se obtem um caminho melhor que P
Recorrência


Dessa subestrutura ótima derivamos a seguinte
recorrência
– LS,j = min(k є S, k != j) {LS-{j}, k + c(k, j)}
Algoritmo

algTSP(G = (V,E), c) {
A[{1}, 1] = 0;
para m = 2 até n //m é o tamanho do subconjunto S
para cada S em {1, 2, ..., n} de tamanho m que
contém 1
para cada j em S, j != 1
A[S, j] = min(k є S, k != j) {A[S-{j}, k] + c(k, j)}
devolva min(j=2, ..., n){A[{1, 2, ..., n}, j] + c(j, 1)}
}
Eficiência


Θ(n2 * 2n)
– o número de subproblemas é igual a Θ(n * 2n)
pois são Θ(2n) subconjuntos e Θ(n) destinos
– para resolver cada subproblema gastasse Θ(n), já
que a recorrência verifica todo k є S (exceto k = j)
– do produto do número de subproblemas pelo
esforço para resolver cada subproblema segue o
resultado

Você também pode gostar