Programação Orientada pelos
Objectos
Vigésima oitava aula:
O problema dos degraus
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 2
Nesta aula vamos…
• Primeiro, resolver o “problema dos degraus”, trabalhando pouco.
• Depois, resolver o “problema dos
degraus”, trabalhando um pouco mais para o computador trabalhar menos.
• Finalmente, resolver o “problema dos degraus” mais uma vez, para que os dois trabalhemos menos ☺
09-05-2007
O problema dos degraus
• À porta de casa do João há uma
escada com 10 degraus. João é um
rapaz ginasticado e consegue subir as escadas com passadas de um degrau ou de dois degraus. De quantas
maneiras diferentes pode o João subir a sua escada?
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 4
Outra maneira de ver
• O problema é equivalente a descobrir de quantas maneiras diferentes podemos adicionar 1 e 2, várias vezes cada,
somando 10.
• Por exemplo 1+2+2+1+1+2+1 = 10
corresponde às somas parciais 0, 1, 3, 5, 6, 7, 9, 10.
• Todas as somas parciais começam em zero e acabam em 10, e cada uma delas representa uma maneira de subir as
escadas.
09-05-2007
Generalizando um pouco
• Para o problema ser mais interessante, vamos considerar que a escada tem N degraus e que a passada máxima do João é P.
• Ou seja, de quantas maneiras
podemos somar os números 1, … P, dando N?
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 6
O grafo dos degraus
• Se o João estiver no degrau X, para que degraus pode subir? Para o degrau X+1 e para o degrau X+2 (se estes valores não forem maiores que 10).
• O rés-do-chão é 0 e o último degrau é 10.
0
1
2
3 4 5 6
7
8 9
10
09-05-2007
Estratégia
• Construímos o grafo, calculamos todos os caminhos e contamos quantos são.
• Recorremos às classes Graph e AllPaths.
• A única parte delicada é a construção do grafo.
• Resolvemos o problema com a classe Steps.
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 8
Classe Steps
public class Steps {
private int steps;
private int jump;
private Graph graph;
private ArrayList<ArrayList<Integer>> paths;
public Steps(int steps, int jump) {
this.steps = steps;
this.jump = jump;
makeGraph();
// System.out.println(graph);
} }
private void makeGraph() {
graph = new Graph(steps+1);
for (int i = 0; i < steps; i++)
for (int j = 1; j <= jump && i+j <= steps; j++) graph.connect(i, i+j);
}
public void compute() {
AllPaths ap = new AllPaths(graph);
ap.compute(0, steps);
paths = ap.getPaths();
}
public int solution() {
return paths.size();
}
09-05-2007
Testando
public static void testSteps() {
Scanner in = new Scanner(System.in);
PrintWriter out = new PrintWriter(System.out, true);
while (in.hasNext()) {
int x = in.nextInt();
int y = in.nextInt();
Steps s = new Steps(x, y);
s.compute();
ArrayUtil.writeln(out, s.getPaths(), ArrayUtil.NEWLINE);
out.println(s.solution());
} }
5 2
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 5]
[0, 1, 2, 4, 5]
[0, 1, 3, 4, 5]
[0, 1, 3, 5]
[0, 2, 3, 4, 5]
[0, 2, 3, 5]
[0, 2, 4, 5]
8 7 3
[0, 1, 2, 3, 4, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 7]
[0, 1, 2, 3, 4, 6, 7]
[0, 1, 2, 3, 4, 7]
[0, 1, 2, 3, 5, 6, 7]
[0, 1, 2, 3, 5, 7]
[0, 1, 2, 4, 5, 7]
[0, 1, 2, 4, 6, 7]
...
[0, 3, 4, 6, 7]
[0, 3, 4, 7]
[0, 3, 5, 6, 7]
[0, 3, 5, 7]
[0, 3, 6, 7]
44 10 2
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 10]
[0, 1, 2, 3, 4, 5, 6, 7, 9, 10]
...
[0, 2, 4, 6, 7, 9, 10]
[0, 2, 4, 6, 8, 9, 10]
[0, 2, 4, 6, 8, 10]
89
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 10
O grafo dos degraus é acíclico
• Isto é, partindo de um vértice, é impossível regressar a ele.
• Logo, poderíamos simplificar a função visit em AllPaths:
public void visit(int x) {
path.push(x);
// visited[x] = true;
if (x == destination)
paths.add(new ArrayList<Integer>(path));
else
for (int y : graph.successors(x)) // if (!visited[y])
visit(y);
// visited[x] = false;
path.pop();
}
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 11
Dispensando o grafo
• Vendo bem, podemos dispensar o grafo, observando que no ciclo for as únicas
ligações que se aproveitam são as de x para x+1, ..., x+jump.
public class StepsSimple {
private int steps;
private int jump;
private ArrayList<ArrayList<Integer>> paths;
public StepsSimple(int steps, int jump) {
this.steps = steps;
this.jump = jump;
}
• Fazemos isso numa nova
classe,
StepsSimple:
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 12
Calculando os caminhos, sem grafo
public void compute() {
paths = new ArrayList<ArrayList<Integer>>();
path = new Stack<Integer>(steps + 1);
visit(0);
} public void visit(int x) {
path.push(x);
if (x == steps)
paths.add(new ArrayList<Integer>(path));
else
for (int i = 1; i <= jump && x + i <= steps; i++) visit(x + i);
path.pop();
}
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 13
Contando os caminhos
• Se só quisermos contar, podemos simplificar ainda mais:
public void count() {
countPaths = 0;
visitCounting(0);
} public void visitCounting(int x) {
if (x == steps) countPaths++;
else
for (int i = 1; i <= jump && x + i <= steps; i++) visitCounting(x + i);
}
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 14
Complexidade
• Meçamos os tempos de execução:
public static void testTimingStepsSimple(
int from, int upto, int jump, int repetitions) {
for (int j = from; j <= upto; j++) {
Clock c = new Clock();
for (int i = 0; i < repetitions; i++) {
StepsSimple s = new StepsSimple(j, jump);
s.count();
}
long t = c.milliseconds();
System.out.println(t);
} }
testTimingStepsSimple(15, 30, 2, 100); testTimingStepsSimple(10, 25, 3, 100);
O tempo cresce exponencialmente:
quando o tamanho dos dados aumenta uma unidade, o tempo passa para o dobro (mais ou menos).
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 15
Tabelando as soluções
public static void testStepsSimpleTable() {
{
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int jump = in.nextInt();
for (int i = 0; i <= n; i++) {
StepsSimple s = new StepsSimple(i, jump);
s.count();
System.out.println(i + " " + s.getCountPaths());
} } }
Mas isto são os números de Fibonacci!
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 16
Números de Fibonacci
• Todos os programadores conhecem a função de Fibonacci:
Fibonacci(0) = 0 Fibonacci(1) = 1
Fibonacci(x) = Fibonacci(x – 2) + Fibonacci(x – 1), se x ≥ 2
Programamos na classe
Fibonacci:
public class Fibonacci {
public static int at(int x) {
assert x >= 0;
return x == 0 ? 0 : x == 1 ? 1 : at(x-2) + at(x-1);
}
} Note bem: o número de caminhos quando há x degraus é Fibonacci.at(x+1).
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 17
public int get(int x) {
assert x >= 0;
assert x <= MAX_FIBONACCI;
return table[x];
}
Precalculando
• Podemos precalcular, não são muitos:
public class Fibonacci {
public static final int MAX_FIBONACCI = 46;
private int[] table;
// … }
public Fibonacci() {
init();
}
private void init() {
table = new int[MAX_FIBONACCI +1];
table[0] = 0;
table[1] = 1;
for (int i = 2; i <= MAX_FIBONACCI ; i++) table[i] = table[i-2] + table[i-1];
}
Para mais de 46, o valor da função ultrapassa os
limites do tipo int.
Bem mais eficiente do que a função at.
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 18
Calculando iterativamente
• Exercício clássico:
calcular números de Fibonacci
iterativamente:
public static int fib(int x) {
int previous = 1;
int result = 0;
for (int i = 1; i <= x; i++) {
int z = previous;
previous = result;
result += z;
}
return result;
}
09-05-2007
Problema dos degraus, de novo
• O problema é contar as maneiras de subir as escadas.
Podemos abordá-lo assim:
• Seja F(x) o número de maneiras diferentes de subir uma escada de x degraus, com uma passada máxima de 2.
• Suponhamos que já resolvemos o problema para todas as escadas com 1, 2, ..., x-1 degraus e que, portanto,
conhecemos F(1), F(2), ..., F(x-1). Então, quanto vale F(x)?
• Ora bem, para subir x degraus, ou se sobem x-1 degraus e depois mais um, ou se sobem x-2 degraus e depois mais dois, de uma vez.
• Logo: F(x) = F(x-1) + F(x-2). Além disso, F(1) = 1 e F(2) = 2, nitidamente.
• Logo: F(x) = Fibonacci.at(x+1).
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 20
0 0 1 1 2 1 3 2 4 3 5 5 6 8 7 13 8 21 9 34 10 55 11 89 12 144 13 233 14 377 15 610 16 987 17 1597 18 2584 19 4181 20 6765 21 10946 22 17711 23 28657 24 46368 25 75025 26 121393 27 196418 28 317811 29 514229 30 832040 31 1346269 32 2178309 33 3524578
34 5702887 35 9227465 36 14930352 37 24157817 38 39088169 39 63245986 40 102334155 41 165580141 42 267914296 43 433494437 44 701408733 45 1134903170 46 1836311903 47 2971215073 48 4807526976 49 7778742049 50 12586269025 51 20365011074 52 32951280099 53 53316291173 54 86267571272 55 139583862445 56 225851433717 57 365435296162 58 591286729879 59 956722026041 60 1548008755920 61 2504730781961 62 4052739537881 63 6557470319842 64 10610209857723 65 17167680177565 66 27777890035288 67 44945570212853
68 72723460248141 69 117669030460994 70 190392490709135 71 308061521170129 72 498454011879264 73 806515533049393 74 1304969544928657 75 2111485077978050 76 3416454622906707 77 5527939700884757 78 8944394323791464 79 14472334024676221 80 23416728348467685 81 37889062373143906 82 61305790721611591 83 99194853094755497 84 160500643816367088 85 259695496911122585 86 420196140727489673 87 679891637638612258 88 1100087778366101931 89 1779979416004714189 90 2880067194370816120 91 4660046610375530309 92 7540113804746346429 93 12200160415121876738 94 19740274219868223167 95 31940434634990099905 96 51680708854858323072 97 83621143489848422977 98 135301852344706746049 99 218922995834555169026 100 354224848179261915075
Tabela
09-05-2007
Grande Fibonacci
• Para calcular números de Fibonacci grandes usamos a classe BigInteger:
public class FibonacciBig {
public static BigInteger fib(int x) {
BigInteger previous = BigInteger.ONE;
BigInteger result = BigInteger.ZERO;
for (int i = 1; i <= x; i++) {
BigInteger z = BigInteger.ZERO.add(previous);
previous = BigInteger.ZERO.add(result);
result = result.add(z);
}
return result;
} }
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 22
Controlo
• Qual é a fórmula dos números de Fibonacci?
• Qual era o nome próprio de Fibonacci?
• O que é um grafo acíclico?
09-05-2007
Exercícios
• Calcule numericamente o limite do quociente dos pares de números de
Fibonacci consecutivos. Verifique que é (51/2+1)/2.
• Este é o número dourado, phi.
• Confirme que
Fib(x) = (phin – (– phi)-n / (2·phi – 1)
09-05-2007 Programação Orientada pelos Objectos-28 © Pedro Guerreiro 24
Na próxima aula
• Estudaremos os iteradores.
• Os iteradores representam sequências de objectos, que ficam disponíveis cada um após o outro.