documentacao - 2º trabalho prático - aeds iii

23
Universidade Federal de São João Del Rei Ciência da Computação Trabalho Prático 2: Algoritmos para Resolução de Sudokus Fernando H. C. Tavares de Lima Walkiria K. Resende Professor – Leonardo Rocha SÃO JOÃO DEL REI – MG 01/05/2010 1-23

Upload: fernando-tavares

Post on 25-Jul-2015

344 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: Documentacao - 2º Trabalho Prático - AEDS III

Universidade Federal de São João Del ReiCiência da Computação

Trabalho Prático 2: Algoritmos para Resolução de Sudokus

Fernando H. C. Tavares de LimaWalkiria K. Resende

Professor – Leonardo Rocha

SÃO JOÃO DEL REI – MG01/05/2010

1-23

Page 2: Documentacao - 2º Trabalho Prático - AEDS III

SUMÁRIO

Introdução............................................................................... 3

Proposta de trabalho.............................................................. 4

Formatos de entrada/saída.................................................... 6

Solução apresentada............................................................. 7

Fluxograma do Algoritmo...................................................... 9

O Algoritmo............................................................................. 10

Listagem de rotina e análise de suas complexidades........ 12

Testes e resultados................................................................ 15

Conclusão............................................................................... 23

Como executar o programa................................................... 23

Bibliografia.............................................................................. 23

2-23

Page 3: Documentacao - 2º Trabalho Prático - AEDS III

INTRODUÇÃO

O Sudoku, como conhecido hoje, teve origem nos EUA em meados de 1970, e atingiu um enorme sucesso no Japão, onde a palavra Sudoku significa manter os dígitos únicos. No ocidente o jogo demorou a tornar-se popular, e só passou a ser publicado, em Londres, em 2004, chegando efetivamente ao Brasil, apenas em 2005.

Como prova do sucesso alcançado pelo jogo, atualmente, existem revistas que publicam apenas Sudokus, jornais que trazem o jogo como passa tempo, e são realizados, também, campeonatos mundiais de Sudoku, nos quais os vencedores recebem prêmios em dinheiro. Outra prova do sucesso foi a criação do Al Escargot, Sudoku mais difícil de ser resolvido em seu tempo de criação; obra de um matemático finlandês que levou 3 meses e milhões de combinações para concluir o trabalho.

O objetivo do jogo é preencher todos os quadrados com números de 1 a 9, onde eles devem ser diferentes nas colunas, nas linhas e nos grids, que são quadrados 3x3 dentro da matriz maior 9x9. Até o momento, sabe-se que o mínimo de casas preenchidas para gerar um Sudoku com solução única é 17, e, o máximo que casas possível preenchidas para não garantir uma solução única é 77.

Existem diversas maneiras de solucionar o problema, assim como existem variações nos níveis de dificuldade.

Como inúmeros jogos podem possuir softwares para resolução, o trabalho aqui documentado apresenta duas formas de solucionar o jogo através de um programa de computador escrito em linguagem C.

Observe que não é um programa para jogar Sudoku, mas sim um programa que recebe qualquer Sudoku e apresenta a solução.

Uma das formas é a partir da força bruta, ou seja, o algoritmo deve trabalhar testando um grande número de possibilidades até encontrar a solução correta. Esta é uma solução inviável, e foi aqui implementada, apenas, para fins didáticos. A outra forma é uma heurística, que deve trabalhar o menos possível para encontrar a solução correta.

3-23

Page 4: Documentacao - 2º Trabalho Prático - AEDS III

PROPOSTA DE TRABALHO

Este trabalho tem, por objetivo, exercitar os conceitos relacionados a problemas NP-Completos e aos paradigmas de projetos de algoritmos.

Você trabalha na Ximguiling Entertainment Games Inc., uma importante empresa multinacional da área de jogos eletrônicos. O presidente da Ximguiling, senhor Michiu, com a intenção de motivar e avaliar seus funcionários lançou um desa_o na empresa, um concurso de algoritmos para resolver probleminhas de Sudoku. Segundo Michiu, o funcionário que conseguir desenvolver o algoritmo mais rápido, ou seja, aquele que obtém a solução de um Sudoku em menor tempo, terá um aumento salarial de 300% e se tornará o vice-presidente da empresa.

A palavra Sudoku significa “número sozinho” em japonês, o que mostra exatamente o objetivo do jogo. O Sudoku existe desde a década de 1970, mas começou a ganhar popularidade no final de 2004 quando começou a ser publicado diariamente na sessão de puzzles do jornal The Times.

Entre abril e maio de 2005 o puzzle começou a ganhar um espaço na publicação de outros jornais britânicos e, poucos meses depois, ganhou popularidade mundial.

