MAC0122  Princípios de Desenvolvimento de Algoritmos
Home  |   Administração  |   Fórum  |   Livros  |   WWW  |   Diário  |   Tarefas  |   Alunos

 

Listas, recursão e inteiros enormes

Esta página é uma versão simplificada do capítulo 12 (Recursive lists) do livro de Eric Roberts. O capítulo discute manipulação recursiva de listas e um programa de aritmética inteira com precisão arbitrária.

 

Definição recursiva de listas

Uma lista encadeada é uma estrutura inerentemente recursiva, especialmente se confundirmos a idéia de lista com a idéia de endereço do primeiro elemento da lista. Suponha, por exemplo, que os elementos de nossas listas são números inteiros. Então podemos dar a seguinte definição recursiva do conceito de lista: uma lista é

Em consonância com essa definição, convém reescrever assim a definição das células da lista:

typedef struct cellT *listT;

struct cellT {                                    
   int    head; // cabeça da lista
   listT  tail; // cauda da lista
} ;

As palavras "cabeça", "cauda", "head" e "tail" têm aqui um significado diferente daquele empregado no contexto de filas.

 

Exercícios

  1. Escreva uma função recursiva que calcule o número de células de uma lista.

  2. Escreva uma função recursiva que faça uma cópia de uma lista dada.

  3. Escreva uma função recursiva que libere o espaço ocupado por uma lista.

  4. Escreva uma função recursiva que verifique se os inteiros armazenados numa lista estão em ordem crescente.

  5. Escreva uma função recursiva que inverta uma lista, ou seja, coloca a última célula no lugar da primeira, a penúltima no lugar da segunda, etc. Sua função não deve criar novas células.

  6. Escreva uma função recursiva que remova de uma lista encadeada uma célula cuja chave (campo head) seja mínima. Documente sua função.

 

Inteiros com número arbitrário de dígitos

O que segue é um pequeno programa que manipula inteiros com número arbitrário de dígitos. O programa é muito simples:

Diremos que um inteiro desse tipo é um bigint. Eis nossas decisões de projeto:

  1. cada bigint será implementado em uma lista;
  2. cada elemento da lista será um dígito decimal;
  3. o primeiro elemento será o dígito das unidades, o segunda o dígito das dezenas, etc.
  4. zero será representado pela lista vazia.

Todas as funções no módulo abaixo são escritas recursivamente para enfatizar o caráter recursivo do conceito de lista. (É bem verdade que isso torna algumas das funções um pouco ineficientes.)

/////////////////////////////////////////////////////////////
// Módulo bigint
// Este módulo manipula bigints, ou seja, inteiros com número
// arbitrário de dígitos. Um bigint é 
//  . NULL (que representa o bigint de valor 0) ou
//  . um número d entre 0 e 9 seguido de um bigint n (que
//       representa o bigint de valor 10 * n + d). 
/////////////////////////////////////////////////////////////


typedef struct cellT *bigIntT;

struct cellT {
   int     finalDigit;    // cabeça (dígito das unidades)
   bigIntT leadingDigits; // cauda (dezenas, centenas etc.)
} ;

// A função NewBigInt cria um bigint com valor i.

bigIntT NewBigInt( int i) {
   bigIntT b;
   if (i < 0) exit( EXIT_FAILURE);
   if (i == 0) 
      return NULL;
   else {
      b = NewBigInt( i / 10);
      return DigitCons( b, i % 10);
   }
}

// A função abaixo converte uma string str de dígitos 
// em um bigint. Exemplos: "1234" é convertida em 1234,
// "001" é convertido em 1, "00" é convertida em NULL.

bigIntT StringToBigInt( string str) {
   int len;
   char ch;
   bigIntT b;
   len = strlen( str);
   if (len == 0) 
      return NULL;
   else {
      ch = str[len - 1];
      str[len - 1] = '\0';
      b = StringToBigInt( str);
      return DigitCons( b, ch - '0');
   }
}

// A função abaixo converte um bigint n em uma string.
// Exemplos: 1234 é convertido em "1234" e NULL é
// convertido em "0".

string BigIntToString( bigIntT n) {
   string oldstr, newstr;
   bigIntT b;
   if (b == NULL) 
      return "0";
   else {
      b = LeadingDigits( n);
      oldstr = BigIntToString( b);
      len = strlen( oldstr);
      newstr = malloc( len + 1 + 1);
      if (newstr == NULL) exit( EXIT_FAILURE);
      strcpy( newstr, oldstr);
      free( oldstr);
      newstr[len] = FinalDigit( n) + '0';
      newstr[len + 1] = '\0';
      return newstr;
   }
}

// A função DigitCons recebe um bigint n e um inteiro d
// entre 0 e 9 e devolve o bigint cujo valor é 10 * n + d.
// Se n é NULL ou d == 0 então a função devolve NULL.

bigIntT DigitCons( bigIntT n, int d) {
   bigIntT cp;
   if (n == NULL && d == 0) 
      return NULL;
   else {
      cp = malloc( sizeof (cellT));
      if (cp == NULL) exit( EXIT_FAILURE);
      cp->finalDigit = d;
      cp->leadingDigits = n;
      return cp;
   }
}

