Lista de exercícios

Exercícios resolvidos: divisão e conquista além dos exemplos clássicos

Aplicações de redução por descarte e resolução de todas as partes em busca de um ninho, localização bidimensional e compressão de imagens.

Esta lista transfere as ideias da Aula 02 para problemas diferentes de buscar em um vetor e ordenar valores. Tente resolver cada exercício antes de consultar a solução.

O contraste central permanece o mesmo: alguns problemas permitem descartar regiões inteiras, enquanto outros exigem resolver todas as regiões e combinar os resultados.

Os dois primeiros exercícios aplicam o mesmo descarte espacial: primeiro em uma dimensão e depois em duas.

Exercício 1: localização de um ninho de abelhas

Uma técnica de localização pode usar a direção de voo de abelhas soltas em pontos diferentes. Nesta adaptação computacional, considere uma trilha reta marcada com posições inteiras.

Uma primeira observação indicou que a abelha voou para a direita. Depois de caminhar até o outro extremo da trilha, uma segunda observação indicou voo para a esquerda. Assim, o ninho ficou delimitado no intervalo [0, 1024).

O simulador fornece a função:

BeeDirection release_bee(int position);

Ela retorna:

  • BEE_LEFT quando o ninho está à esquerda;
  • BEE_RIGHT quando o ninho está à direita;
  • NEST_FOUND quando a soltura ocorreu na posição do ninho.

Baixe o código-base com o simulador e complete somente:

static int locate_nest(int begin, int end);

Requisitos:

  1. preserve o intervalo semiaberto [begin, end);
  2. solte a abelha no ponto central;
  3. descarte a parte incompatível com a direção observada;
  4. retorne a posição quando o ninho for encontrado;
  5. passe por todos os testes usando no máximo 11 solturas por ninho;
  6. compare esse limite com a tentativa de soltar uma abelha em cada posição, da esquerda para a direita.
Solução

Explicação

Correspondência com a busca binária

A trilha não é um vetor ordenado, mas a resposta da abelha impõe uma ordem espacial:

  • BEE_LEFT elimina o ponto central e todas as posições à direita;
  • BEE_RIGHT elimina o ponto central e todas as posições à esquerda;
  • NEST_FOUND encerra a busca.

O invariante é: se o ninho ainda não foi encontrado, sua posição permanece em [begin, end).

Atualização do intervalo

Se a abelha voar para a esquerda, a nova região é [begin, middle), portanto end = middle.

Se ela voar para a direita, a nova região é [middle + 1, end), portanto begin = middle + 1.

Rastreamento para o ninho na posição 731

[0, 1024), middle = 512  → direita
[513, 1024), middle = 768 → esquerda
[513, 768), middle = 640  → direita
[641, 768), middle = 704  → direita
[705, 768), middle = 736  → esquerda
[705, 736), middle = 720  → direita
[721, 736), middle = 728  → direita
[729, 736), middle = 732  → esquerda
[729, 732), middle = 730  → direita
[731, 732), middle = 731  → encontrado

Foram necessárias 10 solturas nesse caso. O limite de 11 também cobre as posições menos favorecidas pela escolha do ponto central.

Uma tentativa sequencial poderia precisar de 1.024 solturas para alcançar a última posição. A redução pela metade limita a busca a 11 solturas neste simulador.

Resposta esperada

static int locate_nest(int begin, int end) {
    while (begin < end) {
        const int middle = (begin + end) / 2;
        const BeeDirection direction = release_bee(middle);

        if (direction == NEST_FOUND) {
            return middle;
        }

        if (direction == BEE_LEFT) {
            end = middle;
        } else {
            begin = middle + 1;
        }
    }

    return -1;
}

A implementação completa está em ninho_abelhas_solucao.c. Ela localiza todos os ninhos testados, mantém o intervalo semiaberto e respeita o limite de 11 solturas.

No pior caso do intervalo fornecido, a busca sequencial usa até 1.024 solturas, enquanto a redução sucessiva usa no máximo 11.

Exercício 2: busca binária em um edifício