O objetivo do Sudoku é preencher todos os espaços em branco do quadrado maior, que está dividido em nove grids, com os números de 1 a 9. Os algarismos não podem se repetir na mesma coluna, linha ou grid.

Figura 1: Exemplo Sudoku

Assim, para esse trabalho, elabore duas estratégicas para solucionar problemas de Sudoku:

1. Utilizando um algoritmo força bruta;2. Utilizando alguma heurística própria.

O programa deve receber dois parâmetros pela linha de comando, utilizando a primitiva getopt:

1. arquivo contendo as configurações inicial do Sudoku a ser resolvido;2. arquivo onde o resultado do Sudoku do arquivo de entrada deverá ser gravado;

Para o exemplo da figura acima, o arquivo de entrada seria como apresentado abaixo: onde o v representa uma casa vazia.

4-23

Page 5: Documentacao - 2º Trabalho Prático - AEDS III

v v 1 8 v v 3 v vv 4 9 7 1 6 v 8 vv 2 v v 9 v v v v

v v 4 v v v v 2 vv 5 6 v v 1 8 v vv 1 v v v v 5 v 9

v v v v v v 4 3 vv v v 1 6 v v v 87 v v v v 2 v v 1

O arquivo de saída deverá ser como o arquivo de entrada, obviamente substituindo os “v” pela soluções.

OBS.: Com o objetivo de facilitar a manipulação de dados foi definido que os “v” do arquivo de entrada será substituído por 0.

5-23

Page 6: Documentacao - 2º Trabalho Prático - AEDS III

FORMATOS DE ENTRADA/SAÍDA

Conforme a proposta de trabalho, a entrada de dados ocorre através de um arquivo que contém um Sudoku a ser resolvido e a saída de dados é feita pela impressão na tela e gravação da solução do Sudoku no arquivo.

Foram desenvolvidas funções de leitura e escrita em arquivo, as quais dependem da função getopt() para ler o arquivo.

Por motivos de organização do projeto, foi estabelecida uma condição que impõe a utilização de um parâmetro de leitura do arquivo do Sudoku (-a) e outro parâmetro de leitura para o arquivo que será gravada a solução(-b). Caso estas imposições não sejam atendidas, serão impressos na tela os parâmetros a serem utilizados e quais os devidos tipos de arquivos referentes a cada parâmetro.

A organização do arquivo de entrada do Sudoku deve conter a seguinte representação:

0 7 0 0 0 3 0 2 8 0 0 9 6 0 0 1 0 0

0 1 0 0 2 9 0 6 3

0 8 4 7 0 0 0 0 61 6 7 0 0 0 5 8 49 0 0 0 0 6 2 1 0

8 3 0 2 6 0 0 7 00 0 1 0 0 4 8 0 07 4 0 3 0 0 0 5 0

Obs.: Os número são separados por espaço e os grids por um “Tab” ou, então, um “Enter”.

Solucao:

---------------------------| 4 7 6 | 1 5 3 | 9 2 8 || 3 2 9 | 6 8 7 | 1 4 5 || 5 1 8 | 4 2 9 | 7 6 3 |---------------------------| 2 8 4 | 7 1 5 | 3 9 6 || 1 6 7 | 9 3 2 | 5 8 4 || 9 5 3 | 8 4 6 | 2 1 7 |---------------------------| 8 3 5 | 2 6 1 | 4 7 9 || 6 9 1 | 5 7 4 | 8 3 2 || 7 4 2 | 3 9 8 | 6 5 1 |---------------------------

6-23

Page 7: Documentacao - 2º Trabalho Prático - AEDS III

SOLUÇÃO APRESENTADA

1. Tipos abstratos de dados

Foi implementada uma matriz por alocação dinâmica, a qual possui 9 linhas e 9 colunas, cujos índices variam de 0 a 8.

2. Métodos de resolução do SudokuPara melhor compreender o funcionamento do algoritmo utilizaremos a noção de

“casas vizinhas”, onde, se uma casa está ao lado da outra, então, estas são vizinhas, e, se uma casa está na ultima coluna, então, esta é vizinha da primeira casa da linha seguinte.

Além disso, o algoritmo utiliza duas matrizes, sendo que, uma matriz é a que armazena o Sudoku original e a outra é a matriz de testes. É importante ressaltar que a matriz de testes, na primeira execução, será igual ao Sudoku original.

2. 1 Resolvendo o Sudoku pela heurística

Com o objetivo de resolver o Sudoku rapidamente, o método de backtracking foi o escolhido pela dupla, visto que, este é muito referenciado como a melhor forma de solucionar este problema.

Para implementar o backtracking, foi necessário desenvolver funções para verificar se, ao inserir um elemento em uma devida posição na matriz, o mesmo causaria uma quebra das regras do jogo.

