Atividade Prática 11: Encontrando a raiz de uma função com busca binária. Algoritmos e Programação de Computadores Raoni F. S. Teixeira Introdução Nesta aula, vamos aprender como utilizar o algoritmo de busca binária estudado em sala para encontrar a raiz de uma equação. As atividades desta aula vão estimular sua intuição sobre o processo de solução numérica que pode ser aplicado em uma variedade de problemas de engenharia. Objetivos Ao fim do exercı́cio, você será capaz de: • Identificar a estrutura repetitiva do problema de busca de raı́zes; • Construir códigos que encontram soluções numéricas e • Testar programas numéricos. Arquivos incluı́dos nesta atividade • raizes.c: disponı́vel em: ieng.ufmt.br/algoritmos/code/raizes.c Este arquivo será utilizado na primeira parte da atividade. 1 Busca binária Busca binária é um algoritmo eficiente para o problema de busca em um vetor ordenado. Mais especificamente, dado um inteiro x e um vetor crescente v[0..n-1] de inteiros, o algoritmo de busca binária encontra um ı́ndice m tal que v[m] == x. Note que se x não está v[0..n-1] então a solução para o problema é um ı́ndice inválido (−1 por exemplo). 1 O algoritmo de busca binária é baseado no mesmo princı́pio que te ajuda a buscar um nome em uma lista telefônica ou dicionário 1 . O algoritmo realiza sucessivas divisões do espaço de busca comparando o elemento buscado (x) com o elemento no meio do vetor (v[m]). Se v[m] == x, então a busca termina. Caso contrário, como v[0..n-1] está em ordem crescente, podemos sempre ignorar metade dos elementos do vetor. Se x > v[m], então a busca continua na metade posterior do vetor (v[m+1..n-1]). E finalmente, se x < v[m] , então a busca continua na metade anterior do vetor(v[0...m-1]). Em C, tal como vimos na aula isto pode ser feito da seguinte forma: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ... ini = 0; fim = n - 1; r = -1; while ( ini <= fim ) { meio = ( ini + fim )/2; if ( A [ meio ] == x ) { r = meio ; break ; } else if ( x < A [ meio ]) fim = meio - 1; else ini = meio + 1; } /* neste ponto r e a r e s p o s t a da busca */ ... Copie e cole este algoritmo no Code::Blocks e teste o código com vários exemplos. 2 Roots Bloody Roots Encontrar as raizes de uma função f é um problema recorrente em engenharia e com certeza se você ainda não topou topará com ele alguma vezes na sua carreira! Talvez você fique um pouco surpreso mas o algoritmo de busca binária apresentado na seção anterior pode ser utilizado para resolver este problema também :) Como você deve saber, a raiz de um função f , f : R 7→ R2 , é um ponto x, x ∈ R, para o qual f (x) é igual a 0. Assim, por exemplo, o ponto x = 5 é a raiz da função f (x) = (x − 5)3 , porque f (x) vale 0 quando x é igual a 5 (n.b. que (5 − 5)3 = 0). Como outro exemplo, considere a função f (x) = x2 − 4, ilustrada na Figura 1, que tem duas raizes: 2 e −2. 1 É... eu sei... você não usa lista telefônica :) 2 4 2 −4 −2 2 4 −2 −4 Figura 1: Gráfico da função f (x) = x2 − 4 com duas raizes. Encontrar as raizes de uma função f é portanto encontrar (buscar) o ponto x em que f (x) é igual a zero. A pergunta que talvez você esteja se fazendo agora é: Como podemos usar a busca binária para encontrar a raiz da função? É... Como? Com um pouco de atenção, podemos notar que o o segredo do algoritmo de busca binária é que a cada iteração o espaço de busca é reduzido pela metade. Como podemos fazer o mesmo para o problema das raizes? Para responder esta pergunta vamos considerar a função f (x) = x2 − 4. Agora que já escolhemos uma função, podemos pensar em como definir o espaço de busca. Como a função escolhida é unidimensional o espaço de busca só pode ser um intervalo [ini, fim] 2 . Mas como escolher os valores deste intervalo? Esta é fácil: precisamos escolher o intervalo onde há ao menos uma raiz! Olhando a Figura 1 podemos obervar, por exemplo, que há uma raiz no intervalo [0, 3]. Como regra você pode assumir que, se a função f é contı́nua e f (ini) é De uma maneira geral, sempre haverá uma raiz negativa e f (fim) é pono intervalo se a função f for contı́nua e os sitiva, então existe uma sinais de f (ini) e f (fim) forem opostos (um raiz no intervalo [ini, fim]. positivo e outro negativo). Para confirmar esta regra podemos verificar que f (x) = x2 − 4 é contı́nua e que f (0) é igual a −4 (negativo) e f (3) é igual a 5 (positivo). 2 Mais formalmete, temos que [ini, fim] = {x ∈ R, tal que ini ≤ x ≤ fim} 3 Agora que já definimos o intervalo, podemos pensar em como implementar a busca binária. A primeira coisa a ser feita é calcular o meio do intervalo. Não é difı́cil perceber que isto pode ser feito da seguinte maneira: m = (ini + ini)/2; (1) Outro passo importante é descobrir se f (m) é uma raiz de f . Para fazer isto, devemos verificar se f (m) é bem próximo de zero. Como há erros de representação (veja o guia de computação numérica da IBM), quase nunca dois números reais vão ser exatamente iguais. Podemos fazer isto isto verificando se fabs(m*m-2) < 0.01, por exemplo. Para terminar, precisamos definir como reduzir o espaço de busca (i.e., reduzir o intervalo). Podemos notar que se sinal de f (m) é igual ao sinal de f (fim), então pela discussão anterior há ao menos uma raiz entre ini e m e todos valores entre m e fim podem ser ignorados, Podemos aplicar o mesmo raciocı́nio para o inı́cio do intervalo e perceber que se sinal de f (m) é igual ao sinal de f (ini), então há ao menos uma raiz entre m e fim. O código a seguir implementa esta ideia. Complete os balões para que o programa calcule a raiz da função f (x) = x2 − 4. Um versão online deste código está disponı́vel em: ieng.ufmt.br/algoritmos/code/raizes.c 1 include <stdio.h> 2 ... 3 int main() { 4 float ini = 0, fim = 3, 5 r; while( m = (ini+fim)/2; if(fabs(m*m-4) < 0.01) r = m; break; } 6 7 8 9 10 11 12 13 //Condiç~ ao de parada { if (sig(fim*fim-4) == sig(m*m-4)) fim=m; else 14 15 16 17 18 } ) { } //sinal f(fim) == sinal de f(m) return 0; 4 O comando sig(z) devolve o sinal de z. Pronto. Você escreveu o seu primeiro programa numérico! Teste o programa. 3 Desafio Escreva um programa em C que lê um número inteiro n e imprime o resultado √ de n sem utilizar a biblioteca math.h. √ Antes de escrever o código pense em como você faria para calcular n? Qual seria a raiz de função? Qual intervalo você utilizaria? 5