O exercício anterior reduziu um único intervalo. Agora, resolva o desafio Shadows of the Knight, Episode 1 aplicando a mesma ideia simultaneamente aos dois eixos do edifício.

O edifício possui W colunas e H linhas de janelas. A janela superior esquerda possui coordenadas (0, 0). Depois de cada salto, o detector informa uma das direções U, UR, R, DR, D, DL, L ou UL em relação à posição atual.

Antes de programar, registre:

  1. os quatro limites da região em que a sala ainda pode estar;
    • No início, essa região corresponde ao edifício inteiro. As pistas reduzem a região possível, como ocorre na busca binária.
  2. como cada letra da direção altera esses limites;
    • Na busca binária, quando a solução está à direita, toda a região à esquerda pode ser descartada.
  3. como escolher a próxima janela;
    • A busca binária examina o ponto central do intervalo em que a solução ainda pode estar.
  4. por que uma direção diagonal reduz os dois eixos no mesmo turno;
    • Quando a direção não é diagonal, a linha ou a coluna atual já está correta.
  5. por que não é necessário examinar todas as W × H janelas.

Use limites inclusivos:

x_min ≤ x_alvo ≤ x_max
y_min ≤ y_alvo ≤ y_max

Considere também este estado intermediário, obtido depois que algumas janelas já foram descartadas:

x possível: [6, 13]
y possível: [2, 9]
posição atual: (9, 6)
direção recebida: UL

Determine os novos limites e a próxima janela escolhida pelo ponto central, usando divisão inteira.

Tente resolver esse estado à mão antes de programar. O objetivo é compreender como construir a solução.

A solução abaixo orienta a modelagem, mas não fornece código pronto para submissão na plataforma.

Solução

Explicação

Região possível

No início, qualquer janela pode conter a sala:

x_min = 0
x_max = W - 1
y_min = 0
y_max = H - 1

Esses quatro valores descrevem um retângulo. A cada resposta do detector, uma parte desse retângulo deixa de ser possível.

Atualização horizontal

  • Se a direção contém L, a sala está à esquerda: x_max recebe x_atual - 1.
  • Se a direção contém R, a sala está à direita: x_min recebe x_atual + 1.
  • Se não existe L nem R, a coluna já está determinada: os dois limites recebem x_atual.

Atualização vertical

  • Se a direção contém U, a sala está acima: y_max recebe y_atual - 1.
  • Se a direção contém D, a sala está abaixo: y_min recebe y_atual + 1.
  • Se não existe U nem D, a linha já está determinada: os dois limites recebem y_atual.

Uma resposta diagonal contém uma informação horizontal e outra vertical. Por exemplo, UL permite podar simultaneamente as janelas à direita e as janelas abaixo da posição atual.

Escolha da próxima janela

Depois de atualizar os limites, escolha o centro da região restante:

próximo_x = (x_min + x_max) / 2
próximo_y = (y_min + y_max) / 2

No estado fornecido, UL produz:

x possível: [6, 8]
y possível: [2, 5]

Com divisão inteira:

próximo_x = (6 + 8) / 2 = 7
próximo_y = (2 + 5) / 2 = 3

A próxima janela é (7, 3).

O programa precisa manter os limites entre os turnos. Recomeçar com o edifício inteiro apagaria o conhecimento obtido nas respostas anteriores.

Resposta esperada

  1. Representar as posições possíveis pelo retângulo inclusivo [x_min, x_max] × [y_min, y_max].
  2. Usar L e R para reduzir o eixo horizontal, e U e D para reduzir o eixo vertical.
  3. Fixar um eixo na coordenada atual quando a resposta não possui letra referente a esse eixo.
  4. Escolher o centro do retângulo restante como próxima janela.
  5. Para o estado fornecido, obter x ∈ [6, 8], y ∈ [2, 5] e próxima janela (7, 3).
  6. Justificar que regiões incompatíveis com a direção são descartadas sem inspeção individual. A quantidade de turnos cresce de forma logarítmica em relação às dimensões do edifício.