O algoritmo do backtracking se baseia em verificar se um valor - iniciando de 1 (um) - pode ser inserido em uma posição cujo Sudoku original contém 0; caso esse valor seja válido, então a posição de teste receberá esse valor e o teste se repetirá de forma recursiva para o elemento vizinho posterior.

Se não for possível inserir um valor em uma posição, este valor será incrementado e o teste será refeito sobre o novo valor.

E esse processo se repete até que se encontre um valor possível ou até testar todos os valores de 1 a 9.

Se ao chegar a 9 e, mesmo assim, não encontrar um valor possível, o algoritmo faz um retorno da função até chegar ao vizinho anterior e, então, incrementa-o e continua os processos descritos anteriormente, até que se chegue à última posição do Sudoku. E, então, finalmente, a solução é encontrada, impressa e gravada em um arquivo, e o programa é fechado.

Esse método de resolução do Sudoku apesar de simples é extremamente rápido e garante 100% de precisão no resultado.

2.2 Resolvendo o Sudoku pela força bruta

Para resolver o Sudoku por força bruta, foi desenvolvido um algoritmo recursivo que testa todas as possibilidades de preenchimento das “casas” vazias de um Sudoku.

A função forcabruta() chamará a função recursiva testaFB() na casa (0,0). A função testaFB() fará as seguintes verificações:

1) Se o elemento da casa em análise for diferente de zero (0) no Sudoku original, será feita uma verificação para saber se foi encontrada uma solução. Se isto acontecer, esta será impressa e salva e, após isto, o programa terminará.

7-23

Page 8: Documentacao - 2º Trabalho Prático - AEDS III

Caso contrário, será feita uma chamada da função testaFB() no vizinho posterior. Se a função retornar da recursão, então, essa recursão terminará.

2) Se o elemento da casa em análise for igual à zero (0) no Sudoku original, será executado um laço aninhado que vai da casa em teste até a última casa do Sudoku. Dentro do laço aninhado, a posição em teste será incrementada e, então, será feita uma verificação para saber se foi encontrada uma solução. Se isto acontecer, esta será impressa e salva e, após isto, o programa terminará. Caso contrário, será feita uma chamada da função testaFB() no vizinho posterior. Se a função retornar da recursão, então, verificará se a posição em teste tem valor igual a 9 (nove). Se for, esta passará a ser 1 (um) e a função retornará. Caso contrário, a função finalizará o laço interno e, então, retornará.

Estes processos terminarão, apenas, quando a solução for encontrada.

Na prática, a função preencherá todas as casas vazias com 1 (um) e, partindo do canto inferior à direita, irá incrementar as casas que, na matriz original, possuem 0. Este processo acontecerá até que a solução seja encontrada. É importante ressaltar que esse algoritmo é excessivamente devagar e, portanto, inviável para resolver um Sudoku, mas, ainda assim, é um algoritmo que SEMPRE chega a uma solução ótima.

8-23

Page 9: Documentacao - 2º Trabalho Prático - AEDS III

9-23

Page 10: Documentacao - 2º Trabalho Prático - AEDS III

O ALGORITMO

Uma das formas mais comuns de resolver um Sudoku é utilizar backtracking. Esta técnica consiste em percorrer, sistematicamente, o Sudoku, da esquerda para a direita e de cima para baixo, até encontrar a solução ou até encontrar um deadlock.

Em caso de deadlock, a função começa o backtracking (voltar atrás), que é um procedimento que retorna pelo mesmo caminho percorrido, até encontrar um novo ponto de partida para continuar a sua execução. Neste caso, a função chegará a um ponto que pode ser incrementado e, a partir deste, será possível fazer novos testes e, então, repetir o processo de procura pela solução ou achar um deadlock para tentar outra vez.

Fazendo uma observação importante, utilizamos o comando exit(0), este é conhecido como saída após obtenção de sucesso e foi utilizado para agilizar o processo de finalização do programa, visto que, com esse, o algoritmo diminui o tempo de processamento em até 5x, já que não precisa realizar o retorno das funções após encontrar a solução do Sudoku.

Trecho do código:

void fazerTestes(int i, int j, int **matriz, int **copia, int k, char *b){int possivel;if(copia[i][j]!=0){

if(j<8) fazerTestes(i, j+1, matriz, copia, 1, b);else if(i<8) fazerTestes(i+1, 0, matriz, copia, 1, b);else {

imprimirMatriz(matriz); printf("\n"); gravarSolucao(b, matriz);matriz = desalocaMatriz(9, 9, matriz); /* Desaloca a Matriz */copia = desalocaMatriz(9, 9, copia); /* Desaloca a Copia */printf("\nContagem de Operações: %d\n", cont);imprimirTempo(); exit(0);

} }else{

possivel= verificarValidade(i, j, matriz, k);if(possivel ==1){

matriz[i][j] = k; if(j<8) fazerTestes(i, j+1, matriz, copia, 1, b);else if(i<8) fazerTestes(i+1, 0, matriz, copia, 1, b);else {

imprimirMatriz(matriz); printf("\n");gravarSolucao(b, matriz);matriz = desalocaMatriz(9, 9, matriz); /* Desaloca a Matriz */

copia = desalocaMatriz(9, 9, copia); /* Desaloca a Copia */printf("\nContagem de Operações: %d\n", cont);imprimirTempo(); exit(0);

}}k++; if(k<=9) fazerTestes(i, j, matriz, copia, k, b);if(k==10) matriz[i][j]=0;

}}

