domingo, 10 de novembro de 2013

Sequências de Langford: padrões e analogias

Considere uma sequência de tamanho 2*n formada por duas cópias dos inteiros de 1 a n. Dito de outro modo, considere uma permutação dos números {1,1,2,2,3,3,..., n,n}. A sequência diz-se de Langford, de ordem n, designada por L(n), se as ocorrências de cada par do número k tiver a separá-los exactamente k inteiros. Por exemplo, (2,3,1,2,1,3) é uma sequência de Langford de ordem 3, (4,1,3,1,2,4,3,2) é uma sequência de Langford de ordem 4, (1, 2, 1, 6, 2, 8, 11, 9, 10, 3, 6, 4, 7, 3, 8, 5, 4, 9, 11, 10, 7, 5) é uma sequência de ordem 11. São várias as questões que se podem colocar sobre estas sequências :

existência: para todo o n existe pelo menos uma sequência de Langford?
construção: se existe L(n), como obtê-la?
enumeração: quantas sequências de ordem n existem?
geração: as sequências de ordem n podem ser geradas sistematicamente?
optimização: quais as soluções que maximizam (ou minimizam) f(L(n)) para uma dada função objectivo f?

O problema de Langford consiste em encontrar uma sequência de Langford para um dado n. Trata-se na realidade de um caso especial do problema combinatório mais geral conhecido por problema da cobertura exacta. Mas quem é que se pode interessar por isto? Bem, para além do prazer de brincar com a matemática, existem exemplos de aplicação concreta: resolver um problema do Sodoku é resolver um problema de cobertura exacta. O problema da cobertura exacta é um problema combinatório de elevada complexidade (*), sendo que para valores elevados de n não existe um computador suficientemente rápido, nem hoje nem amanhã, que o resolva em tempo útil. Mas nós não vamos discutir estas questões. Vamos começar por criar um programa simples que nos diz, para todo o n, se uma dada sequência é de Langford ou não. Vamos admitir que para um dado n a sequência tem comprimento de 2*n e cada inteiro entre 1 e n ocorre exactamente duas vezes. Vamos a isso.

Como resolver o problema? Vamos pensar num caso mais simples: como saber se um dado k tem k inteiros entre as suas duas ocorrências? Bem, basta encontrar as posições entre as duas ocorrências e contar as posições entre ambas. E para todos? Também não é difícil: repetir para todos inteiros no intervalo [1,n]. Asim vamos ter um ciclo repetido n vezes que faz a verificação da condição. Claro que se existir um número que não verifique a condição podemos logo abandonar o programa pois já sabemos a resposta.
def testa_langford(seq):
    """Verifica se seq é uma sequência de Langford."""
    ordem = len(seq)//2
    for k in range(1,ordem+1):
        indice_1 = seq.index(k)
        indice_2 = seq.index(k,indice_1+1,len(seq))
        if (indice_2 - indice_1 - 1) != k:
            return False
    return True
Nesta solução admitimos como foi dito que a sequência tem comprimento igual a 2*n e cada inteiro entre 1 e n ocorre exactamente duas vezes. Mas para ter a certeza disso não há como testar um sequência qualquer. Como fazer? Só temos que testar a existência de cada número k duas vezes, para o que recorremos ao método count para sequências:
def candidata(seq):
    “””Pode ser sequência de Langford?”””
    n = len(seq) // 2
    for k in range(1,n+1):
        if seq.count(k) != 2:
            return False
    return True
Olhando para este programa e para o anterior o que notamos? Seguem exactamente o mesmo padrão: um ciclo que gera os números de 1 a n de modo ordenado e um teste para verificar a condição que nos pode levar à rejeição da hipótese. Correndo o programa e ficamos todos satisfeitos... ou talvez não. Teste com o caso (1,2,1,2,3). Vai dizer-lhe que é uma candidata quando é óbvio que nunca pode ser: tem um comprimento ímpar! Nada que não se possa resolver de modo trivial:
def candidata(seq):
    “””Pode ser sequência de Langford?”””
    if len(seq) % 2 != 0:
        return False

    n = len(seq) // 2
    for k in range(1,n+1):
        if seq.count(k) != 2:
            return False
    return True
Para concluir vamos pensar num programa que nos indica qual a distância a que uma dada sequência se encontra de uma verdadeira sequência de Langford. O modo como pretendemos fazer isso consistirá em determinar quantos números, no intervalo de 1 a n, não satisfazem a sua condição. Informaticamente já sabemos que precisamos de um ciclo para percorrer todos os números e de um contador para contar os casos em que a condição não é satisfeita. Trata-se pois de mais um exemplo do padrão ciclo - acumulador, em que este último tem funções de contador. Pensando um pouco chegamos à conclusão que este problema é análogo ao primeiro. A diferença está em que não abandonamos o programa mal se saiba que não pode ser uma sequência de Langford, antes continuamos actualizado um contador.
def qualidade(seq):
    """Conta o número de números que satisfaz a condição e Langford."""
    ordem = len(seq)//2
    conta = 0
    for i in range(1,ordem+1):
        indice_1 = seq.index(i)
        indice_2 = seq.index(i,indice_1+1,len(seq))
        if (indice_2 - indice_1 - 1) != i:
            conta += 1
    return conta
Se ao correr o programa o resultado for 0, então sabemos que é uma sequência de Langford.

(*) Tecnicamente diz-se que é um problema NP-Completo.

Sem comentários:

Enviar um comentário