Exercício 3: compressão de uma imagem por quadtree

Uma imagem binária usa 0 para branco e 1 para preto. Uma região uniforme pode ser representada por um único símbolo. Uma região não uniforme deve ser dividida em quatro quadrantes iguais.

Use sempre esta ordem:

  1. superior esquerdo;
  2. superior direito;
  3. inferior esquerdo;
  4. inferior direito.

Regras de codificação:

  • região uniforme: escrever 0 ou 1;
  • região não uniforme: escrever (, codificar os quatro quadrantes e escrever ).

Considere a imagem:

0 0 1 1
0 0 1 1
0 1 1 1
0 0 1 1

Faça o seguinte:

  1. classifique os quatro quadrantes da primeira divisão;
  2. escreva a codificação completa da imagem;
  3. explique por que nenhum quadrante pode ser descartado;
  4. implemente region_is_uniform e encode_region em C;
  5. identifique o caso-base, os subproblemas e a combinação;
  6. determine o pior caso para uma imagem de lado n, considerando que a verificação de uniformidade percorre a região recebida.
  7. explique por que uma região uniforme não deve ser subdividida.

Considere que n é potência de dois e que a imagem é quadrada.

Solução

Explicação

Primeira divisão

Os quadrantes de tamanho 2 × 2 são:

superior esquerdo:  0 0    uniforme → 0
                    0 0

superior direito:   1 1    uniforme → 1
                    1 1

inferior esquerdo:  0 1    não uniforme
                    0 0

inferior direito:   1 1    uniforme → 1
                    1 1

O quadrante inferior esquerdo precisa ser dividido novamente. Seus quatro pixels, na ordem definida, são 0, 1, 0 e 0. Sua codificação é (0100).

Ao combinar os quatro resultados da raiz, obtemos:

(01(0100)1)

Por que todas as partes continuam

Uma região não uniforme não informa qual quadrante é dispensável. Cada quadrante contém pixels que precisam aparecer na representação final. Portanto, as quatro chamadas continuam e seus resultados são concatenados entre parênteses.

Estrutura da divisão e conquista

  • Dividir: separar a região em quatro quadrantes.
  • Resolver: codificar recursivamente os quatro quadrantes.
  • Combinar: colocar as quatro codificações, na ordem definida, entre parênteses.
  • Encerrar: escrever uma única cor quando a região for uniforme. Uma região de um pixel sempre satisfaz esse caso.

Subdividir uma região já uniforme criaria chamadas que repetiriam a mesma cor. Isso aumentaria o trabalho e produziria uma representação maior sem preservar informação adicional.

Custo no pior caso

Para uma região de lado n, a verificação de uniformidade examina n2n^2 pixels. Se nenhuma região maior for uniforme, surgem quatro subproblemas de lado n/2n/2:

T(n)=4T(n2)+Θ(n2)T(n)=4T\left(\frac{n}{2}\right)+\Theta(n^2)

Como cada nível examina, ao todo, os n2n^2 pixels e existem log2n\log_2 n níveis:

T(n)=Θ(n2logn)T(n)=\Theta(n^2\log n)

Resposta esperada

  1. Os quadrantes superior esquerdo, superior direito e inferior direito são uniformes e produzem 0, 1 e 1. O quadrante inferior esquerdo não é uniforme.
  2. A codificação completa é (01(0100)1).
  3. Nenhum quadrante pode ser descartado porque todos contêm pixels que participam da representação final.
  4. A solução deve verificar a uniformidade, encerrar em regiões uniformes e fazer quatro chamadas recursivas para regiões não uniformes.
  5. A combinação deve preservar a ordem superior esquerdo, superior direito, inferior esquerdo e inferior direito.
  6. Com a verificação fornecida, o pior caso é Θ(n2logn)\Theta(n^2\log n) para uma imagem de lado n.
  7. Uma região uniforme deve encerrar a recursão porque um único símbolo já representa todos os seus pixels.

A implementação completa está em compressao_quadtree_solucao.c. Ela imprime:

Codificação: (01(0100)1)