10-23

Page 11: Documentacao - 2º Trabalho Prático - AEDS III

O algoritmo trata também do método de força bruta, o qual consiste em testar todas as possibilidades de valores para as casas até que se encontre uma solução.

Trecho do código:

void testaFB(int i, int j, int **matriz, int **copia, char *b){int x, y, impossivel;if(copia[i][j] !=0){

impossivel= verificaImpossibilidade(matriz);if(impossivel ==0){

imprimirMatriz(matriz); printf("\n");gravarSolucao(b, matriz); matriz = desalocaMatriz(9, 9, matriz); /* Desaloca a Matriz */copia = desalocaMatriz(9, 9, copia); /* Desaloca a Copia */printf("\nContagem de Operações: %d\n", cont);imprimirTempo(); exit(0);

}if(j<8) testa(i,j+1, matriz, copia,b);else if (i<8) testaFB(i+1,0, matriz, copia,b);

}else{

for(x=i; x<9; i++){for(y=j; y<9; j++){

matriz[x][y]++;impossivel= verificaImpossibilidade(matriz);if(impossivel ==0){

imprimirMatriz(matriz); printf("\n");gravarSolucao(b, matriz);matriz = desalocaMatriz(9, 9, matriz);

copia = desalocaMatriz(9, 9, copia);printf("\nContagem de Operações: %d\n", cont); imprimirTempo(); exit(0);

}if(y<8) testaFB(x,y+1, matriz, copia,b);else if (x<8) testaFB(x+1,0, matriz, copia,b);if(matriz[x][y]==9) { matriz[x][y] =1; return; }

}}

}}

11-23

Page 12: Documentacao - 2º Trabalho Prático - AEDS III

LISTAGEM DE ROTINAS E ANÁLISEDE SUAS COMPLEXIDADES

void criarMatriz(int **matriz):É uma função que cria uma matriz de zeros.Esta função possui dois laços aninhados e um comando interno de atribuição. Se os laços forem analisados como uma única estrutura de repetição, esta será executada n*n vezes, onde n é o número de linhas e colunas. Portanto, possui a complexidade definida por n*n*O(1) = O(n²).Complexidade: O(n²).

int **alocaMatriz(int m, int n):É uma função que faz a alocação dinâmica da matriz.Esta função possui um laço, uma chamada a função criarMatriz() e uma sequência de comandos com complexidades constante. Como o laço é executado n vezes, onde n é o número de linhas, e o restante das funções possui complexidade constante, temos que a complexidade dessa função é definida por: n*O(1);Complexidade: O(n).

while((opt = getopt (argc, argv, "a:b:")) != -1):É uma estrutura de repetição que tem como condição de parada a função getopt() a qual receberá dois parâmetros: 1 (um) arquivo de entrada e 1 (um) arquivo de saída.Como as funções internas desta função possuem complexidade O(1), sua complexidade é [ 2*O(1)].Complexidade: O(1).

void carregarMatriz(char *arqMatriz, int **a):É uma função que carrega os dados do arquivo do Sudoku para a matriz.Esta função possui 2 (dois) laços, sendo que, o externo é executado apenas uma vez e o interno n vezes, onde n é o número de linhas. E como os comandos internos possuem complexidade constante, a complexidade desta função é dada por: n*O(1).Complexidade: O(n).

void imprimirMatriz(int **matriz):É uma função que imprime a matriz.Esta função possui dois laços aninhados e um comando interno de atribuição. Se os laços forem analisados como uma única estrutura de repetição, esta será executada n*n vezes, onde n é o número de linhas e colunas. Portanto, possui a complexidade definida por n*n*O(1) = O(n²).Complexidade: O(n²).

void gravarSolucao(char *solucao, int **matriz):É uma função que grava o resultado do Sudoku em um arquivo.Esta função possui 2 (dois) laços, sendo que o externo é executado apenas uma vez e o interno n vezes, onde n é o número de linhas. E como os comandos internos possuem complexidade constante, a complexidade desta função é dada por: n*O(1).Complexidade: O(n).

12-23

Page 13: Documentacao - 2º Trabalho Prático - AEDS III

