TProcura
Biblioteca em C++ para testes paramétricos de algoritmos, e coleção de algoritmos de procura e otimização
Loading...
Searching...
No Matches
TProcuraConstrutiva.cpp
Go to the documentation of this file.
2#include "CListaNo.h"
3#include <stdio.h>
4#include <time.h>
5#include <string.h>
6#include <ctype.h>
7
8constexpr int BUFFER_SIZE = 1024;
9
10// auxiliar para construcao da arvore de procura
12// valor retornado pela procura (tem de ser libertado)
14// valor retornado pela procura (tem de ser libertado)
16// lowerBound: valor mínimo que a solução pode obter
24
25
27int TProcuraConstrutiva::custoHT[TAMANHO_HASHTABLE]; // hashtable / custo do estado que foi gerado
28TBits TProcuraConstrutiva::estadoCodHT; // elemento codificado
29int TProcuraConstrutiva::colocadosHT = 0; // número de elementos colocados na HT
30
32
33
35{
37
38 // definir parametros base
39 // algoritmos, alterar
40 parametro[ALGORITMO] = { "ALGORITMO",1,1,7,"Algoritmo base a executar.", {
41 "Largura Primeiro",
42 "Custo Uniforme",
43 "Profundidade Primeiro",
44 "Melhor Primeiro",
45 "A*",
46 "IDA*",
47 "Branch and Bound" }
48 };
49 // parametros adicionados
50 parametro += {
51 { "VER_ACOES", 4, 1, 100, "Mostra estado a cada K ações. Se 1 mostra sempre estados e nunca ações." },
52 { "LIMITE",0,-1,1000000,
53"Valor dependente do algoritmo. \n\
54Largura: 0 sem limite, >0 número máximo de estados gerados não expandidos. \n\
55Profundidade: >0 limite de profundidade, =0 iterativo, <0 sem limite." },
56 { "ESTADOS_REPETIDOS", 1,1,3, "Forma de lidar com os estados repetidos (ignorá-los, ascendentes, gerados).", {
57 "ignorar",
58 "ascendentes",
59 "gerados" } },
60 { "PESO_ASTAR", 100, 0, 10000,
61 "Peso aplicado à heuristica, na soma com o custo para calculo do lower bound. No A*, se peso 0, fica custo uniforme, h(n) não conta, se peso 100 fica A* normal, se superior a 100 aproxima-se do melhor primeiro.",
62 "", _TV("0,5:7") },
63 { "RUIDO_HEURISTICA",0,-100,100, "Ruído a adicionar à heurística, para testes de robustez. Se K positivo, adicionar entre 0 e K-1, se negativo, o valor a adicionar pode ser positivo ou negativo.",
64 "", _TV("0,5:7") },
65 { "BARALHAR_SUCESSORES",0,0,1, "Baralhar os sucessores ao expandir." }
66 };
67
68 // indicadores, alterar
69 indicador[IND_CUSTO].nome = "IND_CUSTO";
70 indicador[IND_CUSTO].descricao = "o resultado é o custo da solução atual";
71 // indicadores adicionados
72 indicador += {
73 { "IND_EXPANSOES", "número de expansões efetuadas", IND_EXPANSOES },
74 { "IND_GERACOES","número de estados gerados", IND_GERACOES },
75 { "IND_LOWER_BOUND", "valor mínimo para a melhor solução, se igual ao custo da solução obtida, então esta é ótima", IND_LOWER_BOUND }
76 };
78}
79
80// Executa uma ação (movimento, passo, jogada, lance, etc.) no estado atual. Caso não seja feito nada, retornar falso.
84 for (int i = 0; i < sucessores.Count(); i++)
85 if (acao == Acao(sucessores[i])) {
86 custoAcao = sucessores[i]->custo - custo;
89 return true;
90 }
91 return false;
92}
93
94// Coloca em sucessores a lista de objectos sucessores (sao alocados neste metodo e tem de ser apagados)
95// O custo se não existir, deixar a 1 (valor por omissão)
96// chamar o metodo desta classe apos adicionar os sucessores para actualizar geracoes e expansoes,
97// bem como verificar a existência de estados repetidos
100 return;
101 // colocar como pai o estado atual e atualizar custos
102 for (int i = 0; i < sucessores.Count(); i++) {
103 sucessores[i]->pai = this;
104 sucessores[i]->custo += custo;
105 // o custo total tem de estar atualizado antes de se utilizar nas hashtables
106 }
107 // verificação de estados repetidos
109 for (int i = 0; i < sucessores.Count(); i++)
110 if (sucessores[i]->ExisteHT()) {
111 // estado já gerado anteriormente, está na hashtable, pelo que não interessa
112 delete sucessores[i];
113 sucessores[i] = NULL;
114 }
115 }
117 // verificar se o estado já foi gerado na árvore de procura
118 // remover todos os estados que ja tenham sido expandidos neste ramo
119 for (int i = 0; i < sucessores.Count(); i++) {
120 TNo avo = this;
121 while (avo != NULL && avo->Distinto(sucessores[i]))
122 avo = avo->pai;
123 // estado é igual a um avô, não interessa
124 if (avo != NULL) {
125 delete sucessores[i];
126 sucessores[i] = NULL;
127 }
128 }
129 }
130 sucessores -= NULL;
132 sucessores.RandomOrder();
133 expansoes++;
134 if (Parametro(NIVEL_DEBUG) >= PASSOS && !sucessores.Empty()) {
135 for (auto suc : sucessores)
136 suc->debugID = ++geracoes;
137 // avançar se for algoritmo em profundidade
138 switch (Parametro(ALGORITMO)) {
140 case MELHOR_PRIMEIRO:
141 case IDA_STAR:
142 case BRANCH_AND_BOUND:
144 }
145 }
146 else
148}
149
150
151// metodo interno para libertar objectos nao necessarios
152void TProcuraConstrutiva::LibertarVector(TVector<TNo>& vector, int excepto, int maiorQue)
153{
154 for (int i = 0; i < vector.Count(); i++)
155 if (i != excepto && i > maiorQue && vector[i] != NULL)
156 delete vector[i];
157 vector = {};
158}
159
160
162 int custoTotal = 0, nAcoes = 0;
163 // aplicar todas as ações
164 for (auto acao : solucao) {
165 if (!Acao(acao)) {
166 if (Debug(PASSOS, false, "\n │ Ação inválida: %s (custo atual: %d de %d ações válidas)",
168 Debug();
169 resultado = -1; // ação inválida
170 return false;
171 }
173 nAcoes++;
174 }
175 if (SolucaoCompleta()) {
176 resultado = custo = custoTotal; // custo da solução encontrada
177 return true;
178 }
179 if (Debug(PASSOS, false, "\n │ Solução não completa (custo atual: %d de %d ações válidas)",
181 Debug();
182 resultado = -2; // solução incompleta
183 return false;
184}
185
186
188// Procura em Largura Primeiro: expande primeiro o estado gerado não expandido mais antigo
189// retorna o valor da solução e coloca o caminho no vector (se calcularCaminho=true), ou -1 caso não encontre solucao
190// limite é o número de estados gerados não expandidos, que não pode ultrapassar esse limite
191// os que ultrapassarem são deitados fora (se 0 este limite não importa, podendo haver problemas de memória)
193{
194 TVector<TNo> lista; // lista de nós gerados na árvore de procura (expandidos ou não)
195 lista += this; // nó inical: estado, custo
196 for (int i = 0; i < lista.Count() && !Parar(); i++) {
197 // processar o próximo estado não expandido (estão expandidos todos os anteriores a i)
198 lista[i]->DebugPasso();
199 // expansão deste estado
201 lista[i]->Sucessores(sucessores);
202 // garantir que os limites são respeitados, para evitar problemas de memória
203 VerificaLimites(limite, lista.Count() - i, sucessores);
204
206 lista[i]->DebugSucessores(sucessores);
207
208 // inserir tudo no final da lista
209 for (int j = 0; j < sucessores.Count(); j++) {
210 lista += sucessores[j];
211 // verificar se é um estado objetivo, a solução é completa nesse caso
212 // teste na geração e não na expansão
213 if (lista.Last()->SolucaoCompleta()) {
215 return ObjetivoAlcancado(lista.Count() - 1, lista); // Sucesso! Terminar a procura e retornar
216 }
217 }
218
219 // Nao se pode libertar estados ja expandidos porque nao se sabe se
220 // os pais sao necessarios ou nao.
221 // todos os estados antes de i, são os estados gerados e expandidos (fechados)
222 // todos os estados após i são estados gerados mas não expandidos (abertos)
223 }
224 // nada feito, libertar tudo excepto o estado inicial que é este objeto, e retornar
226 return -1; // falhou
227}
228
230{
231 estado->CalculaCaminho(completa); // extrair o caminho do estado objetivo até ao estado inicial
232 solucao = estado->Duplicar(); // registar este estado como a solução
233 solucao->custo = estado->custo;
235 return solucao->custo;
236}
237
244
246 TNo atual = this;
247 // limpar o caminho anterior (caso do BnB que obtém mais que uma solução)
248 while (!caminho.Empty())
249 delete caminho.Pop();
250 caminho += atual->Duplicar();
251 caminho.Last()->custo = atual->custo;
252
253 // apenas se a lista estiver completa, há a garantia dos pais não terem sido apagados
254 if (completa) {
255 // obter caminho para a solucao
256 while (atual->pai != NULL) {
257 atual = atual->pai;
258 caminho += atual->Duplicar();
259 caminho.Last()->custo = atual->custo;
260 }
261 }
262 caminho.Invert();
263}
264
266 int limite, int porProcessar, TVector<TNo>& sucessores)
267{
268 // caso o limite seja ultrapassado,
269 // remover elementos a mais da lista de sucessores
270 if (limite != 0 && limite < porProcessar + sucessores.Count()) {
271 int maximo = limite - porProcessar;
272 if (maximo >= 0 && maximo < sucessores.Count()) {
273 for (int i = maximo; i < sucessores.Count(); i++)
274 delete sucessores[i];
275 sucessores.Count(maximo);
276 }
277 else if (maximo < 0)
279 }
280}
281
282
284// Procura Custo Uniforme: expande primeiro o estado gerado não expandido de menor custo acumulado
286{
287 CListaNo lista(limite);
288
289 lista.Inserir(this); // começa apenas com o elemento atual
290
291 for (lista.atual = 0; !Parar() && lista.Estado() != NULL; lista.atual = lista.Proximo()) {
292 lista.Estado()->DebugPasso(&lista);
293 if (lista.Estado()->SolucaoCompleta())
294 return ObjetivoAlcancado(lista.Estado(), lista.Completa());
295
297 lista.Estado()->Sucessores(sucessores);
298 // insere por ordem de custo, e não apenas no final
299 lista.Inserir(sucessores);
301 lista.Estado()->DebugSucessores(sucessores);
302 }
303 return -1; // falhou
304}
305
307// Procura em Profundidade Primeiro: expande primeiro o estado gerado não expandido mais novo
308// caso o nivel=-1, e feita uma procura em profunidade normal
309// caso o nivel>0, e feita uma procura em profundidade limitada
310// caso o nivel=0, e feita uma procura em profundidade iterativa, sem limite
311// versão recursiva
313{
314 if (nivel == 0) { // metodo iterativo
315 int resultado = -1;
316 do {
317 // limpar hashtable: estados gerados no nível anterior não devem impedir nova geração
318 LimparHT();
319 DebugIteracao(nivel + 1, "🪜");
320 // chamar a profundidade nível 1, e se não resolver, o nível 2, e assim sucessivamente
322 } while (resultado == -1 && !Parar());
323 return resultado;
324 }
325 bool noFolha = !((nivel > 1 || nivel < 0) && !Parar());
327
328 // metodo normal
329 // verificar se o estado atual é objetivo, ou seja, a solução parcial é já completa
330 if (SolucaoCompleta())
331 return SolucaoEncontrada(true);
332
333 if (!noFolha) {
334 // caso o nível seja superior a 1 ou sem limite, expandir o estado atual
337 // tentar todos os sucessores, um de cada vez
338 for (int i = 0; i < sucessores.Count(); i++) {
339 DebugExpansao(i, sucessores.Count());
340 // chamada recursiva, reduzindo o nível
341 if (sucessores[i]->ProfundidadePrimeiro(nivel - 1) >= 0) {
342 // este sucessor resolveu o problema, retornar
343 return SolucaoParcial(i, sucessores);
344 }
345 }
346 // nenhum dos sucessores resolveu o problema
347 DebugCorte(sucessores.Count());
349 }
350 else
351 DebugFolha(false, "%-2s%-2s", Icon(EIcon::FOLHA), Icon(EIcon::LIMITE));
352
353 // falha na procura neste nó, ou não há estado objetivo a partir daqui
354 // ou atingiu-se o limite, pelo que temos de retornar -1
355 return -1;
356}
357
359 // sucesso!
361 if (solucao == NULL)
362 solucao = Duplicar();
363 else // caso existam várias soluções, substitui a anterior
364 solucao->Copiar(this);
366 DebugFolha(false, "%-2s%d → %-2s", Icon(EIcon::SUCESSO), custo, Icon(EIcon::UB));
367 return solucao->custo = custo;
368}
369
371{
372 // solução parcial já registada, adicionar este nó e retornar o custo até ao momento
373 if (Parametro(NIVEL_DEBUG) >= PASSOS) {
375 for (int j = (iAux >= 0 ? iAux + 1 : i + 1);
376 j < (iAux >= 0 ? id->Count() : sucessores.Count()); j++)
377 valores += (iAux >= 0 ? sucessores[(*id)[j]]->debugID : sucessores[j]->debugID);
378 if (!valores.Empty()) {
379 DebugFolha(true, "");
380 MostraConjunto(valores, Icon(EIcon::ID));
381 }
382 }
383 ramo.Pop();
384
386 return solucao->custo;
387}
388
391 for (int i = 0; i < caminho.Count() - 1; i++)
392 resultado += caminho[i]->Acao(caminho[i + 1]);
393 if (resultado.Empty())
394 resultado += TString("vazio"); // para garantir que o resultado não é vazio, para efeitos de validação
395 return resultado;
396}
397
399 printf("\n══ %-2s Solução ══", Icon(EIcon::SOL));
400 for (int i = 0; i < caminho.Count() - 1; i++) {
401 if (Parametro(VER_ACOES) > 1) {
402 // mostrar o estado a cada K ações, no início e no fim
403 if (i % Parametro(VER_ACOES) == 0) {
404 caminho[i]->Debug();
405 // mostrar custo
406 printf(" (%-2sg:%d) %-2s", Icon(EIcon::VALOR), caminho[i]->custo, Icon(EIcon::ACCAO));
407 }
408 // mostrar a ação
409 if (i < caminho.Count() - 1)
410 printf(" → %s", *caminho[i]->Acao(caminho[i + 1]));
411 }
412 else {
413 caminho[i]->Debug();
414 printf(" (%-2sg:%d) %-2s", Icon(EIcon::VALOR), caminho[i]->custo, Icon(EIcon::ACCAO));
415 }
416 }
417 if (caminho.Empty())
418 printf("Caminho vazio.");
419 else {
420 caminho.Last()->Debug();
421 // mostrar custo
422 printf(" (%-2sg:%d) ", Icon(EIcon::VALOR), caminho.Last()->custo);
423 if (caminho.Last()->SolucaoCompleta())
424 printf("%-2s", Icon(EIcon::SUCESSO));
425 else
426 printf("%-2s", Icon(EIcon::INSUC));
427 }
428}
429
430
432// Algoritmo Melhor Primeiro: expande primeiro o estado gerado não expandido com melhor heurística
433// versão recursiva, idêntico a ProcuraPrimeiro()
435{
436 if (nivel > 0 && Parametro(LIMITE) == nivel)
437 DebugIteracao(nivel, Icon(EIcon::LIMITE));
438
439 bool noFolha = !((nivel <= 0 || nivel > 1) && !Parar());
441 if (SolucaoCompleta())
442 return SolucaoEncontrada(true);
443
444 if (!noFolha) {
446 TVector<int> id; // índice para ordenar os sucessores por heurística
448 CalcularHeuristicas(sucessores, &id); // id fica ordenado por heurística
449 // processar todos os sucessores da mesma forma que a procura em profundidade,
450 // mas utilizando a ordem id, por heurística
451 for (int i = 0; i < id.Count(); i++) {
452 DebugExpansao(i, id.Count());
453 if (sucessores[id[i]]->MelhorPrimeiro(nivel - 1) >= 0)
454 return SolucaoParcial(id[i], sucessores);
455 }
456 DebugCorte(sucessores.Count());
458 }
459 else
460 DebugFolha(false, "%-2s&-2s", Icon(EIcon::FOLHA), Icon(EIcon::LIMITE));
461 return -1;
462}
463
465// Algoritmo IDAStar: procura em profundidade limitada a um upperBound
466// idêntico a MelhorPrimeiro() mas cortando quando upperBound já não consegue ser obtido
468{
469 if (upperBound == 0) { // parte iterativa
470 int resultado = -1;
471 // primeiro valor para o lower bound, a heurística no nó raiz
472 lowerBound = Heuristica(); // o custo é zero atualmente
473 if (lowerBound == 0)
474 lowerBound++;
475 do {
476 // limpar hashtable: estados gerados no nível anterior não devem impedir nova geração
477 LimparHT();
478 DebugIteracao(lowerBound, Icon(EIcon::LB));
479 // ver se há uma solução com este valor
481 // o valor de lowerBound é atualizado, para utilizar na próxima iteração se necessário
482 } while (resultado == -1 && !Parar());
483 return resultado;
484 }
485 bool noFolha = Parar();
487
488 if (SolucaoCompleta())
489 return SolucaoEncontrada(true);
490
491 if (!noFolha) {
493 TVector<int> id; // índice para ordenar os sucessores por heurística
495 CalcularHeuristicas(sucessores, &id, true); // id fica ordenado por LB
496 // processar todos os sucessores da mesma forma que a procura em profundidade,
497 // mas utilizando a ordem id, por heurística
498 for (int i = 0; i < id.Count(); i++) {
499 int atual = sucessores[id[i]]->LowerBound();
500 DebugExpansao(i, id.Count());
501 if (atual > upperBound) {
502 // Nó folha, não identificado antes. Mostrar o estado no caso do detalhe
504 Debug();
505 // acima do permitido nesta iteração
506 ramo.Last() = (i < id.Count() - 1 ? RAMO_NOVO : RAMO_FIM);
507 if (lowerBound == upperBound || lowerBound > atual) {
508 DebugFolha(false, "%-2s%d → %-2s", Icon(EIcon::FOLHA), atual, Icon(EIcon::LB));
509 lowerBound = atual;
510 }
511 else
512 DebugFolha(false, Icon(EIcon::FOLHA));
513 // listar os nós não explorados
514 if (Parametro(NIVEL_DEBUG) >= PASSOS) {
516 // apenas o atual, já que continua
517 valores += sucessores[id[i]]->debugID;
518 MostraConjunto(valores, Icon(EIcon::ID));
519 }
520 }
521 else {
522 if (sucessores[id[i]]->IDAStar(upperBound) >= 0)
523 return SolucaoParcial(id[i], sucessores, i, &id);
524 }
525 }
526 DebugCorte(sucessores.Count());
528 }
529 return -1;
530}
531
533// Algoritmo BranchAndBound:
534// idêntico a MelhorPrimeiro(), mas continua após a primeira solução
535// explora apenas os estados em que f(n) < atual solução (LB<UB)
537{
538 bool noFolha = Parar();
540 if (SolucaoCompleta())
541 return SolucaoEncontrada(true);
542
543 if (!noFolha) {
545 TVector<int> id; // índice para ordenar os sucessores por heurística
547 CalcularHeuristicas(sucessores, &id, true); // id fica ordenado por LB
548 // processar todos os sucessores, mas apenas se LB<UB
549 for (int i = 0; i < id.Count(); i++) {
550 DebugExpansao(i, id.Count());
551 if (upperBound && sucessores[id[i]]->LowerBound() >= upperBound) {
552 // Nó folha, não identificado antes. Mostrar o estado no caso do detalhe
554 Debug();
555 DebugFolha(true, "%-2s%-2s", Icon(EIcon::FOLHA), Icon(EIcon::UB));
556 // listar os nós não explorados
557 if (Parametro(NIVEL_DEBUG) >= PASSOS) {
559 for (int j = i; j < id.Count(); j++)
560 valores += sucessores[id[j]]->debugID;
561 MostraConjunto(valores, Icon(EIcon::ID));
562 }
563 break;
564 }
565 int resultado = sucessores[id[i]]->BranchAndBound(upperBound);
566 // mesmo que exista um resultado, continuar à procura de melhor
567 if (resultado > 0 && (!upperBound || resultado < upperBound))
569 }
570 DebugCorte(sucessores.Count());
572 }
573 return (solucao != NULL ? solucao->custo : -1);
574}
575
577 TVector<TNo>& sucessores, TVector<int>* id, bool sortLB)
578{
580 // obter os valores heurísticos, e ordentar o índice
581 for (int i = 0; i < sucessores.Count(); i++) {
582 sucessores[i]->heuristica = sucessores[i]->Heuristica();
583 if (id != NULL)
584 heuristicas += (sucessores[i]->heuristica + (sortLB ? sucessores[i]->custo : 0));
585 }
586 if (id != NULL)
587 heuristicas.Sort(id); // ordenar id
588}
589
590
592// Algoritmo AStar: expande primeiro o estado gerado não expandido com melhor custo+heurística
593// Idêntico a CustoUniforme()
595{
596 CListaNo lista(limite);
597 lista.Inserir(this); // estado único
598
599 for (lista.atual = 0; !Parar() && lista.Estado() != NULL; lista.atual = lista.Proximo()) {
600 lista.Estado()->DebugPasso(&lista);
601 if (lista.Estado()->SolucaoCompleta())
602 return ObjetivoAlcancado(lista.Estado(), lista.Completa());
603
605 lista.Estado()->Sucessores(sucessores);
606 // atualiza as heurísticas, sendo esta a única diferença para CustoUniforme
608 // insere por ordem de custo, e não apenas no final
609 lista.Inserir(sucessores);
611 lista.Estado()->DebugSucessores(sucessores);
612 }
613
614 return -1; // falhou
615}
616
617// Redefinir para poder utilizar os algoritmos informados
618// Esta função deve devolver o custo estimado por baixo,
619// desde este estado até ao estado final mais proximo (é um valor minimo),
620// colocando esse valor na variável heuristica
621// chamar este método para actualização de avaliações
630
631
632// Metodo para ser chamado antes de analisar cada sucessor
633void TProcuraConstrutiva::DebugExpansao(int sucessor, int sucessores, bool minimizar)
634{
635 if (minimizar)
637 else
639}
640
641void TProcuraConstrutiva::DebugRamo(const char* ramo, const char* folha) {
642 printf("%s", ramo);
643 printf("%s", folha);
644}
645
646
647// Metodo para ser chamado quando nao ha sucessores ou ha um corte de profundidade
648void TProcuraConstrutiva::DebugCorte(int sucessores, bool duplo)
649{
650 if (Parametro(NIVEL_DEBUG) >= PASSOS && sucessores >= 0) {
651 if (sucessores == 0)
652 DebugFolha(false, Icon(EIcon::FOLHA));
653 else
654 ramo.Pop();
655 }
656
657 if (Parametro(NIVEL_DEBUG) >= PASSOS && sucessores < 0) {
658 ramo.Last() = RAMO_FIM;
659 NovaLinha();
660 printf("%-2s%-2s %d ", Icon(EIcon::CORTE), Icon(EIcon::ID), debugID);
661 }
662}
663
664// Encontrou uma solucao
666{
668 NovaLinha();
669 printf(" %-2s Solução encontrada! %-2sg:%d",
670 Icon(EIcon::SUCESSO), Icon(EIcon::VALOR), custo);
672 Debug();
673 if (!continuar)
674 ramo = {};
675 }
676 else {
678 Debug();
680 ramo.Pop();
681 }
682}
683
684// Informacao de debug na chamada ao metodo recursivo
686{
687 if (Parametro(NIVEL_DEBUG) == ATIVIDADE && expansoes % 1000 == 0)
688 printf("#");
689 if (Parametro(NIVEL_DEBUG) >= PASSOS) {
690 bool raiz = (ramo.Count() <= 1);
691 // neste nível, cada estado expandido é visualizado, não apenas os estados folha
692 if (raiz)
694 NovaLinha(true);
697 DebugEstado(false);
698 if (pai != NULL)
699 printf(" %-2s%s", Icon(EIcon::ACCAO), *pai->Acao(this)); // mostra sempre a ação
700 if ((noFolha && Parametro(NIVEL_DEBUG) >= DETALHE) ||
702 Debug();
703 }
704}
705
706// Chamar sempre que se quer uma nova linha com a arvore em baixo
708{
709 printf("\n");
710 for (int i = 0; i < ramo.Count() - (tudo ? 0 : 1); i++)
711 printf("%s", ramo[i]);
712}
713
714// Passo no algoritmo em largura
716{
717 if (Parametro(NIVEL_DEBUG) == ATIVIDADE && expansoes % 1000 == 0)
718 printf("#");
719 if (Parametro(NIVEL_DEBUG) >= PASSOS) {
720 TString str;
722 NovaLinha(true);
724 DebugEstado(false);
725 if (lista == NULL) {
726 if (expansoes < geracoes) {
727 str.printf("%d:%d", expansoes + 1, geracoes);
728 MostraConjunto(_TV(str), Icon(EIcon::ID));
729 }
730 else
731 printf(" { }");
732 }
733 else {
734 int atual = lista->atual;
736 lista->atual = lista->Proximo();
737 for (; lista->Estado() != NULL; lista->atual = lista->Proximo())
738 valores += lista->Estado()->debugID;
739 lista->atual = atual;
740 MostraConjunto(valores, Icon(EIcon::ID));
741 }
742 }
744 Debug();
745}
746// Mostrar sucessores
748 if (Parametro(VER_ACOES) > 2) {
749 // mostrar apenas ações
750 NovaLinha(true);
751 TProcura::MostraCaixa(Icon(EIcon::ACCAO), ECaixaParte::Fundo, 1, true, -1);
752 for (int i = 0; i < sucessores.Count(); i++) {
753 printf(" %s", *Acao(sucessores[i]));
754 if (i == 2 && sucessores.Count() > 10) {
755 printf(" …");
756 i = sucessores.Count() - 4;
757 }
758 }
759 if (sucessores.Count() > 0) {
761 for (auto suc : sucessores)
762 valores += suc->debugID;
763 if (valores.First() != 0)
764 MostraConjunto(valores, Icon(EIcon::ID));
765 }
766 }
767 else {
769 for (int i = 0; i < sucessores.Count(); i++) {
770 NovaLinha(true);
771 sucessores[i]->DebugEstado(false);
772 printf(" %s", *Acao(sucessores[i])); // mostra sempre a ação
773 ramo.Last() = (i < sucessores.Count() - 1 ? RAMO_CONTINUA : RAMO_VAZIO);
774 if (Parametro(VER_ACOES) == 1)
775 sucessores[i]->Debug();
776 // mostrar só alguns se forem muitos sucessores
777 if (i == 2 && sucessores.Count() > 10) {
778 NovaLinha(true);
779 printf(" …");
780 i = sucessores.Count() - 4;
781 }
782 ramo.Last() = (i < sucessores.Count() - 2 ? RAMO_ESTADO : RAMO_ESTADO_FIM);
783 }
784 ramo.Pop();
785 }
786}
787
788
789// uma nova iteração de um algoritmo iterativo
790void TProcuraConstrutiva::DebugIteracao(int iteracao, const char* simbolo) {
791 Debug(PASSOS, false, "\n ├─────────── %-2s%s %d %-2s%s ──────────── ",
792 Icon(EIcon::ARVORE), simbolo, iteracao, Icon(EIcon::TEMPO),
794}
795
796// informação geral sobre o estado
797void TProcuraConstrutiva::DebugEstado(bool novaLinha) const {
798 if (novaLinha)
799 printf("\n═╤═ "); // ╠ ║
800 else
801 printf("═╤═ ");
802
803 if (debugID > 0) {
804 printf("%-2s%d ", Icon(EIcon::ID), debugID);
805 }
806 printf("%-2sg:%d ", Icon(EIcon::VALOR), custo);
807 if (heuristica)
808 printf("%-2sh:%d ", Icon(EIcon::SUCESSO), heuristica);
809
810 if (expansoes || geracoes || iteracoes) {
811 printf("%-2s ", Icon(EIcon::IND));
812 if (expansoes)
813 printf("%d", expansoes);
814 if (geracoes)
815 printf("|%d", geracoes);
816 if (iteracoes)
817 printf("|%d", iteracoes);
818 }
819
820 printf(" ═══"); // ╣ ║
821}
822
823// Chamar antes de iniciar uma procura
825{
827 geracoes = expansoes = 0;
828 ramo = {};
830 while (!caminho.Empty())
831 delete caminho.Pop();
832 if (solucao != NULL)
833 delete solucao;
834 solucao = NULL;
835 LimparHT();
836}
837
838
840 int resultado = -1;
841 switch (Parametro(ALGORITMO)) {
842 case 1: resultado = LarguraPrimeiro(Dominio(Parametro(LIMITE), 0)); break;
843 case 2: resultado = CustoUniforme(Dominio(Parametro(LIMITE), 0)); break;
845 case 4: resultado = MelhorPrimeiro(Parametro(LIMITE)); break;
846 case 5: resultado = AStar(Dominio(Parametro(LIMITE), 0)); break;
847 case 6: resultado = IDAStar(Dominio(Parametro(LIMITE), 0)); break;
848 case 7:
849 if (Parametro(LIMITE) > 0)
850 DebugIteracao(Parametro(LIMITE), Icon(EIcon::UB));
852 break;
853 }
854 ramo = {};
855 LimparHT();
856 return custo = resultado;
857}
858
859
860
862{
864 if (solucao != NULL) {
866 delete solucao;
867 solucao = NULL;
868 }
869}
870
871
874 int opcao = 0;
876 do {
877 caminho += Duplicar();
878 if (caminho.Count() > 1)
879 caminho.Last()->custo += caminho[caminho.Count() - 2]->custo;
880 else
881 caminho.Last()->custo = 0;
883 ramo = {};
887 // linha com informação
888 DebugEstado();
889 Debug();
891 if (sucessores.Empty()) {
892 TProcura::Mensagem(Icon(EIcon::IMP), "Sem sucessores.");
893 opcao = 0;
894 }
895 else {
896 TString str;
897 printf("\n%-2sSucessor [1-%d, ação(ões), exe]: ", Icon(EIcon::EXP), sucessores.Count());
898 opcao = atoi(str = NovoTexto(""));
899 if (opcao == 0 && strlen(str) > 1) {
900 TVector<TString> acoes = str.tok();
901 opcao = sucessores.Count() + 1;
902 // executar algoritmo
903 if (strcmp(acoes.First(), "exe") == 0) {
905 backup = caminho;
906 caminho = {};
908 int resultado = 0;
909 switch (resultado = ExecutaAlgoritmo()) {
910 case RES_IMPOSSIVEL: TProcura::Mensagem(Icon(EIcon::IMP), "Impossível"); break;
911 case RES_NAO_RESOLVIDO: TProcura::Mensagem(Icon(EIcon::INSUC), "Não resolvido"); break;
912 default: TProcura::Mensagem(Icon(EIcon::SOL), "Resolvido (%d)", resultado); break;
913 }
915 if (solucao != NULL) {
917 delete backup.Pop();
918 backup += caminho;
919 caminho = backup;
920 caminho.Pop(); // este último estado será adicionado
921 }
922 else
923 caminho = backup;
924 }
925 else {
926 int nAcoes = 0;
927 // evitar bloqueio de ações por estado repetidos
930 for (int i = 0; i < acoes.Count(); i++) {
931 // executar a ação
932 if (Acao(acoes[i]))
933 nAcoes++;
934 else {
935 TProcura::Mensagem(Icon(EIcon::IMP), "Ação %s inválida.", *acoes[i]);
936 break;
937 }
938
939 if (i < acoes.Count() - 1) {
940 // há outra ação, é preciso atualizar o caminho
941 caminho += Duplicar();
942 if (caminho.Count() > 1)
943 caminho.Last()->custo += caminho[caminho.Count() - 2]->custo;
944 else
945 caminho.Last()->custo = 0;
946 }
947 }
949 if (nAcoes > 0)
950 TProcura::Mensagem(Icon(EIcon::SOL), "Executadas %d ações.", nAcoes);
951 }
952 }
953 }
954 if (opcao > 0 && opcao <= sucessores.Count()) {
955 Copiar(sucessores[opcao - 1]);
956 TProcura::Mensagem(Icon(EIcon::SOL), "Ação executada.");
957 }
959 } while (opcao != 0);
960}
961
966
970 int usado = 0; // contar para calcular taxa de ocupação
971 for (int i = 0; i < TAMANHO_HASHTABLE; i++)
972 if (elementosHT[i].Count() > 0) {
973 usado++;
974 elementosHT[i].Count(0);
975 }
976 // reportar estatísticas se existir muito reuso
977 if (Parametro(NIVEL_DEBUG) >= DETALHE && usado > 0 && usado * 2 <= colocadosHT) {
978 NovaLinha();
979 printf("HT: utilização %d%%, reuso: %.2f vezes",
980 usado * 100 / TAMANHO_HASHTABLE,
981 1.0 * colocadosHT / usado);
982 }
983 }
984 else
985 for (int i = 0; i < TAMANHO_HASHTABLE; i++)
986 elementosHT[i].Count(0);
987 colocadosHT = 0;
988 // coloca o estado atual na hashtable, para não ser gerado
989 ExisteHT();
990 }
991}
992
994 // caso não exista, retorna falso e insere
995 // se existe retorna verdade, e estiver lá outro elemento, substitui
996 unsigned int original = Hash();
997 unsigned int indice = original % TAMANHO_HASHTABLE;
998 if (elementosHT[indice] != estadoCodHT) {
999 SubstituirHT(indice);
1000 colocadosHT++;
1001 return false; // não existia
1002 }
1003 // elemento é igual, mas se o custo for mais alto, não conta
1004 // já que este elemento com custo mais baixo, pode conduzir à solução ou melhores soluções
1005 // neste caso substitui e retorna que o estado não existe
1006 if (custoHT[indice] > custo) {
1007 SubstituirHT(indice);
1008 return false; // existe mas com um custo mais alto
1009 }
1010 return true; // igual, estado já analisado
1011}
1012
1014{
1015 // substituir elemento
1016 elementosHT[indice]=estadoCodHT;
1017 custoHT[indice] = custo;
1018}
1019
1020// estados repetidos: verificar todos os gerados
1021// implementar para utilizar hashtables com perdas
1022// converte o estado atual para a variável estado, utilizando o menor espaço possível
1023// caso existam simetrias, normalizar o estado antes de codificar, para considerar exploradas todas as versões
1025 estado.Count(0);
1026}
1027
1029{
1030 if (id == IND_CUSTO)
1031 return custo;
1032 else if (id == IND_EXPANSOES)
1033 return expansoes;
1034 else if (id == IND_GERACOES)
1035 return geracoes;
1036 else if (id == IND_LOWER_BOUND)
1037 return lowerBound;
1038 return TProcura::Indicador(id);
1039}
1040
1041
1042
constexpr int BUFFER_SIZE
@ VER_ACOES
Mostra estado a cada K ações. Se 1, mostra sempre estados e nunca ações.
@ LIMITE
Valor dependente do algoritmo. Exemplo: Profundidade limitada.
@ BARALHAR_SUCESSORES
Baralhar os sucessores ao expandir.
@ RUIDO_HEURISTICA
Ruído a adicionar à heurística para testes de robustez.
@ ESTADOS_REPETIDOS
Forma de lidar com estados repetidos (ignorá-los, ascendentes, gerados).
@ IND_GERACOES
número de estados gerados durante a procura
@ IND_CUSTO
o resultado é o custo da solução atual, sendo um upper bound
@ IND_EXPANSOES
número de expansões efetuadas durante a procura
@ IND_LOWER_BOUND
valor mínimo para a melhor solução, se igual ao custo da solução obtida, então esta é ótima
@ IGNORADOS
ignorados os estados gerados repetidos
@ ASCENDENTES
estados são comparados com ascendentes, e se forem repetidos são removidos
@ GERADOS
estados são comparados com todos os gerados, e se forem repetidos são removidos
@ PROFUNDIDADE_PRIMEIRO
Executa a procura em profundidade primeiro, algoritmo cego.
@ MELHOR_PRIMEIRO
Executa a procura melhor primeiro, algoritmo informado.
@ IDA_STAR
Executa a procura IDA*, algoritmo informado.
@ BRANCH_AND_BOUND
Executa o algoritmo Branch-and-Bound, um algoritmo informado.
constexpr int TAMANHO_HASHTABLE
constexpr int RES_NAO_RESOLVIDO
Definition TProcura.h:18
@ PASSOS
Exibe passos intermediários.
Definition TProcura.h:93
@ ATIVIDADE
Apenas eventos principais.
Definition TProcura.h:92
@ COMPLETO
Mostra toda a execução detalhadamente.
Definition TProcura.h:95
@ NADA
Sem informações de debug.
Definition TProcura.h:91
@ DETALHE
Debug detalhada sobre estados e decisões.
Definition TProcura.h:94
constexpr int RES_IMPOSSIVEL
Definition TProcura.h:17
@ CONT_ALGORITMO
Tempo da execução do algoritmo por instância.
Definition TProcura.h:112
@ ALGORITMO
Algoritmo base a executar.
Definition TProcura.h:70
@ NIVEL_DEBUG
Nível de debug, de reduzido a completo.
Definition TProcura.h:71
TVector< int > _TV(const char *str)
Definition TVector.h:1460
Lista ordenada de nós para algoritmos de procura informada.
Definition CListaNo.h:21
unsigned int Hash() const
Definition TVector.h:1294
Representa um estado no espaço de estados.
void DebugCorte(int sucessores=-1, bool duplo=false)
void DebugEstado(bool novaLinha=true) const
void DebugSucessores(TVector< TNo > &sucessores)
void DebugFolha(bool fimLinha, const char *fmt,...)
int SolucaoEncontrada(bool continuar=false)
static TBits elementosHT[TAMANHO_HASHTABLE]
void DebugSolucao(bool continuar=false)
void ExecucaoTerminada() override
Chamar após a execução do algoritmo. Grava o tempo consumido.
void DebugRamo(const char *ramo, const char *folha)
int SolucaoParcial(int i, TVector< TNo > &sucessores, int iAux=-1, TVector< int > *id=NULL)
void CalcularHeuristicas(TVector< TNo > &sucessores, TVector< int > *id=NULL, bool sortLB=false)
void NovaLinha(bool tudo=true)
int ObjetivoAlcancado(int item, TVector< TNo > &lista)
static void LibertarVector(TVector< TNo > &vector, int excepto=-1, int maiorQue=-1)
void DebugPasso(CListaNo *lista=NULL)
virtual void SubstituirHT(int indice)
void Explorar()
definir para explorar manualmente os dados (não definido em TProcura, apenas em TProcuraConstrutiva)
void CalculaCaminho(bool completa=true)
void DebugExpansao(int sucessor, int sucessores, bool minimizar=true)
void DebugChamada(bool noFolha)
void DebugIteracao(int iteracao, const char *simbolo)
void LimparEstatisticas() override
Chamar antes da execução do algoritmo. Limpa valores estatísticos, e fixa o instante limite de tempo ...
void VerificaLimites(int limite, int porProcessar, TVector< TNo > &sucessores)
static int custoHT[TAMANHO_HASHTABLE]
static bool memoriaEsgotada
Flag indicando problemas de memória esgotada.
Definition TProcura.h:583
static void MostraCaixa(TVector< TString > titulo, ECaixaParte parte, TVector< int > largura, bool aberta=true, int identacao=0)
Definition TProcura.cpp:368
virtual void Debug(bool completo=true)
Mostra o estado no ecrã, para debug.
Definition TProcura.cpp:86
int Parametro(int id) const
Definition TProcura.h:607
static int Dominio(int &variavel, int min=INT_MIN, int max=INT_MAX)
Limita o domínio de um parâmetro inteiro.
static int resultado
Resultado retornado pelo algoritmo na última execução.
Definition TProcura.h:575
virtual int64_t Indicador(int id)
Retorna um indicador, após a execução do algoritmo.
Definition TProcura.cpp:73
static TString NovoTexto(TString prompt)
static int iteracoes
Número total de iterações realizadas na última execução.
Definition TProcura.h:579
virtual void ResetParametros()
Inicializa os parâmetros, indicadores e instâncias.
Definition TProcura.cpp:48
virtual bool Parar(void)
Verifica se a procura deve ser interrompida.
Definition TProcura.h:405
static TString MostraTempo(double segundos)
Mostra tempo num formato humano.
Definition TProcura.cpp:752
virtual void ExecucaoTerminada()
Chamar após a execução do algoritmo. Grava o tempo consumido.
static void Mensagem(TString titulo, const char *fmt,...)
Definition TProcura.cpp:502
static TVector< TIndicador > indicador
Indicadores que podem ser calculados após a execução, quer com informação da instãncia,...
Definition TProcura.h:570
static TVector< int > indAtivo
Definition TProcura.h:571
static double Cronometro(enum ECronometro id=CONT_ALGORITMO, bool inicializar=false)
retorna o tempo em segundos desde que o cronómetro foi inicializado
Definition TProcura.h:854
static TVector< TParametro > parametro
Parâmetros a serem utilizados na configuração atual.
Definition TProcura.h:567
virtual void LimparEstatisticas()
Chamar antes da execução do algoritmo. Limpa valores estatísticos, e fixa o instante limite de tempo ...
Definition TProcura.cpp:92
static double tempo
tempo consumido na última execução.
Definition TProcura.h:577
static void MostraConjunto(TVector< int > valores, const char *etiqueta)
Definition TProcura.cpp:969
Item & First()
Definition TVector.h:224
Item & Pop()
Definition TVector.h:193
TVector< Item > & Sort(TVector< int > *idxvect=nullptr)
Ordena todo o vetor, opcionalmente devolvendo índices ordenados.
Definition TVector.h:606
TVector< Item > & Add(Item a)
Definition TVector.h:181
Item & Last()
Definition TVector.h:227
int Count() const
Definition TVector.h:230
TVector< Item > & Push(Item a)
Definition TVector.h:190
int CustoUniforme(int limite=0)
Executa a procura por custo uniforme, algoritmo cego.
int ProfundidadePrimeiro(int nivel=0)
Executa a procura em profundidade primeiro, algoritmo cego.
int LarguraPrimeiro(int limite=0)
Executa a procura em largura primeiro, algoritmo cego.
int AStar(int limite=0)
Executa a procura A*, algoritmo informado.
int IDAStar(int upperBound=0)
Executa a procura IDA*, algoritmo informado.
int MelhorPrimeiro(int nivel=0)
Executa a procura melhor primeiro, algoritmo informado.
int BranchAndBound(int upperBound=0)
Executa o algoritmo Branch-and-Bound, um algoritmo informado.
virtual void Sucessores(TVector< TNo > &sucessores)
Coloca em sucessores a lista de estados sucessores.
virtual bool SolucaoCompleta(void)
Verifica se o estado actual é objectivo (é uma solução completa)
void ResetParametros() override
Redefinição. Ver TProcura::ResetParametros().
virtual void Copiar(TNo objecto)
Fica com uma cópia do objecto.
virtual bool Validar(TVector< TString > solucao)
Verifica a validade de uma solução para a instância atual.
virtual TNo Duplicar(void)=0
Cria um objecto que é uma cópia deste.
int64_t Indicador(int id) override
Redefinição. Ver TProcura::Indicador().
TVector< TString > Solucao()
retorna uma solução no formato do TResultado, para ser gravada em ficheiro de soluções,...
int ExecutaAlgoritmo()
Executa o algoritmo com os parâmetros atuais.
virtual int Heuristica(void)
Função para calcular quanto falta para o final, o valor da heurística.
virtual void Codifica(TBits &estado)
Codifica o estado para um vetor de inteiros de 64 bits.
virtual TString Acao(TNo sucessor)
Retorna a ação (movimento, passo, jogada, lance, etc.) que gerou o sucessor.
int heuristica
Estimativa para o custo até um estado objetivo, se disponível.
int custo
Custo total acumulado desde o estado inicial.
TNo pai
Ponteiro para o estado pai, na árvore de procura.
int debugID
ID do estado, para efeitos de debug.
static constexpr const char * RAMO_ESTADO
static int expansoes
Número de expansões efetuadas.
static int lowerBound
Valor mínimo que a solução pode apresentar, obtido pela procura.
static constexpr const char * RAMO_NOVO
static constexpr const char * RAMO_ESTADO_FIM
static int geracoes
Número de estados gerados.
static TVector< TNo > caminho
Solução retornada pela procura (os estados devem ser libertados).
static constexpr const char * RAMO_ESTADO2
static TVector< const char * > ramo
static int custoAcao
custo da última ação realizada (Acao(TString))
static constexpr const char * RAMO_FIM
static TNo solucao
Estado objetivo encontrado, retornado pela procura (deve ser libertado).
static constexpr const char * RAMO_CONTINUA
static constexpr const char * RAMO_VAZIO
static constexpr const char * RAMO_ESTADO2_FIM
unsigned int rand(int seq)
Retorna o próximo valor pseudo-aleatório.
Definition TRand.cpp:46