def busca_largura_lexicografica(G): lista_vertices = ListaDuplamenteEncadeada() pos_vertices = {} for v in G.vertices: #O(n) lista_vertices.inserir_fim(v) #O(1) pos_vertices[v] = lista_vertices.cauda #O(1) Q = ListaDuplamenteEncadeada([Conjunto(lista_vertices, pos_vertices)]) localizacao = {v: Q.cabeca.prox for v in G.vertices} #(O(n)) ordem_eliminacao_perfeita = [] #O(1) predecessores = {v: [] for v in G.vertices} #O(n) esta_na_oep = {v: False for v in G.vertices} #O(n) for i in range(len(G.vertices) - 1, -1, -1): v = Q.get_primeiro_no().item.vertices.remover_primeiro_no( ) #O(1), sempre temos a referência do primeiro conjunto del Q.get_primeiro_no().item.pos[ v.item] #O(1), remoção de elemento de um dicionário if Q.get_primeiro_no().item.vertices.vazia( ): #O(1), ListaDuplamenteEncadeada tem um membro que indica se a lista está vazia ou não Q.remover_primeiro_no() #O(1) ordem_eliminacao_perfeita.append(v.item) #O(1) esta_na_oep[v.item] = True #O(1) for u in G.vizinhos[v.item]: #O(N(u)) if not esta_na_oep[u]: #O(1) predecessores[u].append(v.item) #O(1) del localizacao[v.item] #O(1) for w in G.vizinhos[v.item]: #O(N(v)) try: conj_w = localizacao[w] #O(1) except: continue ant_conj_w = conj_w.ant if ant_conj_w == Q.cabeca or ant_conj_w.item.label == "" or ant_conj_w.item.label[ -1] != chr( i ): #O(1), só comparamos 1 char da label ou comparamos com string vazia ant_conj_w = No( Conjunto(vertices=ListaDuplamenteEncadeada(), pos={}, label=conj_w.item.label + chr(i)) ) #linear no tamanho da string, que normalmente é negligível :) Q.inserir_no_antecessor(ant_conj_w, conj_w) #O(1) no_w = conj_w.item.pos[w] #O(1) conj_w.item.vertices.remover_no(conj_w.item.pos[w]) #O(1) del conj_w.item.pos[w] #O(1) if conj_w.item.vertices.vazia(): #O(1) Q.remover_no(conj_w) #O(1) localizacao[w] = ant_conj_w ant_conj_w.item.vertices.inserir_fim(no_w) ant_conj_w.item.pos[w] = no_w conta_memoria() return (ordem_eliminacao_perfeita, predecessores)
def caminho_de_u_ate_v(G, u, v, caminho_proibido=[]): fila = deque() fila.append(u) pred = {u: None} caminho_encontrado = False while fila: w = fila.popleft() for x in G.vizinhos[w]: if x not in caminho_proibido: pred_x = pred.get(x, None) if pred_x is None: pred[x] = w fila.append(x) if x == v: conta_memoria() fila.clear() caminho_encontrado = True break caminho_uv = [] if not caminho_encontrado: return [] x = v while pred[x] != u: caminho_uv.append(pred[x]) x = pred[x] conta_memoria() return caminho_uv
def eh_cordal_LEXBFS(G): OEP, OEP_predecessores = busca_largura_lexicografica( G) #OEP = Ordem de Eliminação Perfeita eh_cordal, corda_faltando = testar_ordem_eliminacao_perfeita( G, OEP, OEP_predecessores) if not eh_cordal: conta_memoria() return (False, encontrar_ciclo(G, corda_faltando[0], corda_faltando[1])) conta_memoria() return (True, OEP[::-1])
def tem_corda(G, ciclo): tamanho_ciclo = len(ciclo) for indice_v, v in enumerate(ciclo): for u in G.vizinhos[v]: adjacentes_v = [ ciclo[indice_v - 1], ciclo[(indice_v + 1) % tamanho_ciclo] ] if u not in adjacentes_v and u in ciclo: conta_memoria() return True conta_memoria() return False
def eh_cordal_forca_bruta(G): ciclos = achar_ciclos4_forca_bruta(G) proibidos = [] qtd_sem_corda = 0 for ciclo in ciclos: if not tem_corda(G, ciclo): proibidos.append(ciclo) qtd_sem_corda += 1 conta_memoria() if qtd_sem_corda == 0: return (True, qtd_sem_corda, []) return (False, qtd_sem_corda, proibidos)
def testar_ordem_eliminacao_perfeita(G, ordenacao, predecessores): test = [ ] #conjunto de pares de vértices que devem ser testados para verificar se formam aresta for i in range(len(G.vertices) - 1, -1, -1): #O(n) if predecessores[ordenacao[i]] != []: #O(1) u = predecessores[ordenacao[i]][-1] #O(1) for w in predecessores[ordenacao[ i]]: #para cada vértice na ordenação, verificamos os vizinhos, logo passamos por todas as arestas 2x, então é na ordem de O(n + 2m), considerando o loop exterior if u != w: #O(1) test.append((u, w)) #O(1) conta_memoria() for u, v in test: if u in G.vizinhos[v]: continue else: return (False, (u, v)) return (True, None)
def encontrar_ciclo(G, u, v): caminhos = [] while True: caminho = caminho_de_u_ate_v( G, u, v, caminho_proibido=[v for caminho in caminhos for v in caminho]) if caminho == []: conta_memoria() break caminhos.append(caminho) ciclo = [] for i in range(0, len(caminhos) - 1): for j in range(i + 1, len(caminhos)): ciclo = [u] + caminhos[i][::-1] + [v] + caminhos[j] if not tem_corda(G, ciclo): conta_memoria() return ciclo conta_memoria() return []
def achar_ciclos4_forca_bruta(G): ciclos = [] #variável que contém todos os ciclos únicos encontrados for v in G.vertices: #percorremos a partir de cada um dos vértices vertice_inicial = v pilha = deque( ) #um conteiner que contém todos os caminhos que iniciam a partir do vértice_inicial (poderia ser qualquer conteiner, na real) pilha.append([vertice_inicial ]) #o caminho inicial começa com o vértice_inicial while pilha: #enquanto a pilha não estiver vazia caminho_ate_v = pilha.pop() #removemos algum caminho do conteiner v = caminho_ate_v[-1] #último vértice do caminho até o momento for u in G.vizinhos[ v]: #para continuar o caminho, percorremos os vizinhos do último vértice if u == vertice_inicial: #caso um dos vizinhos seja o vertice_inicial, então formamos um ciclo que começa e termina em vertice_inicial if len( caminho_ate_v ) > 3: #para o problema de grafos cordais (força bruta), queremos apenas os ciclos de tamanho 4 ou maiores novo_ciclo = caminho_ate_v ciclo_duplicado = any( [ciclos_sao_iguais(novo_ciclo, c) for c in ciclos] ) #verificamos se o novo ciclo já foi encontrado anteriormente (pode estar permutado) if not ciclo_duplicado: #se o ciclo é diferente de todos os outros até agora ciclos.append(novo_ciclo.copy( )) #novo ciclo é adicionado aos ciclos encontrados elif u not in caminho_ate_v: #caso o vizinho u seja diferente do vértice inicial, precisamos saber se u já não está no caminho (formaria um ciclo com 'folhas') caminho_ate_u = caminho_ate_v[:] #cópia do caminho caminho_ate_u.append( u) #vizinho u é adicionado ao novo caminho pilha.append( caminho_ate_u) #novo caminho é adicionado na pilha conta_memoria() return ciclos
args = parser.parse_args() if not utils.hp: print("Módulo guppy não encontrado. Não será possível contar a memória utilizada pelos algoritmos!\nPara instalar o módulo guppy, use esse comando no cmd/terminal: pip install -U guppy3") elif args.contar_memoria: print("<<CUIDADO: O contador de memória deixa a execução dos algoritmos significantemente mais devagar>>") else: utils.hp = None arquivo = args.arquivo g = Grafo(arquivo) print("Grafo carregado:") print(g) if args.metodo in {"fb", "forca_bruta"}: print("Executando o algoritmo força bruta") mem_antes = utils.conta_memoria() saida_fb = forca_bruta.eh_cordal_forca_bruta(g) mem_utilizada = utils.conta_memoria() - mem_antes if saida_fb[0] == False: print("O grafo não é cordal!") print(str(saida_fb[1]) + " dos ciclos de tamanho > 3 não contêm cordas") print("Subgrafo(s) proibido(s) encontrado(s): ") for proibido in saida_fb[2]: print(", ".join(proibido)) else: print("O grafo é cordal!") if args.contar_memoria: print("Memória utilizada: " + str(mem_utilizada / 1000.0) + " KB")