int **desalocaMatriz (int m, int n, int **matriz):É uma função que desaloca a matriz.Esta função possui um laço e uma sequência de comandos com complexidade constante. Como o laço é executado n vezes, onde n é o número de linhas, e o restante das funções possui complexidade constante, temos que a complexidade desta função é definida por: n*O(1);Complexidade: O(n).

void imprimirTempo():É uma função que imprime os tempos de usuário, sistema, total e real.Esta função possui apenas comandos de complexidade constante, portanto, possui complexidade O(1).Complexidade: O(1).

int verificarGrid(int i, int j, int **matriz, int valor);É uma função que verifica se uma posição é válida no grid.Esta função possui 3 (três) laços, sendo que, 2 (dois) destes são aninhados e juntos são executados n vezes, onde n é o número de grids, e o outro laço possui apenas comandos constantes e é executado n vezes em seu pior caso, onde n é o número de elementos em um grid. Portanto, a complexidade desta função é dada por O (Max(n*O(1), n*O(1))).Complexidade: O(n).

int verificarValidade(int i, int j, int **matriz, int valor);É uma função que verifica se uma posição é válida no Sudoku. Ou seja, se não infringe nenhuma regra do mesmo.Esta função possui uma chamada para a função verificarGrid(), comandos constantes e um laço que será executado n vezes, onde n pode ser considerado o número de linhas ou de colunas.Portanto, a complexidade desta função é dada por O(Max(n*O(1),O(n))).Complexidade: O(n).

int verificaImpossibilidade(int **matriz);É uma função que verifica se o Sudoku está resolvido.Esta função possui 2 (dois) laços aninhados que, se analisados como uma única estrutura, será executados n*n vezes no seu pior caso. Estes laços contêm uma chamada para a função verificarValidade(). Portanto, a complexidade dessa função é dada por: n*n*O(n).Complexidade: O(n³).

void fazerTestes(int i, int j, int **matriz, int **copia, int k, char *b);Esta é a função que realiza os testes do backtracking e chega à solução do Sudoku. Não conseguimos desenvolver essa análise de complexidade.Complexidade: O (??).

void backtracking(char *a, char *b, int **matriz, int **copia, double iniciotempo);É a função do backtracking.Como não sabemos a complexidade da função fazerTestes(), não foi possível definir a complexidade desta função.Complexidade: O (??).

13-23

Page 14: Documentacao - 2º Trabalho Prático - AEDS III

void testaFB(int i, int j, int **matriz, int **copia, char *b):É uma função que realiza os testes da forcaBruta e chega à solução do Sudoku.Esta função possui laços, condições e recursão. Para definir sua complexidade vamos desconsiderar estas estruturas e basear no funcionamento da função na prática, a qual já foi explicada anteriormente.

É possível perceber que um Sudoku, sem preencher, possui as seguintes possibilidades de preenchimento:1ª linha: 9! possibilidades2ª linha: 9! possibilidades3ª linha: 9! possibilidades4ª linha: 9! possibilidades5ª linha: 9! possibilidades6ª linha: 9! possibilidades7ª linha: 9! possibilidades8ª linha: 9! possibilidades9ª linha: 9! possibilidades

Portanto, segue a equação de recorrência a seguir:T(n) = 9! * T(n-1)T(n-1) = 9! * T(n-2)

...T(2) = 9! * T(1)T(1) = 9!Substituindo temos: 9!n.Portanto, temos que a complexidade desta função é da ordem de (cc)n.Complexidade: O ((cc)n).

void forcaBruta(char *a, char *b, int **matriz, int **copia, double iniciotempo);É a função da força bruta.Esta função faz duas chamadas à função carregaMatriz() e uma à testaFB().Portanto, sua complexidade é dada por: O(max(n,n,(cc)n));Complexidade: O ((cc)n).

int main (int argc, char *argv[]):É a função principal do programa.Esta função possui uma sequência de comandos de complexidade constante e uma chama a função backtracking() ou forcaBruta().A primeira não conseguimos calcular sua complexidade, porém a segunda possui complexidade O ((cc)n). Como a complexidade da forcaBruta() é a maior, então essa é a complexidade da main().Complexidade: O ((cc)n).

14-23

Page 15: Documentacao - 2º Trabalho Prático - AEDS III

TESTES E RESULTADOS

Os Sudokus abaixo foram gerados pelo GNOME Sudoku 2.30.0 ou encontrados no site: http://www.fiendishSudoku.com/pt/Sudoku.html.

Os testes foram realizados em uma máquina com a seguinte descrição:

Hardware/Software ModeloSistema operacional: Ubuntu 10.04 LTS 32bitsProcessador: Intel Core 2 Duo CPU T5670 1.80GHzPlaca de Vídeo: Intel GMA X3100RAM: 3,00 GB, DDR2, 667MHz

