Linha | Codigo (aula_05_problema_n_rainhas.py) |
001 | # Aula 05: problemas das N rainhas |
002 | # Dado N (natural) verificar se e' ou nao possivel posicionar N |
003 | # rainhas no tabuleiro de xadrez sem que uma delas possa atacar outra. |
004 | # Na solucao 1 apresento uma ideia inicial (ineficiente em termos de espaco, usando O(n^2)), depois |
005 | # a solucao 2 que economiza espaco, usando apenas O(n) posicoes de memoria. |
006 | # Apresento apenas a implementacao mais eficiente em termos de espaco em memoria (solucao 2). |
007 | |
008 | # Solucao 1. O(n^2) posicoes de memoria, O(n) para verificar posicao sob ataque |
009 | # + Estrutura auxiliar: uma matriz (para posicionar as rainhas e indicar posicoes sob ataque); |
010 | # + Ideia: Fazer umma implementacao recursiva para posicionar a linha i (sempre na linha i), entao escolher sua coluna. |
011 | |
012 | # Solucao 2. Mais eficiente em termos de memoria (O(n)) e velocidade (verifica posicao sob ataque em O(1)) |
013 | # + Estrutura auxiliar: 4 vetores |
014 | # . - vlinha[] : vlinha[i]=k se rainha i foi posicionada na coluna i (vlinha[i]=-1 indica coluna livre) |
015 | # . - vdiag_princ[ ] : vdiag_princ[i]=1 se existe uma rainha (qualquer) atacando a diagonal principal i |
016 | # . - vdiag_secund[] : vdiag_secund[i]=1 se existe uma rainha (qualquer) atacando a diagonal secundaria i |
017 | |
018 | # Matematica necessaria para vdiag_princ[]: | Considerando uma matriz n=5 (dimensao 5) |
019 | # - Ideia da Algebra Linear e do calculo de curva de nivel: | 0_1_2_3_4 |
020 | # d e x vetores do R^n, sao ortogonais se, e somente se, d'x=0 | 0|_|\|_|_|\| -> curva de nivel i-j=-(n-1)=-4 (menor) |
021 | # Em R^2 os vetores x e y estao na curva de nivel de valor k, (1;-1)'(x;y)=k | 1|_|_|\|_|_| |
022 | # Considerando as posicoes de uma matriz quadrada de ordem n, desde (0,0) ate' (n-1,n-1), | 2|_|_|_|\|_| |
023 | # um elemento esta' na linha i, coluna j tal que i-j=k, k=-(n-1),-(n-2),...,-1,0,1,2,...,n-1 | 3|_|_|_|_|\| -> curva de nivel i-j=-1 |
024 | # Assim, as curvas de nivel vao de i-j=-(n-1) ate' i-j=n-1 | 4|\|_|_|_|_| |
025 | # Entao podemos usar uma bijecao para associar i com sua posicao: dp(i) = l-c + (n-1) | +-> curva de nivel i-j=(n-1)=4 (maior curva) |
026 | |
027 | # Matematica necessaria para vdiag_secund[]: | Considerando uma matriz n=5 (dimensao 5) |
028 | # - Ideia da Algebra Linear e do calculo de curva de nivel: | 0_1_2_3_4 |
029 | # d e x vetores do R^n, sao ortogonais se, e somente se, d'x=0 | 0|/|_|_|_|_| -> menor curva de nivel: i+j=0 |
030 | # Em R^2 os vetores x e y estao na curva de nivel de valor k, (1;1)'(x;y)=k | 1|_|_|_|_|_| |
031 | # Considerando as posicoes de uma matriz quadrada de ordem n, desde (0,0) ate' (n-1,n-1), | 2|_|_|_|_|_| |
032 | # um elemento esta' na linha i, coluna j tal que i+j=k, k=0, 1, 3, ..., 2n-2 | 3|_|_|_|_|/| -> curva de nivel i+j=2n-3=7 |
033 | # Assim, as curvas de nivel vao de i-j=-(n-1) ate' i-j=n-1 | 4|_|_|_|/|/| -> maior curva de nivel i+j=2n-2=8 |
034 | # Entao podemos usar uma bijecao para associar i com sua posicao: ds(i) = i = l+c (l=linha) | |
035 | |
036 | from __future__ import print_function; # para funciona print com 'end=""' no Python 2 |
037 | |
038 | import sys; # Para passar parametro via linha de comando (valor do n): |
039 | # $ python problema_n_rainhas.py 5 |
040 | |
041 | |
042 | def imprima_solucao (N, vet_linhas) : |
043 | #D print("imprima_solucao(%d,%s)" % (N,vet_linhas)); |
044 | for i in range(N) : |
045 | pos = vet_linhas[i]; # rainha i na coluna 'vet_linhas[i]' |
046 | for j in range(N) : |
047 | if (j==pos) : print("%2d " % (i+1), end=""); # internamente a primeira rainha e' 0, mas para diferenciar imprimimos como 1 ate' N |
048 | else : print("%2d " % 0, end=""); |
049 | print(); # mude de linha |
050 | |
051 | |
052 | # Diagonal principal |
053 | # i : -(n-1), -(n-2), ..., -1, 0, 1, 2, ... (n-1) |
054 | # dp(i): 0, 1, (n-2), (n-1), n, n+1 ... 2n-1 => dp(i) = i+(n-1) |
055 | # Exemplo de diagonal principal: Para n=4, menor curva e' i=0 e j=n-1 => (i-j)=-(n-1); maior e' i=n-1 e j=0 => (i-j)=n-1 |
056 | # Entao ajustando a funcao bijetora dp(i) para associar (0,n-1) com -(n-1) e (n-1,0) com n-1 |
057 | # dp(i) = dp(l+c) = (l+c) + (n-1) |
058 | # Tabela para n=4 |
059 | # i : -3, -2, -1, 0, 1, 2, 3 |
060 | # dp(i): 0, 1, 2, 3, 4, 5, 6 => dp(i) = i+(n-1) = i+3 (i=linha-coluna) |
061 | |
062 | # Diagonal secundaria |
063 | # ds(i): 0, 1, (n-2), (n-1), n, n+1 ... 2n-2 => ds(i) = i (i=linha+coluna) |
064 | |
065 | |
066 | # Verifica se a posicao [lin][col] esta' livre para a rinha lin |
067 | # Note que os vetores/listas 'vlinha,vdiagp,vdiags' sao passsado por referencia (logo modificacao reflete nos parametros efetivos), |
068 | # ou usando o termo da apostila digital, esta' e' uma funcao "modificadora" em relacao 'a estas variveis! |
069 | def posicaoSegura (N, r, lin, col, vlinha, vdiagp, vdiags) : |
070 | if (vlinha[col]!=-1) : return False; # coluna 'col' ja' ocupada... |
071 | if (vdiagp[lin-col+N-1]==1) : return False; # diag. princ. ja' atacada... |
072 | if (vdiags[lin+col]==1) : return False; # diag. secun. ja' atacada... |
073 | vlinha[col] = r; # agora linha ocupada (rainha 'lin' na coluna 'col') (note: como r>=0, usamos -1 para indicar posicao livre) |
074 | vdiagp[lin-col+N-1] = 1; # agora diagonal principal ocupada: k=l-c, k = -(n-1), -(n-2), ...0, 1, 2, ... (n-1) <-> 0, 1, ..., n, n+1, n+2, ... 2n-1 |
075 | vdiags[lin+col] = 1; # agora diagonal secundaria ocupada: k=l+c, k = 0, 1, n-1, n, n+1, 2n-2 |
076 | return True; # pode posicionar rainha lin na coluna col |
077 | |
078 | |
079 | # Tenta posicionar a rainha 'r' em alguma coluna |
080 | def tenta_recursivamente (N, r, vlinha, vdiagp, vdiags) : |
081 | # Testa final de posicionamento: lin==N => posicionadas N rainhas! Sucesso! |
082 | if (r == N) : return True; # Sucesso! Todas as N rainhas posicionadas! |
083 | # Considere cada coluna 'col' para a rainha 'lin': tente posicionar rainha primeiro na colunas 0, depois 1, ate' N-1 |
084 | for col in range(N) : |
085 | lin = r; # rainha i sempre na linha i |
086 | if (posicaoSegura(N, r, lin, col, vlinha, vdiagp, vdiags)) : |
087 | |
088 | # Agora tente posicioar a rainha r+1 |
089 | if (tenta_recursivamente(N, r+1, vlinha, vdiagp, vdiags)) : |
090 | return True; # Teve sucesso com esta posicao (posicionou todas) |
091 | |
092 | # Se houve erro, desfaca a marcacao para tentar em nova linha |
093 | vlinha[col] = -1; # desocupa coluna (note: como r>=0, usamos -1 para indicar posicao livre) |
094 | vdiagp[lin-col+N-1] = 0; # desocupa diagonal principal |
095 | vdiags[lin+col] = 0; # desocupa diagonal secundaria |
096 | # Se chegou neste ponto, entao nao foi possivel posiciona rainha col, desista... |
097 | return False; |
098 | |
099 | |
100 | # Tentativa e erro para o problema das N rainhas |
101 | def n_rainhas (N) : |
102 | if (tenta_recursivamente(N, 0, vlinha, vdiagp, vdiags) == False) : # tenta rainha 0 na coluna 0 |
103 | print ("NAO existe posicionamente viavel para %d rainhas em tabuleiro %d x %d!" % (N, N, N)); |
104 | return False; |
105 | imprima_solucao(N, vlinha); |
106 | return True; |
107 | |
108 | |
109 | # Auxiliar para depuracao |
110 | # Testando o truque da bijecao para a diagonal principal |
111 | def testa_diagonal_p (n) : |
112 | print("2*N-2 = %d " % (2*n-2)); |
113 | # dp(i) : 0, 1, 2, 3, 4, 5, 6 => dp(i) = i+3 |
114 | for i in range(n) : |
115 | for j in range(n) : print("%d " % (i-j + n-1), end=""); |
116 | print(); |
117 | |
118 | |
119 | # Codigo principal |
120 | if (len(sys.argv)<2) : print("Precisa informar tamanho do tabuleiro, e.g., para n=5, deve digitar\npython aula_05_problema_n_rainhas.py 5"); sys.exit(); |
121 | #D print("sys.argv=%d" % len(sys.argv)); |
122 | |
123 | # Tenta com N rainhas |
124 | N = int(sys.argv[1]); # pega primeiro argumento (deve ser inteiro) |
125 | |
126 | # testa_diagonal_p(N); |
127 | |
128 | print("Tenta posicionar %d rainhas em um tabuleiro %d x %d" % (N, N, N)); |
129 | |
130 | # Estruturas para algoritmo mais eficiente: |
131 | vlinha = N*[-1]; # linha[i] = k <=> rainha i esta' na coluna k (note: como r>=0, usamos -1 para indicar posicao livre) |
132 | vdiagp = 2*N*[0]; # diagp[i] = 1 <=> existe rainha na diagonal princial i (i=-(n-1), -(n-2), ..., 0, 1, 2, ... (n-1)) |
133 | vdiags = 2*N*[0]; # diags[i] = 1 <=> existe rainha na diagonal secundaria i (i=-0, 1, 2, ... 2*(n-1)) |
134 | |
135 | n_rainhas(N); |
136 | |
137 |