"A vida imita o vídeo,
Garotos inventam um novo inglês,
Vivendo num país sedento
Um momento de embriaguez."
—Somos Quem Podemos Ser,
Engenheiros do Hawaii
Este é um resumo da primeira parte do
Todos os programas foram extraídos desses livros (alguns foram ligeiramente simplificados).
i = 0;
while (a[i] > 0 && i < n) i++;
if (a[i] > 0) printf("Tudo positivo.");
else printf("Nem tudo positivo.");
Critique o código.
Esta seção dá exemplos dos conceitos de função, documentação e invariantes. Também menciona o mecanismo include da inguagem C. O material está no início da seção 3.1 (Building Blocks) do livro do Sedgewick, em particular no Program 3.1, p.73.
No programa abaixo, a função lg recebe um número inteiro positivo N e devolve o logaritmo de N na base 2 arredondado para baixo. Esse número é conhecido como piso ( log2N ) .
#include <stdio.h> int lg (int); // A função main imprime uma tabela dos valores de lg(N) e N lg(N) // para N = 10^2, 10^3, ..., 10^6. // int main (void) { int i, N = 10; for (i = 1; i <= 6; i++) { N = 10 * N; printf("%7d %2d %9d\n", N, lg(N), N * lg(N)); } return 0; } // A função lg recebe um inteiro N > 0 // e devolve piso(log_2 N) // int lg (int N) { int i, n; i = 0; n = N; while (n > 1) { n = n / 2; ++i; } return i; }
|
Aplique a função lg ao argumento N = 10, por exemplo. A tabela registra os valores das variáveis no início de sucessivas iterações (o início de uma iteração fica imediatamente antes da comparação de n com 0):
Invariante: no começo de cada iteração temos
n = piso(N/2i) .
(A propósito, o comentário /* n = N/2^i */ logo depois do while seria muito útil para ajudar o leitor a entender o que está acontecendo.) No começo da última iteração, é fácil verificar que temos n = 1, donde 1 < N/2i < 2, donde 2i < N < 2i+1, donde i < log2 N < i+1. Portanto, a função lg de fato faz o que promete fazer.
O programa acima também serve para ilustrar o tipo-de-dados int.
int lg1 (int N) {
int i = 0, n;
for (n = N; n > 1; n = n/2)
++i;
return i; }
int lg2 (int N) {
int i = 0, n = 1;
while (n <= N) {
n = 2 * n;
++i;
}
return i - 1; }
int lg3 (int N) {
int i = 0, n;
for (n = 2; n <= N; n *= 2)
i++;
return i; }
int lg4 (int N) {
int i = 0, n = 1;
while (n < N) {
n = 2 * n;
++i;
}
return i;
}
float(x) == (int)x / 2 ceil(x) == (int)x / 2 + 1
Escreva uma função que faça a mesma coisa que float e outra que faça a mesma coisa que ceil.
Esta seção ilustra os conceitos de tipo-de-dados e molde (= cast). Também usa a função rand da biblioteca padrão stdlib. A seção foi baseada no Program 3.2, p.75, do livro do Sedgewick.
#include <stdio.h> #include <math.h> #include <stdlib.h> // Este programa abaixo calcula a média e o desvio padrão // de N números inteiros aleatórios. int main (void) { int i, N, x; float m1, m2; m1 = m2 = 0.0; printf("\nValor de N: "); scanf("%d", &N); for (i = 0; i < N; i++) { x = rand(); m1 += ((float)x) / N; m2 += ((float)x * x) / N; } printf(" Average: %f\n", m1); printf("Std. deviation: %f\n", sqrt(m2 - m1 * m1)); return 0; }
Qual o invariante do processo iterativo no programa acima? Fácil: no começo de cada iteração, m1 é a soma dos i primeiros números aleatórios dividida por N. Analogamente, m2 é a soma dos quadrados dos i primeiros números aleatórios dividida por N.
int rand(void);
int soma;
for (i = 0; i < N; i++) {
x = rand();
soma += x;
}
m1 = (float)x / N;
Veja minhas notas de aula sobre caracteres, strings e inteiros.
Esta seção é baseada nas seções 2.2-2.3, p.56-65, do livro do Roberts. Ela introduz os conceitos de endereço (= address) e ponteiro (= pointer). Em particular, a seção introduz os operadores
& ("endereço de" ou "address-of") e
* ("conteúdo de" ou "dereference").
// Function SolveQuadratic. // Usage: SolveQuadratic(a, b, c, &x1, &x2); // ---------------------------------------- // This function solves the quadratic equation ax^2 + bx + c = 0. // If the equation has no real solution, the function returns 0. // Otherwise, the function returns 1 and places the solutions // in x1 and x2. int SolveQuadratic (float a, float b, float c, float *px1, float *px2) { float discr, sqrtDiscr; discr = b * b - 4 * a * c; if (discr < 0) return 0; sqrtDiscr = sqrt(discr); if (a == 0) return 0; *px1 = (-b + sqrtDiscr) / (2 * a); *px2 = (-b - sqrtDiscr) / (2 * a); return 1; }
int v1, v2, *p1, *p2; v1 = 10; v2 = 25; p1 = &v1; p2 = &v2; *p1 += *p2; p2 = p1; *p2 = *p1 + *p2;
int SomaVetor(int *p, int n) {
int i, s = 0;
for (i = 0; i < n; i++) {
s += *p;
p++;
}
return s; }
Reescreva a função de modo que ela fique mais de acordo com o protótipo
int SomaVetor(int vetor[], int n) ;
int x;
scanf("%d %d", &x, x);
Esta seção introduz o conceito de vetor. Ela corresponde à primeira parte da seção 3.2 (Arrays) do livro do Sedgewick. O programa abaixo é essencialmente o mesmo que Program 3.5, p.84, no Sedgewick.
#include <stdio.h> #define N 10000 // Este programa imprime uma lista de todos os // números primos menores que N. // O método usado é o do crivo de Eratóstenes // (veja Program 3.5, p.84, no Sedgewick). int main (void) { int i, j, a[N]; for (i = 2; i < N; i++) a[i] = 1; for (i = 2; i < N; i++) if (a[i] == 1) { printf("%5d\n", i); for (j = i; i*j < N; j++) a[i*j] = 0; } return 0; }
É claro que cada elemento do vetor a[2..N-1] vale 0 ou 1. Podemos então formular o invariante principal do programa assim: no começo de cada iteração do segundo for, para cada h em 2..N-1,
a[h] == 1 se e somente se h não é divisível por nenhum dos números do intervalo 2..i-1.
Portanto, para h=2..i-1, o número h é primo se e só se a[h]==1.
for (i = 2; i < N; i++) a[i] = 1;
for (i = 2; i < N; i++)
for (j = i; i*j < N; j++) a[i*j] = 0;
for (i = 2; i < N; i++)
if (a[i] == 1)
printf("%4d ", i);
Faça testes para comparar o consumo de tempo da versão original com o desta nova versão.
int a[99];
for (i = 0; i < 99; ++i) a[i] = 98 - i;
for (i = 0; i < 99; ++i) a[i] = a[a[i]];
int a[99];
for (i = 0; i < 99; ++i) *(a+i) = 98 - i;
for (i = 0; i < 10; ++i) . . .
for (i = 0; i < 10; i++) . . .
while (i <= n && a[++i] < 0) {}
while (i <= n && a[i++] < 0) {}
while (i <= n && a[i] < 0) ++i ;
int func1 (int x, int a[]) {
int achou = 0, j = 0;
while (j < n && !achou) {
if (a[j] == x) achou = 1;
else j++; }
if (!achou) j = 0;
return j; }
int func2(int x, int a[]) {
int j, k;
for (k = 0; k < n; k++)
if (a[k] == x) j = k;
return j; }
Esta seção introduz o conceito de alocação dinâmica de memória e a função malloc da biblioteca padrão stdlib. Também introduz o operador sizeof. O programa abaixo é uma cópia do Program 3.6, p.85, do livro do Sedgewick.
#include <stdlib.h> #include <stdio.h> // Este programa imprime uma lista de todos os números primos // menores que N. // O método usado é o do crivo de Eratóstenes. int main (void) { int i, j, N; int *a; printf("\nValor de N: "); scanf("%d", &N); a = malloc(N * sizeof (int)); for (i = 2; i < N; i++) a[i] = 1; for (i = 2; i < N; i++) if (a[i] == 1) { printf("%5d\n", i); for (j = i; i*j < N; j++) a[i*j] = 0; } return 0; }
Há uma relação estreita entre ponteiros, endereços e vetores porque os elementos de um vetor têm endereços consecutivos. Assim, no exemplo acima, as expressões
a[i] e *(a+i)
são equivalentes.
O programa abaixo é uma cópia do Program 3.7, p.87, de Sedgewick.
#include <stdlib.h> int heads (void); // Este programa faz M experimentos. Cada experimento consiste em // contar o número de caras (= heads) em N jogadas de uma moeda. // O programa imprime um histograma dos resultados. // int main (void) { int i, j, cnt, N, M; int *f; printf("\nValor de N: "); scanf("%d", &N); printf("\nValor de M: "); scanf("%d", &M); f = malloc((N+1) * sizeof (int)); if (a == NULL) { printf("A memória está esgotada!\n"); return 1; } for (j = 0; j <= N; j++) f[j] = 0; for (i = 0; i < M; i++) { cnt = 0; for (j = 0; j < N; j++) if (heads()) cnt++; f[cnt]++; } for (j = 0; j <= N; j++) { printf("%2d ", j); for (i = 0; i < f[j]; i += 10) printf("*"); printf("\n"); } free(f); return 0; } // A função heads devolve um elemento aleatório do conjunto {0,1}. // int heads (void) { return rand() <= RAND_MAX / 2; }
[Sedgewick diz "< RAND_MAX/2", mas eu acho que isso está errado. Mesmo "<= RAND_MAX/2" só está certo porque RAND_MAX é ímpar. A propósito, veja a aula sobre números aleatórios.]
Esta seção introduz o conceito de registro (= struct) e a idéia de tipo-de-dados criado pelo usuário (typedef). Esse material está na parte final da seção 3.2 do livro do Sedgewick.
typedef struct { float x; float y; } point; // Esta função devolve a distância entre os points A e B. // O tipo point é um ponto no plano: A.x e A.y são as // coordenadas cartesianas do point A. float distance (point A, point B) { float dx, dy; dx = A.x - B.x; dy = A.y - B.y; return sqrt(dx * dx + dy * dy); }
O programa abaixo gera N pontos no quadrado unitário fechado [0,1]×[0,1] e em seguida conta quantos pares de pontos estão à distância menor que d um do outro. Esse material está em Programs 3.3, 3.4, 3.8, p.79-88, do livro do Sedgewick.
#include <math.h> #include <stdio.h> #include <stdlib.h> typedef struct { float x; float y; } point; float distance (point A, point B) { float dx = A.x - B.x, dy = A.y - B.y; return sqrt(dx * dx + dy * dy); } float randFloat (void) { return 1.0 * rand() / RAND_MAX; } int main (void) { float d, int i, j, cnt, N; point *a; printf("\nValor de N: "); scanf("%d", &N); printf("\nValor de d: "); scanf("%f", &d); a = malloc(N * sizeof (point)); // Deveria ter verificado se a é NULL cnt = 0; for (i = 0; i < N; i++) { a[i].x = randFloat(); a[i].y = randFloat(); } for (i = 0; i < N; i++) for (j = i+1; j < N; j++) if (distance(a[i], a[j]) < d) cnt++; printf("%d pares de pontos mais próximos que %f\n", cnt, d); free(a); return 0; }
Consumo de tempo: proporcional a N2. Assim, quando N dobra, o consumo de tempo é multiplicado por 4. Quando N é multiplicado por 10, o consumo de tempo é multiplicado por 100.
Qual o valor de k no fim do seguinte trecho de programa?
k = 0;
for (i = 2; i < n-2; i++)
for (j = n; j > i; j--)
k++;