1. Testes utilizando Backtracking

Sudoku Fácil 1:Tempo de usuário: 0.000 msTempo de Sistema: 0.004 msTempo Total: 0.004 msTempo Real: 0.741 ms

Contagem de operações: 756

Sudoku Fácil 2:Tempo de usuário: 0.000 msTempo de Sistema: 0.000 msTempo Total: 0.000 msTempo Real: 0.809 ms

Contagem de operações: 340

Sudoku Fácil 3:Tempo de usuário: 0.000 msTempo de Sistema: 0.004 msTempo Total: 0.004 msTempo Real: 0.362 ms

Contagem de operações: 1596

Sudoku Fácil 4:Tempo de usuário: 0.004 msTempo de Sistema: 0.000 msTempo Total: 0.004 msTempo Real: 0.493 ms

Contagem de operações: 975

Sudoku Fácil 5:Tempo de usuário: 0.000 msTempo de Sistema: 0.004 msTempo Total: 0.004 msTempo Real: 0.669 ms

15-23

Page 16: Documentacao - 2º Trabalho Prático - AEDS III

Contagem de operações: 729

Sudoku Médio 1:Tempo de usuário: 0.004 msTempo de Sistema: 0.000 msTempo Total: 0.004 msTempo Real: 0.694 ms

Contagem de operações: 4151

Sudoku Médio 2:Tempo de usuário: 0.004 msTempo de Sistema: 0.000 msTempo Total: 0.004 msTempo Real: 0.322 ms

Contagem de operações: 1433

Sudoku Médio 3:Tempo de usuário: 0.000 msTempo de Sistema: 0.004 msTempo Total: 0.004 msTempo Real: 0.450 ms

Contagem de operações: 2108

Sudoku Médio 4:Tempo de usuário: 0.000 msTempo de Sistema: 0.000 msTempo Total: 0.000 msTempo Real: 0.319 ms

Contagem de operações: 10373

Sudoku Médio 5:Tempo de usuário: 0.004 msTempo de Sistema: 0.000 msTempo Total: 0.004 msTempo Real: 0.335 msContagem de operações: 7780

Sudoku Difícil 1:

Tempo de usuário: 0.000 msTempo de Sistema: 0.004 msTempo Total: 0.004 msTempo Real: 0.903 ms

Contagem de operações: 8574

16-23

Page 17: Documentacao - 2º Trabalho Prático - AEDS III

Sudoku Difícil 2:Tempo de usuário: 0.004 msTempo de Sistema: 0.000 msTempo Total: 0.004 msTempo Real: 0.861 ms

Contagem de operações: 5928

Sudoku Difícil 3:Tempo de usuário: 0.004 msTempo de Sistema: 0.000 msTempo Total: 0.004 msTempo Real: 0.705 ms

Contagem de operações: 3838

Sudoku Difícil 4:Tempo de usuário: 0.012 msTempo de Sistema: 0.000 msTempo Total: 0.012 msTempo Real: 0.253 ms

Contagem de operações: 60549

Sudoku Difícil 5:Tempo de usuário: 0.004 msTempo de Sistema: 0.000 msTempo Total: 0.004 msTempo Real: 0.241 ms

Contagem de operações: 21080

Sudoku Muito Difícil 1:Tempo de usuário: 0.016 msTempo de Sistema: 0.000 msTempo Total: 0.016 msTempo Real: 0.379 ms

Contagem de operações: 91137

Sudoku Muito Difícil 2:Tempo de usuário: 0.004 msTempo de Sistema: 0.000 msTempo Total: 0.004 msTempo Real: 0.384 ms

Contagem de operações: 59827

17-23

Page 18: Documentacao - 2º Trabalho Prático - AEDS III

Sudoku Muito Difícil 3:Tempo de usuário: 0.012 msTempo de Sistema: 0.000 msTempo Total: 0.012 msTempo Real: 0.549 ms

Contagem de operações: 40146

Sudoku Muito Difícil 4:Tempo de usuário: 0.036 msTempo de Sistema: 0.000 msTempo Total: 0.036 msTempo Real: 0.338 ms

Contagem de operações: 308000

Sudoku Muito Difícil 5:Tempo de usuário: 0.000 msTempo de Sistema: 0.008 msTempo Total: 0.008 msTempo Real: 0.540 ms

Contagem de operações: 16568

Sudoku Extreme 1:Tempo de usuário: 0.044 msTempo de Sistema: 0.008 msTempo Total: 0.052 msTempo Real: 0.637 ms

Contagem de operações: 505035

Sudoku Extreme 2:Tempo de usuário: 0.024 msTempo de Sistema: 0.004 msTempo Total: 0.028 msTempo Real: 0.677 ms

Contagem de operações: 152659