// A função LeadingDigits devolve o bigint formado pelos
// dígitos mais significativos (dezenas, centenas, etc) de n.

bigIntT LeadingDigits( bigIntT n) {
   return (n == NULL) ? NULL : n->leadingDigits;
}

// A função FinalDigit devolve o dígito das unidades de n.

int FinalDigit( bigIntT n) {
   return (n == NULL) ? 0 : n->finalDigit;
}

// Devolve o resultado da soma de n1 com n2.

bigIntT AddBigInt( bigIntT n1, bigIntT n2) {
   return AddWithCarry( n1, n2, 0);
}

// Devolve o valor de n1 + n2 + carry.

bigIntT AddWithCarry( bigIntT n1, bigIntT n2, int carry) {
   int sum;
   bigIntT p1, p2, b;
   p1 = LeadingDigits( n1);
   p2 = LeadingDigits( n2);
   sum = FinalDigit( n1) + FinalDigit( n2) + carry;
   if (sum == 0 && p1 == NULL && p2 == NULL)
      return NULL;
   else {
      b = AddWithCarry( p1, p2, sum / 10);
      return DigitCons( b, sum % 10);
   }
}

// A função MultiplyBigInt devolve o valor de n1 * n2.
// A recursão tem por base a expressão
// FinalDigit( n1) * n2 + LeadingDigits( n1) * n2 * 10.
// Por exemplo, 342 * 173 == 342 * 3 + 342 * 17 * 10.

bigIntT MultiplyBigInt( bigIntT n1, bigIntT n2) {
   int sum;
   bigIntT b, c, n2vezes10;
   if (n1 == NULL) 
      return NULL;
   else {
      b = MultiplyDigit( FinalDigit( n1), n2);
      n2vezes10 = DigitCons( n2, 0);
      c = MultiplyBigInt( LeadingDigits( n1), n2vezes10);
      return AddBigInt( b, c);
   }
}

// A função MultiplyDigit devolve o valor de d * n.

bigIntT MultiplyDigit( int d, bigIntT n) {
   bigIntT b;
   if (d == 0) 
      return NULL;
   else {
      b = MultiplyDigit( d - 1, n);
      return AddBigInt( b, n);
   }
}

Note que não temos uma função FreeBigInt.  Uma tal função seria "perigosa" pois permitiria que o cliente fazer algo como

b = LeadingDigits( n);
. . . 
FreeBigInt( b);

Com isso, o cliente poderia destruir n acidentalmente.

 

Exercícios

  1. Reescreva todas as funções em estilo iterativo.

  2. Escreva uma variante de BigIntToString que insira um caracter '.' entre os dígitos dos milhares e o das centenas, outro caracter '.' entre os dígitos dos milhões e o das centenas de milhares, etc.

  3. Escreva uma função recursiva que compare dois bigints (ou seja, decida se o primeiro é menor, igual ou maior que o segundo). Você pode supor que se o bigint não é nulo então o campo digit da última célula é diferente de 0. Documente sua função. Escreva uma versão iterativa da função.

  4. [Roberts exr.11, p.536]  Escreva uma nova versão do módulo bigint em que cada célula da lista contém um inteiro entre 000 e 999 (portanto, os números serão representados em base 1000).

  5. [Roberts exr.12, p.536]  Amplie o módulo bigint de modo que ele também possa manipular números inteiros negativos. Escreva as funções de subtração e divisão.

 

Aplicação: fatoriais

Para testar nosso módulo de inteiros com número arbitrário de dígitos, vamos escrever uma função que faz uma tabela de fatoriais:

// Programa bigfactorial.

int main( int numargc, char *arg[]) {
   int LowerLimit, Upperlimit, i;
   LowerLimit = atoi( arg[1]);
   Upperlimit = atoi( arg[2]);
   for (i = LowerLimit; i <= UpperLimit; i++) 
      printf( "2%d! = %s\n", i, BigIntToString( BigFact( i)));
   return 0;
}


// A função BigFact recebe um inteiro n e devolve um bigint
// cujo valor é o fatorial de n.

bigIntT BigFact( int n) {
   bigIntT bn, bf;
   if (n == 0)
      return NewBigInt( 1);
   else {
      bn = NewBigInt( n);
      bf = BigFact( n - 1);
      return MultiplyBigInt( bn, bf);
   }
}

Se invocarmos  "bigfactorial 15 20"  teremos:

15! = 1307674368000
16! = 20922789888000
17! = 355687428096000
18! = 6402373705728000
19! = 121645100408832000
20! = 2432902008176640000                   

 

Exercícios

  1. [Roberts exr.9, p.535]  Escreva um programa que imprima uma lista das potências de 2 de 20 a 299. Use o módulo bigint discutido acima.

 


URL of this site: www.ime.usp.br/~pf/mac0122-2002/
Last modified: Mon Oct 9 07:14:54 BRT 2017
Paulo Feofiloff
IME-USP