Sudoku Extreme 3:Tempo de usuário: 0.116 msTempo de Sistema: 0.000 msTempo Total: 0.116 msTempo Real: 0.718 msContagem de operações: 1372211

18-23

Page 19: Documentacao - 2º Trabalho Prático - AEDS III

Sudoku Extreme 4:Tempo de usuário: 0.004 msTempo de Sistema: 0.000 msTempo Total: 0.004 msTempo Real: 0.449 ms

Contagem de operações: 40945

Sudoku Extreme 5:Tempo de usuário: 0.060 msTempo de Sistema: 0.000 msTempo Total: 0.060 msTempo Real: 0.447 ms

Contagem de operações: 729021

Sudoku Al Escargot:Tempo de usuário: 0.020 msTempo de Sistema: 0.000 msTempo Total: 0.020 msTempo Real: 0.381 msContagem de operações: 83707

Sudoku O mais difícil:Tempo de usuário: 0.332 msTempo de Sistema: 0.004 msTempo Total: 0.336 msTempo Real: 0.743 msContagem de operações: 4125542

19-23

Page 20: Documentacao - 2º Trabalho Prático - AEDS III

Com base no gráfico acima, conseguimos perceber a grande diferença no nº de operações entre os níveis mais dificéis(Extreme e Mais dificil) e os outros nível, visto que boa parte desses realizam menos que 50.000 operações e a maioria dos mais difícieis ultrapassam a casa das centenas de milhar.

Gráfico de tempos de execução

Através do gráfico apresentado acima, percebemos que o aumento do nível de dificuldade é proporcional ao tempo total de execução. Além disso, vemos que no pior dos casos, o maior tempo atingido foi menor que 0,5 segundos.

Nível de Dificuldade

Média de Tempo Total

Fácil 0.0032 msMédio 0.0032 msDifícil 0.0056 msMuito Difícil 0.0152 msExtremo 0.0520 msAl Escargot 0.0200 msSudoku mais difícil 0.3360 ms

Pela tabela acima, é possível perceber que o método de backtracking para resolver o Sudoku possui resposta em tempo real. Portanto, é um programa que poderia facilmente ser utilizado em um jogo de Sudoku comercializado se fosse necessário. Confirmando assim, a importância de utilizar heurísticas para solucionar problemas com complexidade similares a NP-completos.

É possível ver que os tempos reais de execução são bem maiores que o tempo total de execução do programa, isso se ocorre por que é necessário computar o tempo de resposta do usuário ao menu que escolhe qual o método utilizado para resolver o sudoku. Caso este não existisse, o tempo real seria poquissímo maior ou igual ao tempo total.

20-23

Page 21: Documentacao - 2º Trabalho Prático - AEDS III

2. Testes utilizando Força Bruta

Foi realizado um teste com duração de 12hrs para resolver o Sudoku Fácil 5 por força bruta. Infelizmente, não foi possível obter a solução, mais uma vez, provando quão inviável é a utilização desta técnica. Porém, para fins de análise, decidimos fazer reduzindo bastante o número de casas vazias, para assim, poder demonstrar, explicitamente, a lentidão de um método de resolução por força bruta.

Solução Sudoku Fácil 5:

I II III4 3 2 8 6 5 9 1 76 8 7 1 9 4 2 3 59 1 5 3 2 7 4 6 8

IV V VI2 5 9 4 7 6 3 8 11 4 6 2 8 3 5 7 98 7 3 9 5 1 6 2 4

VII VIII IX7 6 8 5 4 2 1 9 35 9 1 6 3 8 7 4 23 2 4 7 1 9 8 5 6

Para facilitar a explicação, utilizamos algarismos romanos para representar os grids.

O teste da força bruta consiste em 2 fases:1ª) Retira-se o 9 (nove) do grid I e realiza o teste. Em seguida, retira-se o 9 do grid II e realiza o teste. Repete-se esse processo até que o teste seja feito para o grid IX, ou seja, até que o teste seja feito o Sudoku com todos os 9 (nove) faltando para completar o Sudoku.

2ª) Retira-se o 9 (nove) do grid IX e realiza o teste. Em seguida, retira-se o 9 do grid VIII e realiza o teste. Repete-se esse processo até que o teste seja feito para o grid I, ou seja, até que o teste seja feito o Sudoku com todos os 9 (nove) faltando para completar o Sudoku.

1ª Fase:

9 (Nove)faltando

Tempo deUsuário

Tempo deSistema

Tempo deTotal

1 0.004ms 0.000ms 0.004ms2 0.012ms 0.004ms 0.016ms3 0.028ms 0.008ms 0.036ms4 0.156ms 0.004ms 0.160ms5 0.804ms 0.000ms 0.804ms6 5.996ms 0.004ms 0.004ms7 25.254ms 0.016ms 25.270ms8 61.180ms 0.076ms 61.256ms9 473.938ms 0.612ms 474.550ms

21-23

Page 22: Documentacao - 2º Trabalho Prático - AEDS III

2ª Fase:

9 (Nove)faltando

Tempo deUsuário

Tempo deSistema

Tempo deTotal

1 0.004ms 0.000ms 0.004ms2 0.008ms 0.000ms 0.008ms3 0.028ms 0.000ms 0.028ms4 0.108ms 0.004ms 0.112ms5 0.676ms 0.008ms 0.684ms6 4.748ms 0.012ms 4.388ms7 11.561ms 0.020ms 11.581ms8 76.709ms 0.096ms 76.805ms9 471.645ms 0.528ms 472.174ms

Analisando as duas fases, conseguimos perceber que os tempos da 1ª Fase são relativamente maiores que os 2ª, com exceção do último (faltando 9) e penúltimo (faltando 8).

No último, sabemos que é o mesmo teste, portanto, o tempo é quase o mesmo. No penúltimo, os resultados são bem distinto, e, ainda, uma coisa incomum ocorre:

o tempo da 1ª Fase é menor que o da 2ª. Mas porque isto ocorre? Simples. Pela forma que o algoritmo foi implementado.

Como o algoritmo faz a busca de trás para frente e de baixo para cima, o tempo para resolver um Sudoku com elementos sem preencher nos últimos grids (2ª Fase) é mais rápido que o tempo de resolver um Sudoku com elementos sem preencher nos primeiros grids (1ª Fase). Porém, no caso do penúltimo teste isso não ocorre, pois quando apenas o grid IX está preenchido com o 9, o algoritmo faz recursões indo desde o grid IX até o grid I. Mas, no caso de apenas o grid I estar preenchido com 9, o algoritmo faz recursões indo desde o grid XI até o grid II, já que no grid 9 todos os números estão preenchidos.

Após isso, conseguimos definir que o tempo para resolver esse algoritmo depende não apenas no número de casas que faltam preencher, mas também em qual grid essas casas sem preencher estão.

22-23

Page 23: Documentacao - 2º Trabalho Prático - AEDS III

CONCLUSÃO

Neste trabalho aprofundamos ainda mais nossos conhecimentos sobre programação e iniciamos uma nova fase, a qual lida com problemas reais NP-completos, como o famoso jogo japonês Sudoku. Por meio deste, conseguimos perceber que para solucionar um problema de tamanha complexidade é preciso conhecer técnicas que realizem o menor número de comparações possíveis e cheguem a uma solução de forma ágil. Por isso utilizamos um dos paradigmas de projetos de algoritmos: Backtracking.

Conforme proposto, implementamos nesse trabalho um algoritmo de heurística própria e um algoritmo de força bruta.

No backtracking, criamos um algoritmo recursivo que analisa os valores possíveis de uma casa e, a partir deste, procura uma solução para o Sudoku, porém, se no meio desta procura encontrar um deadlock, será preciso realizar o backtracking (voltar atrás) para fazer um novo teste, satisfazendo assim, o método de um algoritmo de backtracking, o qual consiste no princípio da tentativa e erro. Esse algoritmo é bastante ágil para solucionar um Sudoku, visto que, com base nos testes realizados, não chega a atingir 1 segundo no tempo para encontrar uma solução.

O algoritmo de força bruta realiza todos os testes possíveis para as posições vazias, já que consiste em fazer uma busca “cega” pela solução. Infelizmente, não houve tempo para desenvolver uma força bruta com um “filtro” de possibilidades óbvias, por este motivo, esse algoritmo não é considerado viável para solucionar um Sudoku, mas é um método capaz, logicamente, de encontrar a solução, mesmo que para isso demore alguns anos.

Fazemos uma ressalva, na qual dizemos que nosso algoritmo é limitado a Sudokus padrão (9x9), portanto, não tem utilidade na resolução de outras versões.

COMO EXECUTAR O PROGRAMA

Com a finalidade de facilitar a execução, serão disponibilizados na pasta /TP2/Sudokus vários exemplos de Sudokus.

Para juntar os módulos do projeto e compilá-los foi desenvolvido um Makefile. Portanto, para compilar o trabalho é preciso entrar na pasta /TP2/codigo/ e digitar o

seguinte comando:

make

Após a compilação, para executar o programa no terminal, é preciso digitar:

./TP2 –a “arquivo_do_sudoku_original” – b “arquivo_para_grava_a_solução”

Para fazer uma limpeza no projeto compilado digite:

make clean

Bibliografiahttp://www.fiendishSudoku.com/pt/Sudoku.htmlhttp://pt.wikipedia.org/wiki/Sudokuhttp://www2.uol.com.br/sciam/reportagens/a_ciencia_do_sudoku.html

23-23