SlideShare uma empresa Scribd logo
1 de 167
Estrutura de Dados I


              Docente:   Dikiefu Fabiano,
Msc
Sumário
1.   Introdução
     1.1 Tipo de dados Abstracto -TDA
     1.2 Ponteiros
     1.3 Funções
     1.4 Estruturas e tipos de dados definidos pelo programador
2.   Listas Lineares
     2. 1 Listas Sequenciais
     2.2 Listas ligadas
     2.3 Listas Circulares
     2.4 Listas duplamente ligadas
     2.5 Pilhas
        2.5.1Implementação sequencial da pilha
        2.5.2Implementação dinâmica da pilha
     2.6 Filas
        2.6.1Implementação sequencial da fila
        2.6.2Implementação dinâmica da fila
3.      Árvores
     3.1 Introdução
     3.2 Representação de árvores
     3.2 Definição
     3.3 Terminologia
     3.4 Árvores binárias
     3.5 Árvores balanceadas
     3.6 Definição da estrutura de dados
4.      Ordenação
       4.1 Ordenação por Selecção
       4.2 Ordenação por Inserção
       4.3 Shellsort
       4.4 Quicksort
       4.5 Heapsort
5.      Pesquisa
       5.1 Pesquisa Sequencial
       5.2 Pesquisa Binária
       5.3 Árvores de Pesquisa
Capitulo I: Introdução

Sumário:

  1.1Tipo de Dado Abstracto (TDA)
  1.2Ponteiros
  1.3Funções
  1.4Estruturas
  1.5 Tipos de dados definidos pelo
     programador
1.1 Tipo de Dados Abstracto -
TDA
   Tipos de Dados
    ◦ Tipo de dados de uma variáveis
       define o conjunto de valores que a variável pode
        assumir.
       Especifica a quantidade de bytes que deve ser
        reservadas a ela.
    ◦ Tipos de dados podem ser vistos como métodos
      para interpretar o conteúdo da memória do
      computador
    ◦ Podemos ver o conceito de tipo de dados de uma
      outra perspectiva: não em termos do que um
      computador pode fazer (interpretar os bits …) mas
      em termos do que o utilizador desejam fazer
      (somar dois inteiros…)
1.1 Tipo de Dado Abstracto - TDA
   Tipo de Dado Abstracto
    ◦ Agrupa a estrutura de dados juntamente com as operações
      que podem ser feitas sobre esses dados
    ◦ O TDA encapsula a estrutura de dados. Os utilizadores do
      TDA só tem acesso a algumas operações disponibilizadas
      sobre esses dados
    ◦ Utilizador do TDA x Programador do TDA
       Utilizador só “enxerga” a interface, não a sua
         implementação
       Dessa forma, o utilizador pode abstrair da implementação
         específica.
       Qualquer modificação nessa implementação fica restrita
         ao TDA
    ◦ A escolha de uma representação específica é fortemente
      influenciada pelas operações a serem executadas
1.1 Tipo de Dado Abstracto -
TDA
   Estrutura de Dados - ED
    ◦ É um método particular de se implementar um
      TDA
    ◦ Cada ED é construída dos tipos primitivos
      (inteiro, real, char, …) ou dos tipos compostos
      (array, registo, …) de uma linguagem de
      programação.
    ◦ Algumas operações realizadas em TDA são:
      criação, inclusão, busca e remoção de dados.
   Exemplos de TDA
    ◦ Lineares:             Não Lineares:
       Listas ligadas               . Árvores
       Pilhas                       . Grafos
       Filas
TDA-Exemplo
 Existem vários métodos para especificar um TDA. O
  método que usaremos é semi-formal e faz uso
  intensivo da notação de C. Para ilustrar o conceito de
  um TDA e método de especificação, examinemos o
  TDA RACIONAL que corresponde ao conceito
  matemático de um número racional.
 Um número racional é o que pode ser expresso como o
  quociente de dois inteiros.
 As operações sobre números racionais que definimos
  são:
   ◦ Criação de um número racional
   ◦ Adição
   ◦ Multiplicação
   ◦ Teste de igualdade.
TDA-Exemplo
//definição de valor
abstract typedef <integer, integer> RACIONAL;
condiction RACIONAL [1] <> 0;

//definição de operador
abstract RACIONAL criaracional(a, b)
int a, b;
precondition b <> 0;
postcondition criaracional[0]== a;
              criaracional[1]== b;
abstract RACIONAL soma (a, b)
RACIONAL a, b;
postcondition soma[1]== a[1]*b[1];
               soma[0]==a[0]*b[1]+b[0]*a[1];
abstract RACIONAL mult(a, b)
RACIONAL a, b;
postcondition mult[0]==a[0]*b[0];
               mult[1]==a[1]*b[1];

abstract RACIONAL igual(a,b)
RACIONAL a, b;
postcondition igual==(a[0]*b[1]==a[1]*b[0]);

Exercício:
Crie o TODA para o conjunto de números complexos com todas
as suas operações básicas.
1.2 Apontadores
   Na realidade, C permite que o
    programador referencie a posição de
    objectos bem como os próprios
    objectos (isto é, o conteúdo de suas
    posições)
1.2 Apontadores
•   O objectivo:
    – Armazenar o endereço de outra variável.
•   Sintaxe
    tipo * ptr
      • ptr - é o nome da variável do tipo apontador
      • tipo – o tipo da variável para qual apontará.
      • * - indica que é uma variável do tipo apontador
•   Exemplo 1:
    char *pc;
    int *pi;
    float *pf
1.2 Apontadores
   Um ponteiro é como qualquer outro tipo de dado
    em C.
   O valor de um ponteiro é uma posição de
    memória da mesma forma que o valor de um
    inteiro é um número.
   Os valores dos ponteiros podem ser atribuídos
    como qualquer outro valor.
   A conversão de pf do tipo “ponteiro para um
    número de ponto flutuante” para o tipo “ponteiro
    para um inteiro” pode ser feita escrevendo-se:
                      pi = (int *) pf;
   Se pi é um ponteiro para um inteiro, então pi +1 é
    o ponteiro para o inteiro imediatamente seguinte
    ao inteiro *pi em memória, pi–1 é o ponteiro para
    o inteiro imediatamente anterior a *pi.
Apontadores
 Por exemplo, suponha que
  determinada máquina usa
  endereçamento de bytes, que um
  inteiro exija 4 bytes e que valor de pi
  seja 100 (isto é, pi aponta para inteiro
  *pi na posição 100). Sendo assim, o
  valor de pi–1 é 96, o valor de pi+1 é
  104.
 De modo semelhante, se o valor da
  variável pc é 100 e um caractere tem
  um byte, pc – 1 refere-se a posição 99
  e pc+1 à posição 101.
Apontadores
 Uma área na qual os ponteiros    x =5
  de C desempenham um              printf(“%dn”, x) // imprime 5
  notável papel é passagem de
  parâmetro para funções.          func(x);          // imprime 6
 Normalmente, os parâmetros       printf(“%dn”, x) // imprime 5
  são passados para uma            …
  função em C por valor, isto é,   func(y)
  os valores sendo passados
  são copiados nos parâmetros      int y
  da função de chamada no          {
  momento em que a função for          ++y;
  chamada. Se o valor de um
                                       printf(“%dn”, y);
  parâmetro for alterado dentro
  da função, o valor no            }
  programa chamada não será
  modificado.
Se precisar usar func para modificar o valor de x, precisaremos passar
o endereço de x


x =5
printf(“%dn”, x)       // imprime 5
func(&x);         // imprime 6
printf(“%dn”, x)       //imprime 6
…
func(py)
int *py
{
    ++(*py);
    printf(“%dn”, *py);
}
1.2.1 Inicialização automática de
apontadores
•   Um bom hábito para evitar problemas
    de programação é a inicialização
    sempre dos apontadores
•   A constante simbólica NULL, quando
    colocada num apontador, indica que
    este não aponta para nenhuma
    variável.
     int x = 5;
     float pi = 3.1415;
     int px = &x;
     float * ppi = &pi;
1.3 Funções
   Conceito:
    ◦ É uma unidade autónoma de código do
      programa e é desenhada para cumprir
      uma tarefa particular.
   Sintaxe
    tipo nome_da_função (parametros){
    comandos;
    }
1.3 Funções
•   Quando uma função não retorna um valor para função que a
    chamou ela é declarada como void.
•   Ex:
    #include<stdio.h>
    void linha (char ch, int size){
        int i;
        for (i=1; i size; i++)
             putchar(ch);
    }
    main(){
       char lin;
       int tamanho;
       printf(“Introduza o tipo de linha”);
       scanf(“%c”, &lin);
       printf(“Introduza o tamanho da linha”);
       scanf(“%d”, &tamanho);

        linha(lin, tamanho);
        getchar();
    }
1.3 Funções
1.3.3 Função com retorno
O tipo de retorno tem que ser
declarada.                        main(){
                                    int b, e;
   Ex1 :                           printf(“Introduza a base e
    int potencia (int base, int   expoente”);
       expoente){                    scanf(“%d%d”, &b, &e);
       int i, pot=1;                 printf(“valor=%dn”,
       If (expoente<0)            potencia(b,e));
                                           getchar();
          return;
                                  }
       for (i=1; i <= expoente;
          i++)
           pot=pot*base;
       Return pot;
    }
Estrutura de dados e tipo de dados definido pelo
utilizador
   Estruturas são peças contíguas de armazenamento que contém
    vários tipos simples agrupados numa entidade.
   Estrutura são manifestadas através da palavra reservada struct
    seguida pelo nome da estrutura e por uma área delimitada por
    colchetes que contém campos.
       struct pessoa {
          char nome[30];
          int idade;
          char sexo;
       };
   Para criação de novos tipos de estrutura de dados utiliza-se a
    palavra-chave: typedef
      typedef struct pessoa {
         char nome[30];
         int idade;
         char sexo;
      } PESSOA;
Estrutura
   Para obter directamente o valor de um campo, a
    forma é <nome_da_estrutura>.<campo>.
   Para obter o valor de um campo apontado por um
    ponteiro a forma é <nome_da_estrutura> ->
    <campo>
   Ex
       …
     PESSOA func, *estud, &docente = func ;
     func.nome=“Adriano”;                   //atribui o valor Adriano para o campo nome
     func.idade=25;           //atribui o valor 25 para o campo idade
     func.sexo=“M”;                         //atribui o valor M para o campo sexo
     estud=&func;            //faz estud apontar para func
     estud->nome=“Joana”;                  //muda o valor do campo nome para Joana
     estud ->idade =22;                    //muda o valor do campo idade para 22
     estud ->sexo =“F”;                    //muda o valor do campo sexo para F
     docente.Nome=“Pedro”;                 //Referências usam a notação de ponto

     …
Estruturas Recursivas
   Estruturas podem ser recursivas, i.e.,
    podem conter ponteiros para si
    mesmas. Esta peculiaridade ajuda a
    definir estruturas do tipo lista.
           struct lista{
            struct lista *prox;
            int dado;
            }
Capitulo II: Listas
Sumário
2. Listas Lineares
     2. 1    Listas Sequenciais
     2.2     Listas ligadas
     2.3     Listas Circulares
     2.4     Listas duplamente ligadas
     2.5     Pilhas
            2.5.1 Implementação sequencial da pilha
            2.5.2 Implementação dinâmica da pilha
     2.6     Filas
            2.6.1 Implementação sequencial da fila
            2.6.2 Implementação dinâmica da fila
            2.6.3 Fila circular
Listas
   Listas são uma colecção de objectos,
    onde os itens podem ser adicionados
    em qualquer posição arbitrária.
Listas
Propriedades
  • Uma lista pode ter zero ou mais
    elementos
  • Um novo item pode ser adicionado
    a lista em qualquer ponto
  • Qualquer item da lista pode ser
    eliminado
  • Qualquer item da lista pode ser
    acessado
Listas - Formas de
Representação
   Sequencial
    ◦ Explora a sequencialidade da memória do computador, de tal
      forma que os nós de uma lista sejam armazenados em
      endereço sequencias. Podem ser representado por um vector
      na memória principal ou um arquivo sequencial em disco.

    L        Adão    Adelin Basto Daniel Josefin              …
            0       1a     2 s   3       a
                                         4       5                MAX-1
   Ligada
    ◦ Esta estrutura é tida como uma sequencia de elementos
       ligados por ponteiros, ou seja, cada elemento deve conter,
       além do dado propriamente dito, uma referencia para o
       próximo elemento da lista.
            Adão    Adelina   Bastos    Daniel     Josefina

        L
Listas sequenciais
   Uma lista representada de forma sequencial é um
    conjunto de registos onde estão estabelecidas
    regras de precedência entre seus elementos.
   Implementação de operações pode ser feita
    utilizando array e registo, associando o elemento a[i]
    com o índice i.
   Características
    ◦ Os elementos na lista estão armazenados
      fisicamente em posições consecutivas
    ◦ A inserção de um elemento na posição a[i] causa
      o deslocamento a direita do elemento a[i] ao
      último.
    ◦ A eliminação do elemento a[i] requer o
      deslocamento à esquerda do a[i+1] ao último.
Listas Sequenciais
   Vantagens
    ◦ Acesso directo a qualquer dos elementos da lista.
    ◦ Tempo constante para aceder o elemento i - (depende
      somente de índice)
   Desvantagens
    ◦ Para inserir ou remover elementos temos que deslocar
      muitos outros elementos
    ◦ Tamanho máximo pré-estimado
   Quando usar
    ◦ Listas pequenas
    ◦ Inserção e remoção no fim da lista
    ◦ Tamanho máximo bem definido
Listas sequenciais
Operações básicas
   Definição
    #define MAX 50                   //tamanho máximo da lista
    typedef char elem[20];           // tipo base dos elementos da lista
    typedef struct tlista{
           elem v[MAX];//vector que contém a lista
           int n;                    //posição do último elemento da lista
    };

    Tlista
                Adão     Alberto       Ana       Daniela   Carmen                          …
    n=5           v
                          0        1         2         3        4        5         6                MAX – 1




1.Criar uma lista vazia                                    2. Verificar se uma lista está vazia
             void criar(tlista *L) {                                  int vazia(tlista L) {
                           L -> n = 0;                                              return (L .n == 0);
             }                                                        }
Listas sequenciais
Operações básicas

3. Verificar se uma lista está cheia              4. Obter o tamanho de uma lista

            int cheia( tlista L) {                            int tamanho ( tlista L){
                          return (L .n == MAX);                          return (L.n);
            }                                                 }



5. Obter o i-ésimo elemento de uma                6. Pesquisar um dado elemento,
lista                                             retornando a sua posição.

int elemento( tlista L, int pos, elem *dado )     int posicao( tlista L, elem dado )
{                                                 {
      if ((pos > L.n) || (pos <=0))                    int i;
              return (0);                                for (i=1; i<L.n; i++)
      *dado = L.v [pos - 1];                                    if(L.v[i-1] == dado)
       return 1;                                                return i;
}                                                        return 0;
                                                  }
Listas Sequenciais
Operações básicas

7. Inserção de um elemento em uma                8. Remoção do elemento de uma
determinada posição                              determinada posição
(requer o deslocamento à direita dos             (requer o deslocamento à esquerda dos
elementos v(i+1) … v(n))                         elementos v(p+1) … v(n))

// retorna 0 se a posição for inválida ou se a   /* o parâmetro dado irá receber o elemento
lista                                            encontrado.
// estiver cheia, caso contrário retorna 1           Retorna 0 se a posição for inválida , caso
                                                 contrário
                                                      retorna 1 */
int inserir( tlista *L, int pos, elem dado )
{
   int i;                                        int remover( tlista *L, int pos, elem *dado )
   if (( L->n == MAX) || ( pos > L->n + 1 ))     {
       return (0);                                    int i;
   for (i = L->n; i >= pos; i--)                      if ( ( pos > L->n) || (pos <= 0) )
       L->v[i] = L->v [i-1];                               return (0);
   L->v[pos-1] = dado;                                *dado = L-> v[pos-1];
   (L->n)++;                                          for (i = pos; i <= (L->n) - 1; i++)
   return (1);                                             L->v[i-1] = L->v [i];
}                                                      (L->n)--;
                                                    return (1);
                                                  }
Listas Ligadas
   Uma lista ligada ou lista encadeada é uma estrutura de dados
    linear e dinâmica. Ela é composta por células que apontam
    para o próximo elemento da lista.
   Para "ter" uma lista ligada/encadeada, basta guardar seu
    primeiro elemento, e seu último elemento aponta para uma
    célula nula.
   Vantagens
    ◦ A inserção ou a retirada de um elemento na lista não implica na mudança de lugar de
      outros elementos;
    ◦ Não é necessário saber, anteriormente, o número máximo de elementos que uma lista
      pode ter, o que implica em não desperdiçar espaço na Memória Principal (Memória
      RAM) do computador.
   Desvantagens
    ◦ manipulação torna-se mais "perigosa" uma vez que, se o encadeamento (ligação) entre
      elementos da lista for mal feito, toda a lista pode ser perdida;
    ◦ Para acessar o elemento na posição n da lista, deve-se percorrer os n - 1 anteriores.
Listas ligadas
 As listas ligadas são conjunto de
  elementos ligados, onde cada elemento
  contem uma ligação com um ou mais
  elementos da lista.
 Uma lista ligada tem necessariamente uma
  variável ponteiro apontando para o seu
  primeiro elemento. Essa variável será
  utilizada sempre, mesmo que esteja vazia,
  e deverá apontar sempre para o início da
  lista.
 Cada elemento da lista ligada será
  composta por duas/três partes principais:
  uma parte conterá informações e a(s)
Listas Ligadas - Propriedades
 Uma lista pode ter zero ou mais
  elementos
 Um novo item pode ser adicionado a
  lista em qualquer ponto
 Qualquer item da lista pode ser
  eliminado
 Qualquer item da lista pode ser
  acessado.
Listas Ligadas
 Uma lista ligada é composta por
  elementos alocadas em memória.
 Utiliza-se duas funções para alocar
  (desalocar) memória dinamicamente:
    ◦ malloc()
    ◦ free()
   Ex:
    L = (lista) malloc(sizeof(lista));
    free(L);
Lista Simplesmente Ligada
 Cada elemento da lista possui uma única
  conexão. Assim, cada elemento será
  formado por um bloco de dados e um
  ponteiro para próximo elemento.
  Chamaremos cada elemento nó.
 Um nó é um conjunto de informações de
  tipos diferentes, portanto ela é uma estrutura
  definida pelo programador:
      struct no{
             int dado;
             struct no *prox; //ponteiro para o próximo
    nó
         }
Listas Simplesmente Ligadas
   Sendo que uma memória alocada
    dinamicamente não possui o nome
    como no caso das variáveis
    declaradas no programa, toda lista
    deverá ter uma variável que apontará
    para o seu primeiro elemento.
     struct no *head; //cabeça da lista
     head=null; //inicia a lista com nulo
Listas Simplesmente Ligadas
 Podemos nos referenciar a uma lista
  ligada através do seu primeiro nó.
 Entretanto, toda vez que realizarmos
  uma inserção no início da lista
  teremos que garantir que todas as
  partes do programa que referenciam a
  lista tenha suas referencias
  actualizadas. Para evitar este
  problema, é comum usar um nó
  especial chamado nó cabeça, e um
  ponteiro para o primeiro elemento da
  lista.
Listas Simplesmente Ligadas
 A implementação em C de uma lista
  simplesmente ligada com nó cabeça.
 Precisamos de dois tipos de dados:
    ◦ Um tipo nó, representa cada elemento da
      lista.
    ◦ e um tipo lista que contém um ponteiro
      para nó cabeça.
Listas Simplesmente Ligadas
 typedef struct no{
    int dado; // o dado poderia ser de qualquer tipo
    struct no *prox; // ponteiro para próximo
 elemento

 } NO

 typedef struct lista{
         NO *head;
 }LISTA
Listas Simplesmente Ligadas
    As operações básicas sobre a estrutura são: criação da lista e de nó.
1.    Criação de lista

     LISTA *criaLista(){
          LISTA *L;
          L=(LISTA *)malloc(sizeof(LISTA));
          L->head = NULL;
          return L;
     }
2.     Criação de nó

      NO *criaNo(int dado){
          NO *no;
          no = (NO *)malloc(sizeof(NO));
          no->dado = dado;
          no->prox = NULL;
          return no;
      }
Listas Simplesmente Ligadas
 Ao contrário dos vectores, onde acessamos qualquer
  elemento usando um índice numérico, com uma lista
  ligada temos apenas acesso sequencial, por exemplo,
  para chegar ao decimo elemento temos que passar por
  todos nove elementos anteriores.
 Para percorrer uma lista, precisamos obter seu primeiro
  elemento e então obtemos os elementos subsequentes
  seguindo os ponteiros de próximo elemento em cada
  nó.

    NO *primeiro(LISTA *L){
       return (L->head);
    }

    NO *proximo(NO *no){
       return (no->prox);
    }
Listas Simplesmente Ligadas
   Andar para trás em uma lista simplesmente ligada não é tão
    simples, requer que a lista seja percorrida desde o início verificado
    se, para cada elemento x, proximo(x) é o elemento do qual
    procuramos o elemento anterior.

    NO *anterior(LISTA *L, NO *no){
        NO *p;
        if(no==L->head)
                  return NULL;
        p = primeiro(L);
        while(proximo(p)!=NULL){
                  if(proximo(p) == no)
                           return p;
                  p = proximo(p);
        }
        return NULL;
    }
Listas Simplesmente Ligadas
   Obter o último elemento também requer que
    toda a lista seja percorrida.

    NO *ultimo(LISTA *L){
      NO *p;
      p = primeiro(L);
      if (p == NULL)
             return NULL;
      while(proximo(p)!=NULL)
             p = proximo(p);
      return p;
    }
Listas Simplesmente Ligadas
   Podemos querer 3 tipos de inserções em uma lista:
    ◦ no início,
    ◦ no fim,
    ◦ ou em uma posição arbitrária.
   Na lista simplesmente ligada a inserção no início é trivial:
    Fazemos o próximo do novo elemento apontar para o
    primeiro elemento da lista e fazemos com que o novo
    elemento seja o primeiro elemento (cabeça) da lista.
   A inserção em posição arbitrária pode ser feito em tempo
    constante se tivermos um ponteiro para o elemento que será
    o elemento anterior ao novo elemento:
    void insere_apos(NO *novo, NO *anterior){
         novo->prox = anterior->prox;
         antrior->prox = novo;
    }
    A inserção no final da lista exige que encontremos
     seu último elemento.
Listas Simplesmente Ligadas
   Remoção de um elemento arbitrário requer que
    encontremos o elemento anterior ao removido.

    void remove(LISTA *lista, NO *no){
        NO *ant;
        if(no == lista->head){
                lista->head = no->prox;
        } else{
                ant = anterior(lista, no)
                if(ant!=NULL)
                        ant->prox = no->prox;
        }
        free(no);
    }
Listas duplamente ligadas
   Listas duplamente ligadas são usadas
    frequentemente nos problema que
    exija uma movimentação eficiente
    desde um nó quer para o seu
    antecessor, quer para o seu sucessor.

frent
                                  NULL
  e      valor   valor   valor
NULL                             Atrás
Listas duplamente ligadas
 Uma das virtudes de listas
  duplamente ligadas, é o facto de nos
  podemos deslocar para o nó anterior
  ou para nó seguinte, a um nó dado
  com facilidade.
 Uma lista duplamente ligada permite-
  nos remover um nó qualquer da lista
  em tempo constante, usando apenas
  um ponteiro para essa célula.
Listas duplamente ligadas -
operações
   anterior(): devolve o elemento anterior a
    um determinado elemento.
   cria(): devolve uma lista com um
    elemento
   insere(): insere um determinado
    elemento na lista.
   mostra(): mostra o conteúdo da lista.
   procura(): devolve a posição de um
    determinado elemento.
   remove(): remove o elemento desejado
    da lista.
Lista duplamente Ligada -
operações
#include<stdio.h>
#include<stdlib.h>
typedef struct listadl{
     LDL *prox; //próximo elemento da lista
     LDL *ant; //elemento anterior da lista
     int dado;
}LDL;
Lista duplamente Ligada -
operações
Devolve uma lista com elemento elem.

LDL *cria(int elem){
    LDL*tmp;
    tmp =(LDL *)malloc(sizeof(tmp));
    tmp->dado = elem;
    tmp->prox = NULL;
    tmp->ant = NULL;
    return tmp;
}
Lista duplamente Ligada -
operações
Insere o elemento elem na lista L.

void *insere (int elem, LDL *L){
       LDL*tmp;
       tmp=L;
       while((tmp->prox ==NULL)==0)
                tmp = tmp->prox;
       tmp->prox = cria(elem);
       (tmp->prox)-> ant = tmp;
}
Lista duplamente Ligada -
operações
Imprime todos os elementos da   if((no1 == NULL)==0)
lista.
                                  printf(“O proximo:%d”,
void *mostra(LDL *L){           no1->dado);
  LDL*tmp, *no1, *no2;          else
  tmp=L;                            printf(“Não possui
  while((tmp->prox              próximo.”);
==NULL)==0){                    if((no2 == NULL)==0)
    no1 = tmp->prox;                printf(“O anterior:%d”,
    no2 = tmp->ant;             no2->dado);
    printf(“O elemento: %d”,         else printf(“Não possui
tmp->dado);                     anterior.”);
                                    tmp= tmp->prox;
                                  }
                                }
Lista duplamente Ligada -
operações
Remove o elemento elem da lista L.
                                      else
LDL *remove(int elem, LDL *L){
                                         if((proxno == NULL)==0){
 LDL*tmp, *proxno, *noant;
                                               proxno->ant =NULL;
 tmp=L;
 while((tmp->prox ==NULL)==0){
                                               free(tmp);
   proxno = tmp->prox;
                                               L = proxno;
   noant = tmp->ant;                           return L;
   if((tmp->dado == elem)                }
     if((noant == NULL)==0)              else {
           if((proxno == NULL)==0){            L = NULL;
             noant->prox =proxno;              free(tmp);
             proxno->ant =noant;               return L;
             free(tmp);                  }
             return L;                else{
           }                                tmp = tmp -> prox;
           else{                      }
                 noant->prox=NULL;
                                         printf(“O elemento %d não se
                 free(tmp);
                 return L;
                                      encontra na listan”, elem);
           }                          }
Lista duplamente Ligada -
operações
Devolve a posição do elemento elem.

int *procura(int elem, LDL *L){
    LDL*tmp;
    int pos = 0;
    tmp=L;
    while((tmp->prox ==NULL)==0)
            if((tmp->dado) == elem){
               printf(“A posição do elemento %d é %dn”, elem, pos);
               return pos;
            }
            else{
                 pos++;
                 tmp = tmp->prox;
            }
  printf(“O elemento não se encontra na lista”);

}
Lista duplamente Ligada -
operações
Devolve o elemento anterior a elem.

void *anterior(int elem, LDL *L){
   LDL*tmp, *noant;

  tmp=L;
  while((tmp==NULL)==0)
        if((tmp->dado) == elem){
           noant = tmp->ant;
           printf(“O elemento anterior a %d é %dn”, elem, noant-
>dado);
        else
            printf(“Não existe anterior”);
        exit();
        }
        else
             tmp = tmp->prox;
  printf(“O elemento não se encontra na lista”);
}
Lista Circular Ligada
   As listas circulares são do tipo simples ou
    duplamente ligadas, em que o nó final da lista a
    ponta para o nó inicial. Este tipo de estrutura
    adapta-se a situações do tipo FIFO (First In First Out).
Fig. 1 Lista circular ligada ( lista simplesmente ligada)

                 dado           dado          dado           dado




Fig. 2 Lista circular ligada ( lista duplamente ligada)


             dado           dado          dado            dado
Lista circular Ligada -
operações
typedef struct no{                  3. Aloca e desaloca nó.
          int dado;
                                    int aloca(LCSL *lista, int valor){
          struct no *prox;
}NO, LCSL;
                                      LCSL *tmp;
NO cabeca;                             tmp= (LCSL)
                                    malloc(sizeof(no));
1. Inicializa a lista circular         if (tmp==NULL)
int inicializa(LCSL *cabeca)                  return 1; // Erro …
{                                       *lista = tmp;
      *cabeca=NULL;
                                        tmp->dado = valor;
      return 0;
}                                       tmp->prox = NULL;
2. Verifica se a lista está cheia       return 0;
                                    }
int vazia(LCSL lista){              void liberta(LCSL *lista){
      return (lista==NULL)? 1:0;        free(*lista);
}
                                        *lista=NULL;
                                    }
Lista circular Ligada -
operações
4. Inserção de um item               5. Adiciona um item ao fim da
int insere(LCSL *lista, int valor)   lista.
{                                    int adiciona(LCSL *lista, int
     LCSL tmp;                       valor){
     if (aloca(&tmp, valor)== 1)       LCSL tmp;
         return 1; // Erro             if (aloca(&tmp, valor)== 1)
     if (vazia(*lista) == 1){             return 1;             // Erro
     //True=1                          if (vazia(*lista) == 1) //True=1
                                          tmp->prox = tmp;
         tmp->prox = tmp;
         *lista=tmp;                   else{
     } else {                                tmp->prox = *lista->prox;
                                            *lista->prox = tmp;
        tmp->prox = *lista->prox;           }
        *lista->prox = tmp;            *lista = tmp;
       }                                return 0;               //Ok
     return 0;
}                                    }
Lista circular Ligada -
6. Eliminação de um nóoperações
int apagaNo(LCSL *lista, LCSL no)
{
    LCSL tmp;
    if(vazia(*lista) == 1)
         return 1;
    if(no==no->prox)
      *lista=NULL;
    else {
      for(tmp=*lista->prox; tmp!=*lista && tmp->prox!=no; tmp=tmp-
    >prox) ;
       if(tmp->prox !=no)
         return 1;
      tmp->prox = no->prox;
      if(no== *lista)
         *lista=tmp;
    }
    free(&no);
    return 0;
}
Pilha e Fila

1.    Pilha
     •     Implementação sequencial da pilha
2.    Fila
     I.    Operações básicas
     II.   Implementação sequencial da fila
Pilhas
 Pilhas são listas onde a inserção ou a
  remoção de um item é feita no topo.
 Definição:
  dada a pilha P= (a[1], a[2], …, a[n]),
  dizemos que a[1] é o elemento da base da
  pilha; a[n] é o elemento topo da pilha; e
  a[i+1] está acima de a[i].
 Pilhas são conhecidas como listas LIFO
  (last in first out)
Implementação de pilhas
   Como lista sequencial ou ligada?
    ◦ No caso geral de listas ordenadas, a maior
      vantagem da alocação dinâmica sobre a
      sequencial, se a memória não for problema, é
      a eliminação de deslocamento na inserção ou
      eliminação dos elementos. No caso das pilhas,
      essas operações de deslocamento não
      ocorrem. Portanto, alocação sequencial é mais
      vantajosa na maioria das vezes.
    ◦ Como o acesso a pilha é limitado ao último
      elemento inserido, todos os elementos do meio
      do vector devem estar preenchidos. Há
      desvantagens quando a quantidade de dados
      a ser armazenados não é conhecido
Pilha
Implementação Sequencial
#define MAXPILHA 50
#define tpilha(p) (p->topo – p->base)
typedef struct pilha{
     int base[MAXPILHA];
     int *topo;
}PILHA;
Implementação Sequencial
1.    Inicializa a pilha vazia
      int inicializa(PILHA *pp){
            pp->topo = pp->base;
            return 1;
      }

2.    Verifica se pilha está vazia
     int vazia(PILHA *pp){
             return (pp->topo == pp->base) ? 1 : 0;
     }

3.    Coloca dado na pilha. Apresenta erro se não haver espaço.
      int push(PILHA *pp, int dado){
           if(tpilha(pp) == MAXPILHA)
               return 0;
           *pp->topo=dado;
           pp->topo++;
           return 1;
      }
Implementação Sequencial
4.   Retira o valor do topo da pilha a atribui a dado.
     int pop (PILHA *pp, int dado){
          if(vazia(pp) == 1)
              return 0;
           pp->topo--;
          *dado = *pp->topo
          return 1;
     }

5.   Retorna o valor do topo da pilha se removê-lo.

     int topo(PILHA *pp, int *dado){
          if(pop(pp, dado) == 0)
             return 0;
          return push (pp, dado)
     }
Fila
   Uma Fila é uma estrutura de dados do
    tipo FIFO (First In First Out), cujo
    funcionamento é inspirado no de uma
    fila “natural”, na qual o primeiro
    elemento a ser inserido é sempre o
    primeiro elemento a ser retirado.
Fila
Uma fila tem por norma as seguintes
 funcionalidade:

   Colocar e retirar dados da fila:
    ◦ insere – guardar um elemento na fila
    ◦ remove – retirar um elemento da fila
    ◦ topo – retornar o elemento do topo da fila.
   Testar se a fila está vazia ou cheia:
    ◦ cheia – Verificar se a fila está cheia (não pode
      guardar mais elementos).
    ◦ vazia – Verificar se a fila está vazia (não contém
      elementos)
   Inicializar ou limpar:
    ◦ inicializa – Colocar a fila num estado “pronto” a ser
      utilizada
Fila – Outras Operações

 Buscar por elementos que coincidam com certo
  padrão (casamento de padrão)
 Ordenar uma lista
 Intercalar duas listas
 Concatenar duas listas
 Dividir uma lista em duas
 Fazer cópia da lista
Fila
   A implementação de uma fila pode ser
    efectuada através da utilização de
    diferentes estruturas de dados
    (vectores, listas ligadas, árvores, etc.).
    De seguida, apresenta-se duas
    implementação de uma fila através da
    utilização de vectores e listas ligadas.
Fila
   Características das filas:
    ◦ Os dados são armazenados pela ordem de
      entrada
   Tipos de filas:
    ◦ Filas de espera (queues)
      com duplo fim (deque “double-ended queue)
    ◦ Filas de espera com prioridades (priority
      queues)
   Implementação das filas:
    ◦ usando vectores/arrays (circulares ou não)
    ◦ utilizando um apontador para nós de
Fila
 Implementação em C usando arrays
 Vantagens:
    ◦ Facilidade de implementação.
   Desvantagens:
    ◦ Vectores possuem um espaço limitado para
      armazenamento de dados.
    ◦ Necessidade de definir um espaço grande o suficiente
      para a fila
    ◦ Necessidade de um indicador para o inicio e para o fim da
      fila
   Método de Implementação
    ◦ A adição de elementos à fila resulta no incremento do
      indicador do fim da fila
    ◦ A remoção de elementos da fila resulta no incremento do
      indicador do inicio da fila
Implementação de Filas em C
   Usamos o vector para armazenar os
    elementos da fila e duas variáveis, inic e fim,
    para armazenar as posições dentro do vector
    do primeiro e último elementos da fila:
    #define MAXFILA 100
    struct fila{
     int elem[MAXFILA];
     int inic, fim;
     }f;
   É evidente que usar vector para armazenar
    uma fila introduz a possibilidade de estouro,
    caso a fila fique maior que o tamanho do
    vector.
Capitulo III: Árvores




               Docente:
                          Dikiefu Fabiano, Msc
Sumário

3.1 Introdução
3.2 Tipos de árvores
3.3 Árvores binárias
3.3.1 Estrutura de uma árvore binária
3.3.2 Descrição
3.3.3 Altura
3.3.4 Representação em C
3.4 Árvores Genéricas
3.4.1 Estrutura de uma árvore genérica
3.4.2 Representação em C
3.4.3 Problemas com Representação
Introdução

   As estruturas anteriores são chamadas
    de unidimensionais (ou lineares)
    ◦ Exemplo são listas sequenciais e ligadas
   Não podem ser usadas como
    hierarquias.
    ◦ Exemplo: árvore de directórios
  Árvore é uma estrutura adequada para
  representar hierarquias
 A forma mais natural para definirmos
  uma estrutura de árvore é usando
Estrutura de Árvores
   Uma árvore é composta por um conjunto de
    nós.
    Existe um nó r, denominado raiz, que contém
    zero ou mais sub-árvores, cujas raízes são
    ligadas a r.
    Esses nós raízes das sub-árvores são ditos
    filhos do nó pai, no caso r.
    Nós com filhos são chamados nós internos e
    nós que não têm filhos são chamados folhas,
    ou nós externos.
    É tradicional desenhar as árvores com a raiz
    para cima e folhas para baixo, ao contrário do
    que seria de se esperar.
Estrutura de Árvores




   Por adoptarmos essa forma de representação gráfica,
    não representamos explicitamente a direcção dos
    ponteiros, subentendendo que eles apontam sempre do
    pai para os filhos.
Tipos de Árvores

 O número de filhos permitido por nó e as
  informações armazenadas em cada nó
  diferenciam os diversos tipos de árvores
  existentes.
 Existem dois tipos de árvores:
    ◦ árvores binárias, onde cada nó tem, no
      máximo, dois filhos.
    ◦ árvores genéricas, onde o número de filhos é
      indefinido.
   Estruturas recursivas serão usadas como
    base para o estudo e a implementação
    das operações com árvores.
Árvore Binária
 Um exemplo de utilização de árvores
 binárias está na avaliação de
 expressões.
 Como trabalhamos com operadores
 que esperam um ou dois operandos, os
 nós da árvore para representar uma
 expressão têm no máximo dois filhos.
 Nessa árvore, os nós folhas
 representam operandos e os nós
 internos operadores.
Árvores Binárias




Esta árvore representa a expressão:
    (3+6)*(4-1)+5
Estrutura de uma AB
 Numa árvore binária, cada nó tem
 zero, um ou dois filhos.
 De maneira recursiva, podemos
 definir uma árvore binária como
 sendo:
    ◦ uma árvore vazia; ou
    ◦ um nó raiz tendo duas sub-árvores,
      identificadas como a sub-árvore da direita
      (sad) e a sub-árvore da esquerda (sae).
Estrutura de uma AB
         • Os nós a, b, c, d, e, f formam
           uma árvore binária da seguinte
           maneira:
         • A árvore é composta do nó a,
           da subárvore à esquerda
           formada por b e d, e da sub-
           árvore à direita formada por c,
           e e f.
         • O nó a representa a raiz da
           árvore e os nós b e c as raízes
           das sub-árvores.
         • Finalmente, os nós d, e e f
           são folhas da árvore.
Descrição de AB
    Para descrever árvores binárias,
    podemos usar a seguinte notação
    textual:
    ◦ A árvore vazia é representada por <>,
    ◦ e árvores não vazias por <raiz sae sad>.
   Com essa notação, a árvore da
    Figura anterior é representada por:
    ◦ <a<b<><d<><>>> <c<e<><>><f<><>>>>.
Descrição de AB
  Uma sub-árvore de uma árvore
  binária é sempre especificada como
  sendo a sae ou a sad de uma árvore
  maior, e qualquer das duas sub-
  árvores pode ser vazia.
 As duas árvores da Figura abaixo são
  distintas.
Altura de uma AB
  Uma propriedade fundamental de todas
  as árvores é que só existe um caminho
  da raiz para qualquer nó.
 Podemos definir a altura de uma árvore
  como sendo o comprimento do caminho
  mais longo da raiz até uma das folhas.
 A altura de uma árvore com um único nó
  raiz é zero e, por conseguinte, dizemos
  que a altura de uma árvore vazia é
  negativa e vale -1.
Representação em C
   Podemos definir um tipo para representar
    uma árvore binária.
    Vamos considerar que a informação a ser
    armazenada são valores de caracteres
    simples.
    Vamos inicialmente discutir como podemos
    representar uma estrutura de árvore binária
    em C.
    Que estrutura podemos usar para
    representar um nó da árvore?
    Cada nó deve armazenar três informações:
    a informação propriamente dita, no caso um
    caractere, e dois ponteiros para as sub-
    árvores, à esquerda e à direita.
Representação em C
   struct arv {
     char info;
     struct arv* esq;
     struct arv* dir;
   };
   typedef struct arv Arv;

Da mesma forma que uma lista encadeada é
representada por um ponteiro para o
primeiro nó, a estrutura da árvore como um
todo é representada por um ponteiro para o
nó raiz.
Criando Árvores
 Arv* inicializa(void)
 {
   return NULL;
 }
 Arv* cria(char c, Arv* sae, Arv* sad)
 {
     Arv* p=(Arv*)malloc(sizeof(Arv));
     p->info = c;
     p->esq = sae;
     p->dir = sad;
     return p;
 }
Árvore Vazia

     int vazia(Arv* a)
     {
       return a==NULL;
     }
Exemplo
   Exemplo: Usando as operações
    inicializa e cria, crie uma estrutura que
    represente a seguinte árvore.
Exemplo
/* sub-árvore com 'd' */
Arv* a1= cria('d',inicializa(),inicializa());
/* sub-árvore com 'b' */
Arv* a2= cria('b',inicializa(),a1);
/* sub-árvore com 'e' */
Arv* a3= cria('e',inicializa(),inicializa());
/* sub-árvore com 'f' */
Arv* a4= cria('f',inicializa(),inicializa());
/* sub-árvore com 'c' */
Arv* a5= cria('c',a3,a4);
/* árvore com raiz 'a'*/
Arv* a = cria('a',a2,a5 );
Exemplo



Alternativamente, a árvore poderia
  ser criada
recursivamente com uma única
  atribuição,
seguindo a sua estrutura.
Como isso pode ser feito?
Exemplo
Arv* a = cria('a',
            cria('b',
                   inicializa(),
                   cria('d', inicializa(), inicializa())
                   ),
            cria('c',
                   cria('e', inicializa(),
 inicializa()),
                   cria('f', inicializa(), inicializa())
                   )
            );
Exibe Conteúdo da Árvore
void imprime (Arv* a)
{
  if (!vazia(a)){
       printf("%c ", a->info); /* mostra raiz */
       imprime(a->esq); /* mostra sae */
       imprime(a->dir); /* mostra sad */
       }
}
Como é chamada essa forma de exibição?
E para exibir na forma in-fixada? E na pós-
  fixada?
Liberando Memória
Arv* libera (Arv* a){
  if (!vazia(a)){
      libera(a->esq); /* libera sae */
      libera(a->dir); /* libera sad */
      free(a); /* libera raiz */
  }
  return NULL;
}
Criação e Liberação
Vale a pena notar que a definição de
árvore, por ser recursiva, não faz
distinção entre árvores e sub-árvores.
Assim, cria pode ser usada para
acrescentar uma sub-árvore em um
ramo de uma árvore, e libera pode ser
usada para remover uma sub-árvore
qualquer de uma árvore dada.
Criação e Liberação
Considerando a criação da árvore feita
  anteriormente,
podemos acrescentar alguns nós, com:
  a->esq->esq = cria('x',
      cria('y',inicializa(),inicializa()),
      cria('z',inicializa(),inicializa())
  );
E podemos liberar alguns outros, com:
  a->dir->esq = libera(a->dir->esq);
Deixamos como exercício a verificação do
  resultado
final dessas operações.
Buscando um Elemento
   Essa função tem como retorno um
    valor booleano (um ou zero)
    indicando a ocorrência ou não do
    carácter na árvore.

    int busca (Arv* a, char c){
      if (vazia(a))
         return 0; /* não encontrou */
      else
         return a->info==c ||busca(a->esq,c)
      ||busca(a->dir,c);
    }
Buscando um Elemento
   Podemos dizer que a expressão:
return c==a->info || busca(a->esq,c) ||
  busca(a->dir,c);
é equivalente a:
    if (c==a->info)
      return 1;
    else if (busca(a->esq,c))
      return 1;
    else
      return busca(a->dir,c);
Árvores Genéricas
   Como vimos, numa árvore binária o
    número de filhos dos nós é limitado em
    no máximo dois.
   No caso da árvore genérica, esta
    restrição não existe.
   Cada nó pode ter um número arbitrário
    de filhos.
   Essa estrutura deve ser usada, por
    exemplo, para representar uma árvore
    de directórios.
   Supor que não há árvore vazia.
Exemplo
Representação em C
 Devemos levar em consideração o
  número de filhos que cada nó pode
  apresentar.
 Se soubermos que numa aplicação o
  número máximo de filhos é 3, podemos
  montar uma estrutura com 3 campos
  apontadores, digamos, f1, f2 e f3.
 Os campos não utilizados podem ser
  preenchidos com o valor nulo NULL,
  sendo sempre utilizados os campos em
  ordem.
 Assim, se o nó n tem 2 filhos, os
  campos f1 e f2 seriam utilizados, nessa
  ordem, ficando f3 vazio.
Representação em C
Prevendo um número máximo de filhos
igual a 3,
e considerando a implementação de
árvores para
armazenar valores de caracteres simples,
a declaração do tipo que representa o nó
da árvore poderia ser:
         struct arv3 {
         char val;
         struct arv3 *f1, *f2, *f3;
         };
Exemplo
Problemas com
Representação
 Como se pode ver no exemplo, em cada um dos
  nós que tem menos de três filhos, o espaço
  correspondente aos filhos inexistentes é
  desperdiçado.
 Além disso, se não existe um limite superior no
  número de filhos, esta técnica pode não ser
  aplicável.
 O mesmo acontece se existe um limite no
  número de nós, mas esse limite será raramente
  alcançado, pois estaríamos tendo um grande
  desperdício de espaço de memória com os
  campos não utilizados.
 Há alguma solução para contornar tal
  problema?
Solução
Uma solução que leva a um aproveitamento
  melhor do
espaço utiliza uma “lista de filhos”: um nó aponta
  apenas
para seu primeiro (prim) filho, e cada um de seus
  filhos,
excepto o último, aponta para o próximo (prox)
  irmão.
A declaração de um nó pode ser:
  struct arvgen {
     char info;
     struct arvgen *prim;
     struct arvgen *prox;
  };
  typedef struct arvgen ArvGen;
Exemplo da Solução
Exemplo da Solução
 Uma das vantagens dessa representação
  é que podemos percorrer os filhos de um
  nó de forma sistemática, de maneira
  análoga ao que fizemos para percorrer os
  nós de uma lista simples.
 Com o uso dessa representação, a
  generalização da árvore é apenas
  conceitual, pois, concretamente, a árvore
  foi transformada em uma árvore binária,
  com filhos esquerdos apontados por prim
  e direitos apontados por prox.
Criação de uma ÁrvoreGen
ArvGen* cria (char c)
{
  ArvGen *a =(ArvGen
  *)malloc(sizeof(ArvGen));
  a->info = c;
  a->prim = NULL;
  a->prox = NULL;
  return a;
}
Inserção
 Como não vamos atribuir nenhum significado
  especial para a posição de um nó filho, a
  operação de inserção pode inserir a sub-
  árvore em qualquer posição.
 Vamos optar por inserir sempre no início da
  lista que, como já vimos, é a maneira mais
  simples de inserir um novo elemento numa
  lista ligada.

    void insere (ArvGen* a, ArvGen* f)
    {
      f->prox = a->prim;
      a->prim = f;
    }
Exemplo Criação ÁrvoreGen
   Com as funções cria e insere como
    construir a árvore abaixo?
Exemplo Criação ÁrvoreGen
 /* cria nós como folhas */
 ArvGen* a = cria('a');       /* monta a hierarquia */
 ArvGen* b = cria('b');       insere(c,d);
 ArvGen* c = cria('c');       insere(b,e);
 ArvGen* d = cria('d');       insere(b,c);
 ArvGen* e = cria('e');       insere(i,j);
 ArvGen* f = cria('f');       insere(g,i);
 ArvGen* g = cria('g');       insere(g,h);
 ArvGen* h = cria('h');       insere(a,g);
 ArvGen* i = cria('i');       insere(a,f);
 ArvGen* j = cria('j');       insere(a,b);
 ...
Impressão (pré-ordem)
void imprime (ArvGen* a)
{
  ArvGen* p;
  printf("%cn",a->info);
  for (p=a->prim; p!=NULL; p=p-
  >prox)
     imprime(p);
}
Busca de Elemento
int busca (ArvGen* a, char c)
{
ArvGen* p;
  if (a->info==c)
        return 1;
  else {
        for (p=a->prim; p!=NULL; p=p->prox) {
               if (busca(p,c))
                      return 1;
        }
  }
  return 0;
}
Liberação de Memória
O único cuidado que precisamos
tomar na programação dessa função
é a de liberar as subárvores antes
de liberar o espaço associado a um
nó (isto é, usar pós-ordem).
Liberação de Memória
   void libera (ArvGen* a){
     ArvGen* p = a->prim;
     while (p!=NULL) {
         ArvGen* t = p->prox;
         libera(p);
         p = t;
         }
         free(a);
   }
Capitulo IV - Ordenação




        Docente: Dikiefu Fabiano, Msc
Sumário

4. Ordenação Interna
   4.1 Ordenação por Selecção
   4.2 Ordenação por Inserção
   4.3 Shellsort
   4.4        Quicksort
   4.5 Heapsort




                                121
Conceitos Básicos
     Ordenar: processo de rearranjar um conjunto de
      objectos em uma ordem ascendente ou descendente.
     A ordenação visa facilitar a recuperação posterior de
      itens do conjunto ordenado.
     A maioria dos métodos de ordenação é baseada em
      comparações das chaves.
     Existem métodos de ordenação que utilizam o princípio
      da distribuição.

.




                                                        122
Conceitos Básicos
 Notação utilizada nos algoritmos:
• Os algoritmos trabalham sobre os
  registos de um arquivo.
• Cada registo possui uma chave
  utilizada para controlar a ordenação.
• Podem existir outros componentes
  em um registo
Conceitos básicos

Um método de ordenação é estável se a ordem
relativa dos itens com chaves iguais não se altera
durante a ordenação.
• Alguns dos métodos de ordenação mais eficientes
não são estáveis.
• A estabilidade pode ser forçada quando o método é
não-estável.
• Sedgewick (1988) sugere agregar um pequeno índice
a cada chave antes de ordenar, ou então aumentar a
chave de alguma outra forma.



                                                      124
Classificação dos métodos de
ordenação
1. Ordenação interna: arquivo a ser ordenado cabe todo
   na memória principal.
2. Ordenação externa: arquivo a ser ordenado não cabe
   na memória principal.

Diferenças entre os métodos:
 Em um método de ordenação interna, qualquer
   registo pode ser imediatamente cessado.
 Em um método de ordenação externa, os registos
   são cessados sequencialmente ou em grandes blocos.



                                                    125
Classificação dos métodos de ordenação
interna
 Métodos simples:
   • Adequados para pequenos arquivos.
   • Produzem programas pequenos.
 Métodos eficientes:
   • Adequados para arquivos maiores.
   • Usam menos comparações.
   • As comparações são mais complexas nos
     detalhes.
 Métodos simples são mais eficientes para pequenos
  arquivos.



                                                  126
Ordenação por Selecção
Um dos algoritmos mais simples de ordenação.

 Algoritmo:
  Seleccione o menor item do vector.
  Troque-o com o item da primeira posição do vector.
  Repita essas duas operações com os n − 1 itens restantes, depois
   com os n − 2 itens, até que reste apenas um elemento.




• As chaves em negrito sofreram uma troca entre si.
                                                                      127
Ordenação por Selecção
Vantagens:
  Custo linear no tamanho da entrada para o número
   de movimentos de registos.
  É o algoritmo a ser utilizado para arquivos com
   registos muito grandes.
  É muito interessante para arquivos pequenos.

Desvantagens:
  O facto de o arquivo já estar ordenado não ajuda em
   nada, pois o custo continua quadrático.
  O algoritmo não é estável.



                                                         128
Ordenação por Selecção
void selectionSort(int vector[],int tam) {
  int i, j, min, aux;
  for(i=0; i<tam-1; i++) {
       min = i;
       aux = vector[i];
       for(j=i+1; j<tam; j++) {
        if (vector[j] < aux) {
            min=j; aux=vector[j];
        }
       }
      aux = vector[i];
      vector[i] = vector[min];
      vector[min] = aux;
  }
}
                                             129
Código da ordenação SelectionSort com strings
void ordenar_seleccao() {
  int j;
  for(i=0; i<n-1; i++) {
    for(j=i+1; j<n; j++) {
         if(strcmpi(vector[i], vector[j])>0) {
                strcpy(aux_char, vector[i]);
                strcpy(vector[i], vector[j]);
                strcpy(vector[j], aux_char);
          }
     }
  }
}
Bubble Sort
   O bubble sort, ou ordenação por
    flutuação (literalmente "por bolha"), é
    um algoritmo de ordenação dos mais
    simples. A ideia é percorrer o vector
    diversas vezes, a cada passagem
    fazendo flutuar para o topo o maior
    elemento da sequência. Essa
    movimentação lembra a forma como
    as bolhas em um tanque de água
    procuram seu próprio nível, e disso
    vem o nome do algoritmo
void bubble( int v[], int qtd ) {
  int i; int j, aux, k = qtd - 1 ;
  for(i = 0; i < qtd; i++) {
       for(j = 0; j < k; j++) {
          if(v[j] > v[j+1]) {
               aux = v[j];
               v[j] = v[j+1];
               v[j+1]=aux;
          }
       }
  }
}
void swapbubble( int v[], int i ) {
  aux = v[i];
  v[i] = v[i+1];
  v[i+1]=aux;
}
void bubble( int v[], int qtd ) {
  int i,j;
  for( j = 0; j < qtd; j++ ) {
         for( i = 0; i < qtd - 1; i++ ) {
            if( v[i] > v[i+1] ) {
                   swapbubble( v, i );
            }
         }
  }
}
  Método de ordenação Bolha com ordenação de strings.
void bubble(int v[], int qtd){
   int i, trocou;
   char aux;
   do {
          qtd--;
          trocou = 0;
          for(i = 0; i < qtd; i++)
              if(strcasecmp(v[i],v[i + 1])>0) {
                     strcpy(aux, v[i]);
                     strcpy(v[i], v[i + 1]);
                     strcpy(v[i + 1], aux);
                     trocou = 1;
          }
    }while(trocou==1);
}
Ordenação por Inserção
 Método preferido dos jogadores de cartas.
 Algoritmo:
   • Em cada passo a partir de i =2 faça:
        • Seleccione o i-ésimo item da sequência fonte.
        • Coloque-o no lugar apropriado na sequência destino de
          acordo com o critério de ordenação.




• As chaves em negrito representam a sequência destino.


                                                                  135
Ordenação por Inserção
void insertionSort(int vector[], int tam) {
        int i, j; int key;
        for (j = 1; j < tam; ++j) {
             key = vector[j];
             i = j - 1;
            while (vector[i] > key && i >= 0) {
                    vector[i+1] = vector[i];
                    --i;
            }
               vector[i+1] = key;
        }
 }
A colocação do item no seu lugar apropriado na
sequência destino é realizada movendo-se itens com
chaves maiores para a direita e então inserindo o item
na posição deixada vazia.
                                                         136
Ordenação por Inserção
Considerações sobre o algoritmo:
  O processo de ordenação pode ser
   terminado pelas condições:
    • Um item com chave menor que o item
      em consideração é encontrado.
    • O final da sequência destino é atingido à
      esquerda.
  Solução:
    • Utilizar um registo sentinela na posição
      zero do vector.


                                                  137
Ordenação por Inserção
 O número mínimo de comparações e movimentos
  ocorre quando os itens estão originalmente em
  ordem.
 O número máximo ocorre quando os itens estão
  originalmente na ordem reversa.
 É o método a ser utilizado quando o arquivo está
  “quase” ordenado.
 É um bom método quando se deseja adicionar
  uns poucos itens a um arquivo ordenado, pois o
  custo é linear.
 O algoritmo de ordenação por inserção é estável.


                                                     138
Shellsort
Proposto por Shell em 1959.
  É uma extensão do algoritmo de ordenação por
   inserção.
  Problema com o algoritmo de ordenação por
   inserção:
    • Troca itens adjacentes para determinar o ponto
      de inserção.
    • São efectuadas n − 1 comparações e
      movimentações quando o menor item está na
      posição mais à direita no vector.
  O método de Shell contorna este problema
   permitindo trocas de registos distantes um do
   outro.
                                                       139
Shellsort
  Os itens separados de h posições são
   rearranjados.
  Todo h-ésimo item leva a uma sequência
   ordenada.
  Tal sequência é dita estar h-ordenada.
  Exemplo de utilização:




• Quando h = 1 Shellsort corresponde ao algoritmo de   140
Shellsort
• Como escolher o valor de h:
   Sequência para h:
    h(s) = 3h(s − 1) + 1, para s > 1
    h(s) = 1, para s = 1.
   Knuth (1973, p. 95) mostrou experimentalmente
    que esta sequência é difícil de ser batida por mais
    de 20% em eficiência.
   A sequência para h corresponde a 1, 4, 13, 40, 121,
    364, 1.093, 3.280, . . .




                                                          141
Shellsort
void shellsort ( Int v [ ] , int n) {
  int i,j,x,h = 1;
  do h = h *3 + 1;
  while (h < n) ;
  do {
            h /= 3;
            for ( i = h + 1; i <= n; i ++) {
                 x = v[ i ] ; j = i ;
                 while (v[ j − h] > x > 0) {
                 v[ j ] = v[ j − h] ; j −= h;
                    i f ( j <= h) break;
                 }
                 v[ j ] = x;
            }
  } while (h != 1) ;
}
   A implementação do Shellsort não utiliza registos
    sentinelas.
   Seriam necessários h registos sentinelas, uma para
    cada      h-ordenação.                               142
Shellsort
 Vantagens:
   • Shellsort é uma óptima opção para
     arquivos de tamanho moderado.
   • Sua implementação é simples e requer uma
     quantidade de código pequena.
 Desvantagens:
   • O tempo de execução do algoritmo é
     sensível à ordem inicial do arquivo.
   • O método não é estável,




                                                143
Quicksort
 Proposto por Hoare em 1960 e publicado em
  1962.
 É o algoritmo de ordenação interna mais rápido
  que se conhece para uma ampla variedade de
  situações.
 Provavelmente é o mais utilizado.
 A ideia básica é dividir o problema de ordenar um
  conjunto com n itens em dois problemas menores.
 Os problemas menores são ordenados
  independentemente.
 Os resultados são combinados para produzir a
  solução final.

                                                      144
Quicksort
 A parte mais delicada do método é relativa ao método
  partição.
 O vector v[esq..dir ] é rearranjado por meio da escolha
  arbitrária de um pivô x.
 O vector v é particionado em duas partes:
   • A parte esquerda com chaves menores ou
     iguais a x.
   • A parte direita com chaves maiores ou iguais a
     x.




                                                       145
Quicksort
Algoritmo para o particionamento:
1. Escolha arbitrariamente um pivô x.
2. Percorra o vector a partir da esquerda até que v[i]
   x.
3. Percorra o vector a partir da direita até que v[j] x.
4. Troque v[i] com v[j].
5. Continue este processo até os apontadores i e j se
   cruzarem.
    • Ao final, o vector v[esq..dir ] está particionado de
      tal forma que:
       • Os itens em v[esq], v[esq + 1], . . . , v[j] são
           menores ou iguais a x.
       • Os itens em v[i], v[i + 1], . . . , v[dir ] são
           maiores ou iguais a x.
                                                             146
Quicksort
• Ilustração do processo de partição:




  O pivô x é escolhido como sendo v[(i+j) / 2].
  Como inicialmente i = 1 e j = 6, então x = v[3] = D.
  Ao final do processo de partição i e j se cruzam em i =
   3e       j = 2.

                                                        147
Quicksort
void swap(int* a, int* b) {
      int tmp;
     tmp = *a;
      *a = *b;
     *b = tmp;
}
int partition(int vec[], int left, int right) {
           int i, j;
           i = left;
           for (j = left + 1; j <= right; ++j) {
                     if (vec[j] < vec[left]) {
                                ++i;
                                swap(&vec[i], &vec[j]);
                     }
           }
           swap(&vec[left], &vec[i]);
           return i;
 }
                                                          148
Quicksort
Método ordena e algoritmo Quicksort :

void quickSort(int vec[], int left, int right) {
   int r;
   if (right > left) {
          r = partition(vec, left, right);
          quickSort(vec, left, r - 1);
          quickSort(vec, r + 1, right);
  }
}




                                                   149
  Implementação usando 'fat pivot':
void sort(int array[], int begin, int end) {
  int pivot = array[begin];
  int i = begin + 1, j = end, k = end, t;
  while (i < j) {
    if (array[i] < pivot) i++;
    else if (array[i] > pivot) {
       j--;
       k--;
       t = array[i];
       array[i] = array[j];
       array[j] = array[k];
       array[k] = t;
} else {
          j--;
swap(array[i], array[j]);
}
    }
i--;
swap(array[begin], array[i]);
if (i - begin > 1)
sort(array, begin, i);
if (end - k > 1)
sort(array, k, end);
}
Lembrando que quando você for chamar
a função recursiva terá que chamar a
mesma da seguinte forma
ordenar_quicksort(0,n-1). O 0(zero)
serve para o início receber a posição
zero do vector e o fim será o tamanho
do vector -1.
void ordenar_quicksort(int ini, int fim) {
    int i = ini, f = fim;
          char pivo[50];
              strcpy(pivo,vector[(ini+fim)/2]);
          if (i<=f) {
                     while (strcmpi(vector[i],pivo)<0)
                     i++;
                     while (strcmpi(vetor[f],pivo)>0)
                      f--;
                               if (i<=f) {
                                    strcpy (aux_char,vetor[i]);
                                    strcpy (vetor[i],vetor[f]);
                                    strcpy (vetor[f],aux_char);
                                    i++; f--;
                              }
          }
if (f>ini)
        ordenar_quicksort_nome(ini,f);
    if (i<fim)
        ordenar_quicksort_nome(i,fim);
}
Quicksort
Exemplo do estado do vector em cada chamada
recursiva do procedimento Ordena:




• O pivô é mostrado em negrito.

                                              155
Quicksort
 Vantagens:
   • É extremamente eficiente para ordenar
     arquivos de dados.
   • Necessita de apenas uma pequena pilha como
     memória auxiliar.
 Desvantagens:
   • Sua implementação é muito delicada e difícil:
      Um pequeno engano pode levar a efeitos
     inesperados para algumas entradas de dados.
   • O método não é estável.




                                                     156
Heapsort
 Possui o mesmo princípio de funcionamento da
  ordenação por selecção.
 Algoritmo:
   1. Seleccione o menor item do vector.
   2. Troque-o com o item da primeira posição do
      vector.
   3. Repita estas operações com os n − 1 itens
      restantes, depois com os n − 2 itens, e assim
      sucessivamente.
 O custo para encontrar o menor (ou o maior) item
  entre n itens é n − 1 comparações.
 Isso pode ser reduzido utilizando uma fila de
  prioridades.

                                                      157
Heaps
 É uma sequência de itens com chaves c[1], c[2], . . .
  , c[n], tal que:
          c[i] c[2i],
          c[i] c[2i + 1], para todo i = 1, 2, . . . , n/2.
 A definição pode ser facilmente visualizada em uma
  árvore binária completa:

 árvore binária completa:
  • Os nós são numerados de 1 a n.
  • O primeiro nó é chamado raiz.
  • O nó k/2 é o pai do nó k, para 1 < k n.
  • Os nós 2k e 2k + 1 são os filhos à esquerda e à
    direita do nó k, para 1 k k/2

                                                         158
Heaps

 As chaves na árvore satisfazem a condição do
  heap.
 As chaves em cada nó são maiores do que as
  chaves em seus filhos.
 A chave no nó raiz é a maior chave do conjunto.
 Uma árvore binária completa pode ser
  representada por um arranjo:



   A representação é extremamente compacta.
   Permite caminhar pelos nós da árvore facilmente.
   Os filhos de um nó i estão nas posições 2i e 2i + 1.
   O pai de um nó i está na posição i/2.                  159
Heaps

 Na representação do heap em um arranjo, a
  maior chave está sempre na posição 1 do vector.
 Os algoritmos para implementar as operações
  sobre o heap operam ao longo de um dos
  percursos da árvore.
 Um algoritmo elegante para construir o heap foi
  proposto por Floyd em 1964.
 O algoritmo não necessita de nenhuma memória
  auxiliar.
 Dado um vector v[1], v[2], . . . , v[n].
 Os itens v[n/2 + 1], v[n/2 + 2], . . . , v[n] formam um
  heap:
   • Neste intervalo não existem dois índices i e j
     tais que      j = 2i ou j = 2i + 1.                    160
Heapsort
void heapsort(tipo a[], int n) {
  int i = n/2, pai, filho;
  tipo t;
  for (;;) {
        if (i > 0) {
            i--;
            t = a[i];
        } else {
            n--;
            if (n == 0)
                  return;
            t = a[n];
            a[n] = a[0];
        }
                                   161
pai = i;
    filho = i*2 + 1;
    while (filho < n) {
        if ((filho + 1 < n) && (a[filho + 1] >
a[filho]))
                 filho++;
        if (a[filho] > t) {
                 a[pai] = a[filho];
                 pai = filho;
                 filho = pai*2 + 1;
        } else break;
    }
    a[pai] = t;
   }
}                                                162
Heapsort
Algoritmo:




    Os itens de v[4] a v[7] formam um heap.

    O heap é estendido para a esquerda (esq = 3), englobando o item
     v[3], pai dos itens v[6] e v[7].
    A condição de heap é violada:
      • O heap é refeito trocando os itens D e S.
    O item R é incluindo no heap (esq = 2), o que não viola a condição de
     heap.
    O item O é incluindo no heap (esq = 1).
    A Condição de heap violada:
      • O heap é refeito trocando os itens O e S, encerrando o
           processo.
                                                                             163
Heapsort
Exemplo da operação de aumentar o valor da chave do item na posição i:




O tempo de execução do procedimento AumentaChave em um item do
heap é O(log n).
                                                                         164
Construção de Heap
Algoritmo:

1. Construir o heap.
2. Troque o item na posição 1 do vector (raiz do heap)
   com o item da posição n.
3. Use o procedimento Refaz para reconstituir o heap
   para os itens v[1], v[2], . . . , v[n − 1].
4. Repita os passos 2 e 3 com os n − 1 itens restantes,
   depois com os n − 2, até que reste apenas um item.
Heapsort
Exemplo de aplicação do Heapsort :

         1   2   3 4 5        6 7
         S   R    O E N       A D
         R   N    O E D       A S
         O   N    A E D       R
         N   E   A D O
         E   D   A N
         D   A   E
         A   D

•   O caminho seguido pelo procedimento Refaz para reconstituir a
    condição do heap está em negrito.
•   Por exemplo, após a troca dos itens S e D na segunda linha da
    Figura, o item D volta para a posição 5, após passar pelas posições 1
    e 2.
Referências
1. Levitin, Anany “Introduction to the design and analysis of algorithm”
   Addison-Wesley 2003
2. Pedro Neto, João “Programação, algoritmo e estrutura de dados”
   escola editora 2008
3. Damas, Luís “Programação em C” …
4. Ziviani, Nivio e Botelho, Fabiano Cupertino, “Projecto de Algoritmos
   com implementação em Pascal e C” editora Thomson, 2007
5. http://www.icmc.usp.br/~sce182/arvore.html
6. http://w3.ualg.pt/~hshah/ped
7. http://www.liv.ic.unicamp.br/~bergo/mc202/

Mais conteúdo relacionado

Mais procurados

Aula 06 vetores e matrizes
Aula 06   vetores e matrizesAula 06   vetores e matrizes
Aula 06 vetores e matrizes
Tácito Graça
 
Apresentação recursividade rev2
Apresentação recursividade rev2Apresentação recursividade rev2
Apresentação recursividade rev2
Rogerio Oliveira
 
Programando em python funcoes
Programando em python   funcoesProgramando em python   funcoes
Programando em python funcoes
samuelthiago
 
Python para quem sabe Python (aula 2)
Python para quem sabe Python (aula 2)Python para quem sabe Python (aula 2)
Python para quem sabe Python (aula 2)
Luciano Ramalho
 

Mais procurados (17)

Aula 06 vetores e matrizes
Aula 06   vetores e matrizesAula 06   vetores e matrizes
Aula 06 vetores e matrizes
 
Apresentação recursividade rev2
Apresentação recursividade rev2Apresentação recursividade rev2
Apresentação recursividade rev2
 
Linguagem C - Funções e ponteiros
Linguagem C - Funções e ponteiros Linguagem C - Funções e ponteiros
Linguagem C - Funções e ponteiros
 
Linguagem C - Vetores, Matrizes e Funções
Linguagem C - Vetores, Matrizes e FunçõesLinguagem C - Vetores, Matrizes e Funções
Linguagem C - Vetores, Matrizes e Funções
 
Linguagem C - Strings
Linguagem C - StringsLinguagem C - Strings
Linguagem C - Strings
 
Vetores, Matrizes e Strings em C Parte 3
Vetores, Matrizes e Strings em C Parte 3Vetores, Matrizes e Strings em C Parte 3
Vetores, Matrizes e Strings em C Parte 3
 
Standard ML / CPN ML
Standard ML / CPN MLStandard ML / CPN ML
Standard ML / CPN ML
 
Haskell aula2 tipos-e-classes
Haskell aula2 tipos-e-classesHaskell aula2 tipos-e-classes
Haskell aula2 tipos-e-classes
 
Trabalho vetores
Trabalho vetoresTrabalho vetores
Trabalho vetores
 
Aula c++ estruturas de dados
Aula c++   estruturas de dadosAula c++   estruturas de dados
Aula c++ estruturas de dados
 
Programando em python funcoes
Programando em python   funcoesProgramando em python   funcoes
Programando em python funcoes
 
Python para quem sabe Python (aula 2)
Python para quem sabe Python (aula 2)Python para quem sabe Python (aula 2)
Python para quem sabe Python (aula 2)
 
Orientação a Objetos em Python
Orientação a Objetos em PythonOrientação a Objetos em Python
Orientação a Objetos em Python
 
Estruturas
EstruturasEstruturas
Estruturas
 
Estruturação de Linguagens de Programação (Pascal e C++)
Estruturação de Linguagens de Programação (Pascal e C++)Estruturação de Linguagens de Programação (Pascal e C++)
Estruturação de Linguagens de Programação (Pascal e C++)
 
Linguagem R
Linguagem RLinguagem R
Linguagem R
 
Linguagem C 09 Ponteiros
Linguagem C 09 PonteirosLinguagem C 09 Ponteiros
Linguagem C 09 Ponteiros
 

Destaque

Cap16 Arquivos Slides
Cap16 Arquivos SlidesCap16 Arquivos Slides
Cap16 Arquivos Slides
ramongtx
 
Matéria de apoio (Base de dados)
Matéria de apoio  (Base de dados)Matéria de apoio  (Base de dados)
Matéria de apoio (Base de dados)
André Silva
 

Destaque (20)

Cap16 Arquivos Slides
Cap16 Arquivos SlidesCap16 Arquivos Slides
Cap16 Arquivos Slides
 
Pged 06
Pged 06Pged 06
Pged 06
 
Estrutura de dados
Estrutura de dadosEstrutura de dados
Estrutura de dados
 
Amplificador de potencia
Amplificador de potenciaAmplificador de potencia
Amplificador de potencia
 
Aula02 Pilhas
Aula02   PilhasAula02   Pilhas
Aula02 Pilhas
 
Aula03 Filas
Aula03   FilasAula03   Filas
Aula03 Filas
 
Aula01 - estrutura de dados
Aula01 - estrutura de dadosAula01 - estrutura de dados
Aula01 - estrutura de dados
 
Árvore b+ pesquisa e ordenação
Árvore b+ pesquisa e ordenaçãoÁrvore b+ pesquisa e ordenação
Árvore b+ pesquisa e ordenação
 
Aula 08 - árvores
Aula 08 - árvoresAula 08 - árvores
Aula 08 - árvores
 
Pilhas e Filas
Pilhas e FilasPilhas e Filas
Pilhas e Filas
 
Pilha e Fila Estática
Pilha e Fila EstáticaPilha e Fila Estática
Pilha e Fila Estática
 
Árvores balanceadas - AVL
Árvores balanceadas - AVLÁrvores balanceadas - AVL
Árvores balanceadas - AVL
 
Matéria de apoio (Base de dados)
Matéria de apoio  (Base de dados)Matéria de apoio  (Base de dados)
Matéria de apoio (Base de dados)
 
Pilha e Fila Dinamica
Pilha e Fila DinamicaPilha e Fila Dinamica
Pilha e Fila Dinamica
 
Estrutura de dados - Filas
Estrutura de dados - FilasEstrutura de dados - Filas
Estrutura de dados - Filas
 
Estrutura de Dados Aula 05 - Filas Estáticas
Estrutura de Dados  Aula 05 - Filas EstáticasEstrutura de Dados  Aula 05 - Filas Estáticas
Estrutura de Dados Aula 05 - Filas Estáticas
 
Teoria das Filas
Teoria das FilasTeoria das Filas
Teoria das Filas
 
Estrutura de dados - Pilhas
Estrutura de dados - PilhasEstrutura de dados - Pilhas
Estrutura de dados - Pilhas
 
Estrutura de dados em Java - Filas
Estrutura de dados em Java - Filas Estrutura de dados em Java - Filas
Estrutura de dados em Java - Filas
 
Estrutura de Dados Aula 07 - Alocação dinâmica de memória
Estrutura de Dados Aula 07 - Alocação dinâmica de memóriaEstrutura de Dados Aula 07 - Alocação dinâmica de memória
Estrutura de Dados Aula 07 - Alocação dinâmica de memória
 

Semelhante a Ed1

Estrutura de dados
Estrutura de dadosEstrutura de dados
Estrutura de dados
gjpbg
 
Introdução a Linguagem C
Introdução a Linguagem CIntrodução a Linguagem C
Introdução a Linguagem C
apolllorj
 

Semelhante a Ed1 (20)

Estrutura de dados
Estrutura de dadosEstrutura de dados
Estrutura de dados
 
Estrutura de Dados - Aula 03 - Ponteiros e Funções
Estrutura de Dados - Aula 03 - Ponteiros e FunçõesEstrutura de Dados - Aula 03 - Ponteiros e Funções
Estrutura de Dados - Aula 03 - Ponteiros e Funções
 
Linguagem_C.pdf
Linguagem_C.pdfLinguagem_C.pdf
Linguagem_C.pdf
 
RevisãoCompactaFuncoesPonteiro.pptx
RevisãoCompactaFuncoesPonteiro.pptxRevisãoCompactaFuncoesPonteiro.pptx
RevisãoCompactaFuncoesPonteiro.pptx
 
Estrutura de linguagem de programação - Aula 2.pptx
Estrutura de linguagem de programação - Aula 2.pptxEstrutura de linguagem de programação - Aula 2.pptx
Estrutura de linguagem de programação - Aula 2.pptx
 
Aula 7 pc - estrutura
Aula 7   pc - estruturaAula 7   pc - estrutura
Aula 7 pc - estrutura
 
Python Emsl2009
Python Emsl2009Python Emsl2009
Python Emsl2009
 
Funções e procedimentos
Funções e procedimentosFunções e procedimentos
Funções e procedimentos
 
Introdução a Linguagem C
Introdução a Linguagem CIntrodução a Linguagem C
Introdução a Linguagem C
 
Aula 6 pc - slides
Aula 6   pc - slidesAula 6   pc - slides
Aula 6 pc - slides
 
Comandos de Controle de Programa em C
Comandos de Controle de Programa em CComandos de Controle de Programa em C
Comandos de Controle de Programa em C
 
Introducao Google GO
Introducao Google GOIntroducao Google GO
Introducao Google GO
 
Pged 03
Pged 03Pged 03
Pged 03
 
document.onl_manual-psi-m5.pdf
document.onl_manual-psi-m5.pdfdocument.onl_manual-psi-m5.pdf
document.onl_manual-psi-m5.pdf
 
Pesquisa ppi 2
Pesquisa ppi 2Pesquisa ppi 2
Pesquisa ppi 2
 
9 structs e ponteiros
9   structs e ponteiros9   structs e ponteiros
9 structs e ponteiros
 
095 A 134 Material Auxiliar Para Curso AvançAdo I Msp430
095 A 134   Material Auxiliar Para Curso AvançAdo I Msp430095 A 134   Material Auxiliar Para Curso AvançAdo I Msp430
095 A 134 Material Auxiliar Para Curso AvançAdo I Msp430
 
Programação orientada a objetos – III
Programação orientada a objetos – IIIProgramação orientada a objetos – III
Programação orientada a objetos – III
 
Aula 1 | Introdução a C++
Aula 1 | Introdução a C++Aula 1 | Introdução a C++
Aula 1 | Introdução a C++
 
Programação Estruturada 2 - Aula 03
Programação Estruturada 2 - Aula 03Programação Estruturada 2 - Aula 03
Programação Estruturada 2 - Aula 03
 

Ed1

  • 1. Estrutura de Dados I Docente: Dikiefu Fabiano, Msc
  • 2. Sumário 1. Introdução 1.1 Tipo de dados Abstracto -TDA 1.2 Ponteiros 1.3 Funções 1.4 Estruturas e tipos de dados definidos pelo programador 2. Listas Lineares 2. 1 Listas Sequenciais 2.2 Listas ligadas 2.3 Listas Circulares 2.4 Listas duplamente ligadas 2.5 Pilhas 2.5.1Implementação sequencial da pilha 2.5.2Implementação dinâmica da pilha 2.6 Filas 2.6.1Implementação sequencial da fila 2.6.2Implementação dinâmica da fila
  • 3. 3. Árvores 3.1 Introdução 3.2 Representação de árvores 3.2 Definição 3.3 Terminologia 3.4 Árvores binárias 3.5 Árvores balanceadas 3.6 Definição da estrutura de dados 4. Ordenação 4.1 Ordenação por Selecção 4.2 Ordenação por Inserção 4.3 Shellsort 4.4 Quicksort 4.5 Heapsort 5. Pesquisa 5.1 Pesquisa Sequencial 5.2 Pesquisa Binária 5.3 Árvores de Pesquisa
  • 4. Capitulo I: Introdução Sumário: 1.1Tipo de Dado Abstracto (TDA) 1.2Ponteiros 1.3Funções 1.4Estruturas 1.5 Tipos de dados definidos pelo programador
  • 5. 1.1 Tipo de Dados Abstracto - TDA  Tipos de Dados ◦ Tipo de dados de uma variáveis  define o conjunto de valores que a variável pode assumir.  Especifica a quantidade de bytes que deve ser reservadas a ela. ◦ Tipos de dados podem ser vistos como métodos para interpretar o conteúdo da memória do computador ◦ Podemos ver o conceito de tipo de dados de uma outra perspectiva: não em termos do que um computador pode fazer (interpretar os bits …) mas em termos do que o utilizador desejam fazer (somar dois inteiros…)
  • 6. 1.1 Tipo de Dado Abstracto - TDA  Tipo de Dado Abstracto ◦ Agrupa a estrutura de dados juntamente com as operações que podem ser feitas sobre esses dados ◦ O TDA encapsula a estrutura de dados. Os utilizadores do TDA só tem acesso a algumas operações disponibilizadas sobre esses dados ◦ Utilizador do TDA x Programador do TDA  Utilizador só “enxerga” a interface, não a sua implementação  Dessa forma, o utilizador pode abstrair da implementação específica.  Qualquer modificação nessa implementação fica restrita ao TDA ◦ A escolha de uma representação específica é fortemente influenciada pelas operações a serem executadas
  • 7. 1.1 Tipo de Dado Abstracto - TDA  Estrutura de Dados - ED ◦ É um método particular de se implementar um TDA ◦ Cada ED é construída dos tipos primitivos (inteiro, real, char, …) ou dos tipos compostos (array, registo, …) de uma linguagem de programação. ◦ Algumas operações realizadas em TDA são: criação, inclusão, busca e remoção de dados.  Exemplos de TDA ◦ Lineares: Não Lineares:  Listas ligadas . Árvores  Pilhas . Grafos  Filas
  • 8. TDA-Exemplo  Existem vários métodos para especificar um TDA. O método que usaremos é semi-formal e faz uso intensivo da notação de C. Para ilustrar o conceito de um TDA e método de especificação, examinemos o TDA RACIONAL que corresponde ao conceito matemático de um número racional.  Um número racional é o que pode ser expresso como o quociente de dois inteiros.  As operações sobre números racionais que definimos são: ◦ Criação de um número racional ◦ Adição ◦ Multiplicação ◦ Teste de igualdade.
  • 9. TDA-Exemplo //definição de valor abstract typedef <integer, integer> RACIONAL; condiction RACIONAL [1] <> 0; //definição de operador abstract RACIONAL criaracional(a, b) int a, b; precondition b <> 0; postcondition criaracional[0]== a; criaracional[1]== b; abstract RACIONAL soma (a, b) RACIONAL a, b; postcondition soma[1]== a[1]*b[1]; soma[0]==a[0]*b[1]+b[0]*a[1];
  • 10. abstract RACIONAL mult(a, b) RACIONAL a, b; postcondition mult[0]==a[0]*b[0]; mult[1]==a[1]*b[1]; abstract RACIONAL igual(a,b) RACIONAL a, b; postcondition igual==(a[0]*b[1]==a[1]*b[0]); Exercício: Crie o TODA para o conjunto de números complexos com todas as suas operações básicas.
  • 11. 1.2 Apontadores  Na realidade, C permite que o programador referencie a posição de objectos bem como os próprios objectos (isto é, o conteúdo de suas posições)
  • 12. 1.2 Apontadores • O objectivo: – Armazenar o endereço de outra variável. • Sintaxe tipo * ptr • ptr - é o nome da variável do tipo apontador • tipo – o tipo da variável para qual apontará. • * - indica que é uma variável do tipo apontador • Exemplo 1: char *pc; int *pi; float *pf
  • 13. 1.2 Apontadores  Um ponteiro é como qualquer outro tipo de dado em C.  O valor de um ponteiro é uma posição de memória da mesma forma que o valor de um inteiro é um número.  Os valores dos ponteiros podem ser atribuídos como qualquer outro valor.  A conversão de pf do tipo “ponteiro para um número de ponto flutuante” para o tipo “ponteiro para um inteiro” pode ser feita escrevendo-se: pi = (int *) pf;  Se pi é um ponteiro para um inteiro, então pi +1 é o ponteiro para o inteiro imediatamente seguinte ao inteiro *pi em memória, pi–1 é o ponteiro para o inteiro imediatamente anterior a *pi.
  • 14. Apontadores  Por exemplo, suponha que determinada máquina usa endereçamento de bytes, que um inteiro exija 4 bytes e que valor de pi seja 100 (isto é, pi aponta para inteiro *pi na posição 100). Sendo assim, o valor de pi–1 é 96, o valor de pi+1 é 104.  De modo semelhante, se o valor da variável pc é 100 e um caractere tem um byte, pc – 1 refere-se a posição 99 e pc+1 à posição 101.
  • 15. Apontadores  Uma área na qual os ponteiros x =5 de C desempenham um printf(“%dn”, x) // imprime 5 notável papel é passagem de parâmetro para funções. func(x); // imprime 6  Normalmente, os parâmetros printf(“%dn”, x) // imprime 5 são passados para uma … função em C por valor, isto é, func(y) os valores sendo passados são copiados nos parâmetros int y da função de chamada no { momento em que a função for ++y; chamada. Se o valor de um printf(“%dn”, y); parâmetro for alterado dentro da função, o valor no } programa chamada não será modificado.
  • 16. Se precisar usar func para modificar o valor de x, precisaremos passar o endereço de x x =5 printf(“%dn”, x) // imprime 5 func(&x); // imprime 6 printf(“%dn”, x) //imprime 6 … func(py) int *py { ++(*py); printf(“%dn”, *py); }
  • 17. 1.2.1 Inicialização automática de apontadores • Um bom hábito para evitar problemas de programação é a inicialização sempre dos apontadores • A constante simbólica NULL, quando colocada num apontador, indica que este não aponta para nenhuma variável. int x = 5; float pi = 3.1415; int px = &x; float * ppi = &pi;
  • 18. 1.3 Funções  Conceito: ◦ É uma unidade autónoma de código do programa e é desenhada para cumprir uma tarefa particular.  Sintaxe tipo nome_da_função (parametros){ comandos; }
  • 19. 1.3 Funções • Quando uma função não retorna um valor para função que a chamou ela é declarada como void. • Ex: #include<stdio.h> void linha (char ch, int size){ int i; for (i=1; i size; i++) putchar(ch); } main(){ char lin; int tamanho; printf(“Introduza o tipo de linha”); scanf(“%c”, &lin); printf(“Introduza o tamanho da linha”); scanf(“%d”, &tamanho); linha(lin, tamanho); getchar(); }
  • 20. 1.3 Funções 1.3.3 Função com retorno O tipo de retorno tem que ser declarada. main(){ int b, e;  Ex1 : printf(“Introduza a base e int potencia (int base, int expoente”); expoente){ scanf(“%d%d”, &b, &e); int i, pot=1; printf(“valor=%dn”, If (expoente<0) potencia(b,e)); getchar(); return; } for (i=1; i <= expoente; i++) pot=pot*base; Return pot; }
  • 21. Estrutura de dados e tipo de dados definido pelo utilizador  Estruturas são peças contíguas de armazenamento que contém vários tipos simples agrupados numa entidade.  Estrutura são manifestadas através da palavra reservada struct seguida pelo nome da estrutura e por uma área delimitada por colchetes que contém campos. struct pessoa { char nome[30]; int idade; char sexo; };  Para criação de novos tipos de estrutura de dados utiliza-se a palavra-chave: typedef typedef struct pessoa { char nome[30]; int idade; char sexo; } PESSOA;
  • 22. Estrutura  Para obter directamente o valor de um campo, a forma é <nome_da_estrutura>.<campo>.  Para obter o valor de um campo apontado por um ponteiro a forma é <nome_da_estrutura> -> <campo>  Ex … PESSOA func, *estud, &docente = func ; func.nome=“Adriano”; //atribui o valor Adriano para o campo nome func.idade=25; //atribui o valor 25 para o campo idade func.sexo=“M”; //atribui o valor M para o campo sexo estud=&func; //faz estud apontar para func estud->nome=“Joana”; //muda o valor do campo nome para Joana estud ->idade =22; //muda o valor do campo idade para 22 estud ->sexo =“F”; //muda o valor do campo sexo para F docente.Nome=“Pedro”; //Referências usam a notação de ponto …
  • 23. Estruturas Recursivas  Estruturas podem ser recursivas, i.e., podem conter ponteiros para si mesmas. Esta peculiaridade ajuda a definir estruturas do tipo lista. struct lista{ struct lista *prox; int dado; }
  • 24. Capitulo II: Listas Sumário 2. Listas Lineares 2. 1 Listas Sequenciais 2.2 Listas ligadas 2.3 Listas Circulares 2.4 Listas duplamente ligadas 2.5 Pilhas 2.5.1 Implementação sequencial da pilha 2.5.2 Implementação dinâmica da pilha 2.6 Filas 2.6.1 Implementação sequencial da fila 2.6.2 Implementação dinâmica da fila 2.6.3 Fila circular
  • 25. Listas  Listas são uma colecção de objectos, onde os itens podem ser adicionados em qualquer posição arbitrária.
  • 26. Listas Propriedades • Uma lista pode ter zero ou mais elementos • Um novo item pode ser adicionado a lista em qualquer ponto • Qualquer item da lista pode ser eliminado • Qualquer item da lista pode ser acessado
  • 27. Listas - Formas de Representação  Sequencial ◦ Explora a sequencialidade da memória do computador, de tal forma que os nós de uma lista sejam armazenados em endereço sequencias. Podem ser representado por um vector na memória principal ou um arquivo sequencial em disco. L Adão Adelin Basto Daniel Josefin … 0 1a 2 s 3 a 4 5 MAX-1  Ligada ◦ Esta estrutura é tida como uma sequencia de elementos ligados por ponteiros, ou seja, cada elemento deve conter, além do dado propriamente dito, uma referencia para o próximo elemento da lista. Adão Adelina Bastos Daniel Josefina L
  • 28. Listas sequenciais  Uma lista representada de forma sequencial é um conjunto de registos onde estão estabelecidas regras de precedência entre seus elementos.  Implementação de operações pode ser feita utilizando array e registo, associando o elemento a[i] com o índice i.  Características ◦ Os elementos na lista estão armazenados fisicamente em posições consecutivas ◦ A inserção de um elemento na posição a[i] causa o deslocamento a direita do elemento a[i] ao último. ◦ A eliminação do elemento a[i] requer o deslocamento à esquerda do a[i+1] ao último.
  • 29. Listas Sequenciais  Vantagens ◦ Acesso directo a qualquer dos elementos da lista. ◦ Tempo constante para aceder o elemento i - (depende somente de índice)  Desvantagens ◦ Para inserir ou remover elementos temos que deslocar muitos outros elementos ◦ Tamanho máximo pré-estimado  Quando usar ◦ Listas pequenas ◦ Inserção e remoção no fim da lista ◦ Tamanho máximo bem definido
  • 30. Listas sequenciais Operações básicas  Definição #define MAX 50 //tamanho máximo da lista typedef char elem[20]; // tipo base dos elementos da lista typedef struct tlista{ elem v[MAX];//vector que contém a lista int n; //posição do último elemento da lista }; Tlista Adão Alberto Ana Daniela Carmen … n=5 v 0 1 2 3 4 5 6 MAX – 1 1.Criar uma lista vazia 2. Verificar se uma lista está vazia void criar(tlista *L) { int vazia(tlista L) { L -> n = 0; return (L .n == 0); } }
  • 31. Listas sequenciais Operações básicas 3. Verificar se uma lista está cheia 4. Obter o tamanho de uma lista int cheia( tlista L) { int tamanho ( tlista L){ return (L .n == MAX); return (L.n); } } 5. Obter o i-ésimo elemento de uma 6. Pesquisar um dado elemento, lista retornando a sua posição. int elemento( tlista L, int pos, elem *dado ) int posicao( tlista L, elem dado ) { { if ((pos > L.n) || (pos <=0)) int i; return (0); for (i=1; i<L.n; i++) *dado = L.v [pos - 1]; if(L.v[i-1] == dado) return 1; return i; } return 0; }
  • 32. Listas Sequenciais Operações básicas 7. Inserção de um elemento em uma 8. Remoção do elemento de uma determinada posição determinada posição (requer o deslocamento à direita dos (requer o deslocamento à esquerda dos elementos v(i+1) … v(n)) elementos v(p+1) … v(n)) // retorna 0 se a posição for inválida ou se a /* o parâmetro dado irá receber o elemento lista encontrado. // estiver cheia, caso contrário retorna 1 Retorna 0 se a posição for inválida , caso contrário retorna 1 */ int inserir( tlista *L, int pos, elem dado ) { int i; int remover( tlista *L, int pos, elem *dado ) if (( L->n == MAX) || ( pos > L->n + 1 )) { return (0); int i; for (i = L->n; i >= pos; i--) if ( ( pos > L->n) || (pos <= 0) ) L->v[i] = L->v [i-1]; return (0); L->v[pos-1] = dado; *dado = L-> v[pos-1]; (L->n)++; for (i = pos; i <= (L->n) - 1; i++) return (1); L->v[i-1] = L->v [i]; } (L->n)--; return (1); }
  • 33. Listas Ligadas  Uma lista ligada ou lista encadeada é uma estrutura de dados linear e dinâmica. Ela é composta por células que apontam para o próximo elemento da lista.  Para "ter" uma lista ligada/encadeada, basta guardar seu primeiro elemento, e seu último elemento aponta para uma célula nula.  Vantagens ◦ A inserção ou a retirada de um elemento na lista não implica na mudança de lugar de outros elementos; ◦ Não é necessário saber, anteriormente, o número máximo de elementos que uma lista pode ter, o que implica em não desperdiçar espaço na Memória Principal (Memória RAM) do computador.  Desvantagens ◦ manipulação torna-se mais "perigosa" uma vez que, se o encadeamento (ligação) entre elementos da lista for mal feito, toda a lista pode ser perdida; ◦ Para acessar o elemento na posição n da lista, deve-se percorrer os n - 1 anteriores.
  • 34. Listas ligadas  As listas ligadas são conjunto de elementos ligados, onde cada elemento contem uma ligação com um ou mais elementos da lista.  Uma lista ligada tem necessariamente uma variável ponteiro apontando para o seu primeiro elemento. Essa variável será utilizada sempre, mesmo que esteja vazia, e deverá apontar sempre para o início da lista.  Cada elemento da lista ligada será composta por duas/três partes principais: uma parte conterá informações e a(s)
  • 35. Listas Ligadas - Propriedades  Uma lista pode ter zero ou mais elementos  Um novo item pode ser adicionado a lista em qualquer ponto  Qualquer item da lista pode ser eliminado  Qualquer item da lista pode ser acessado.
  • 36. Listas Ligadas  Uma lista ligada é composta por elementos alocadas em memória.  Utiliza-se duas funções para alocar (desalocar) memória dinamicamente: ◦ malloc() ◦ free()  Ex: L = (lista) malloc(sizeof(lista)); free(L);
  • 37. Lista Simplesmente Ligada  Cada elemento da lista possui uma única conexão. Assim, cada elemento será formado por um bloco de dados e um ponteiro para próximo elemento. Chamaremos cada elemento nó.  Um nó é um conjunto de informações de tipos diferentes, portanto ela é uma estrutura definida pelo programador: struct no{ int dado; struct no *prox; //ponteiro para o próximo nó }
  • 38. Listas Simplesmente Ligadas  Sendo que uma memória alocada dinamicamente não possui o nome como no caso das variáveis declaradas no programa, toda lista deverá ter uma variável que apontará para o seu primeiro elemento. struct no *head; //cabeça da lista head=null; //inicia a lista com nulo
  • 39. Listas Simplesmente Ligadas  Podemos nos referenciar a uma lista ligada através do seu primeiro nó.  Entretanto, toda vez que realizarmos uma inserção no início da lista teremos que garantir que todas as partes do programa que referenciam a lista tenha suas referencias actualizadas. Para evitar este problema, é comum usar um nó especial chamado nó cabeça, e um ponteiro para o primeiro elemento da lista.
  • 40. Listas Simplesmente Ligadas  A implementação em C de uma lista simplesmente ligada com nó cabeça.  Precisamos de dois tipos de dados: ◦ Um tipo nó, representa cada elemento da lista. ◦ e um tipo lista que contém um ponteiro para nó cabeça.
  • 41. Listas Simplesmente Ligadas typedef struct no{ int dado; // o dado poderia ser de qualquer tipo struct no *prox; // ponteiro para próximo elemento } NO typedef struct lista{ NO *head; }LISTA
  • 42. Listas Simplesmente Ligadas  As operações básicas sobre a estrutura são: criação da lista e de nó. 1. Criação de lista LISTA *criaLista(){ LISTA *L; L=(LISTA *)malloc(sizeof(LISTA)); L->head = NULL; return L; } 2. Criação de nó NO *criaNo(int dado){ NO *no; no = (NO *)malloc(sizeof(NO)); no->dado = dado; no->prox = NULL; return no; }
  • 43. Listas Simplesmente Ligadas  Ao contrário dos vectores, onde acessamos qualquer elemento usando um índice numérico, com uma lista ligada temos apenas acesso sequencial, por exemplo, para chegar ao decimo elemento temos que passar por todos nove elementos anteriores.  Para percorrer uma lista, precisamos obter seu primeiro elemento e então obtemos os elementos subsequentes seguindo os ponteiros de próximo elemento em cada nó. NO *primeiro(LISTA *L){ return (L->head); } NO *proximo(NO *no){ return (no->prox); }
  • 44. Listas Simplesmente Ligadas  Andar para trás em uma lista simplesmente ligada não é tão simples, requer que a lista seja percorrida desde o início verificado se, para cada elemento x, proximo(x) é o elemento do qual procuramos o elemento anterior. NO *anterior(LISTA *L, NO *no){ NO *p; if(no==L->head) return NULL; p = primeiro(L); while(proximo(p)!=NULL){ if(proximo(p) == no) return p; p = proximo(p); } return NULL; }
  • 45. Listas Simplesmente Ligadas  Obter o último elemento também requer que toda a lista seja percorrida. NO *ultimo(LISTA *L){ NO *p; p = primeiro(L); if (p == NULL) return NULL; while(proximo(p)!=NULL) p = proximo(p); return p; }
  • 46. Listas Simplesmente Ligadas  Podemos querer 3 tipos de inserções em uma lista: ◦ no início, ◦ no fim, ◦ ou em uma posição arbitrária.  Na lista simplesmente ligada a inserção no início é trivial: Fazemos o próximo do novo elemento apontar para o primeiro elemento da lista e fazemos com que o novo elemento seja o primeiro elemento (cabeça) da lista.  A inserção em posição arbitrária pode ser feito em tempo constante se tivermos um ponteiro para o elemento que será o elemento anterior ao novo elemento: void insere_apos(NO *novo, NO *anterior){ novo->prox = anterior->prox; antrior->prox = novo; }  A inserção no final da lista exige que encontremos seu último elemento.
  • 47. Listas Simplesmente Ligadas  Remoção de um elemento arbitrário requer que encontremos o elemento anterior ao removido. void remove(LISTA *lista, NO *no){ NO *ant; if(no == lista->head){ lista->head = no->prox; } else{ ant = anterior(lista, no) if(ant!=NULL) ant->prox = no->prox; } free(no); }
  • 48. Listas duplamente ligadas  Listas duplamente ligadas são usadas frequentemente nos problema que exija uma movimentação eficiente desde um nó quer para o seu antecessor, quer para o seu sucessor. frent NULL e valor valor valor NULL Atrás
  • 49. Listas duplamente ligadas  Uma das virtudes de listas duplamente ligadas, é o facto de nos podemos deslocar para o nó anterior ou para nó seguinte, a um nó dado com facilidade.  Uma lista duplamente ligada permite- nos remover um nó qualquer da lista em tempo constante, usando apenas um ponteiro para essa célula.
  • 50. Listas duplamente ligadas - operações  anterior(): devolve o elemento anterior a um determinado elemento.  cria(): devolve uma lista com um elemento  insere(): insere um determinado elemento na lista.  mostra(): mostra o conteúdo da lista.  procura(): devolve a posição de um determinado elemento.  remove(): remove o elemento desejado da lista.
  • 51. Lista duplamente Ligada - operações #include<stdio.h> #include<stdlib.h> typedef struct listadl{ LDL *prox; //próximo elemento da lista LDL *ant; //elemento anterior da lista int dado; }LDL;
  • 52. Lista duplamente Ligada - operações Devolve uma lista com elemento elem. LDL *cria(int elem){ LDL*tmp; tmp =(LDL *)malloc(sizeof(tmp)); tmp->dado = elem; tmp->prox = NULL; tmp->ant = NULL; return tmp; }
  • 53. Lista duplamente Ligada - operações Insere o elemento elem na lista L. void *insere (int elem, LDL *L){ LDL*tmp; tmp=L; while((tmp->prox ==NULL)==0) tmp = tmp->prox; tmp->prox = cria(elem); (tmp->prox)-> ant = tmp; }
  • 54. Lista duplamente Ligada - operações Imprime todos os elementos da if((no1 == NULL)==0) lista. printf(“O proximo:%d”, void *mostra(LDL *L){ no1->dado); LDL*tmp, *no1, *no2; else tmp=L; printf(“Não possui while((tmp->prox próximo.”); ==NULL)==0){ if((no2 == NULL)==0) no1 = tmp->prox; printf(“O anterior:%d”, no2 = tmp->ant; no2->dado); printf(“O elemento: %d”, else printf(“Não possui tmp->dado); anterior.”); tmp= tmp->prox; } }
  • 55. Lista duplamente Ligada - operações Remove o elemento elem da lista L. else LDL *remove(int elem, LDL *L){ if((proxno == NULL)==0){ LDL*tmp, *proxno, *noant; proxno->ant =NULL; tmp=L; while((tmp->prox ==NULL)==0){ free(tmp); proxno = tmp->prox; L = proxno; noant = tmp->ant; return L; if((tmp->dado == elem) } if((noant == NULL)==0) else { if((proxno == NULL)==0){ L = NULL; noant->prox =proxno; free(tmp); proxno->ant =noant; return L; free(tmp); } return L; else{ } tmp = tmp -> prox; else{ } noant->prox=NULL; printf(“O elemento %d não se free(tmp); return L; encontra na listan”, elem); } }
  • 56. Lista duplamente Ligada - operações Devolve a posição do elemento elem. int *procura(int elem, LDL *L){ LDL*tmp; int pos = 0; tmp=L; while((tmp->prox ==NULL)==0) if((tmp->dado) == elem){ printf(“A posição do elemento %d é %dn”, elem, pos); return pos; } else{ pos++; tmp = tmp->prox; } printf(“O elemento não se encontra na lista”); }
  • 57. Lista duplamente Ligada - operações Devolve o elemento anterior a elem. void *anterior(int elem, LDL *L){ LDL*tmp, *noant; tmp=L; while((tmp==NULL)==0) if((tmp->dado) == elem){ noant = tmp->ant; printf(“O elemento anterior a %d é %dn”, elem, noant- >dado); else printf(“Não existe anterior”); exit(); } else tmp = tmp->prox; printf(“O elemento não se encontra na lista”); }
  • 58. Lista Circular Ligada  As listas circulares são do tipo simples ou duplamente ligadas, em que o nó final da lista a ponta para o nó inicial. Este tipo de estrutura adapta-se a situações do tipo FIFO (First In First Out). Fig. 1 Lista circular ligada ( lista simplesmente ligada) dado dado dado dado Fig. 2 Lista circular ligada ( lista duplamente ligada) dado dado dado dado
  • 59. Lista circular Ligada - operações typedef struct no{ 3. Aloca e desaloca nó. int dado; int aloca(LCSL *lista, int valor){ struct no *prox; }NO, LCSL; LCSL *tmp; NO cabeca; tmp= (LCSL) malloc(sizeof(no)); 1. Inicializa a lista circular if (tmp==NULL) int inicializa(LCSL *cabeca) return 1; // Erro … { *lista = tmp; *cabeca=NULL; tmp->dado = valor; return 0; } tmp->prox = NULL; 2. Verifica se a lista está cheia return 0; } int vazia(LCSL lista){ void liberta(LCSL *lista){ return (lista==NULL)? 1:0; free(*lista); } *lista=NULL; }
  • 60. Lista circular Ligada - operações 4. Inserção de um item 5. Adiciona um item ao fim da int insere(LCSL *lista, int valor) lista. { int adiciona(LCSL *lista, int LCSL tmp; valor){ if (aloca(&tmp, valor)== 1) LCSL tmp; return 1; // Erro if (aloca(&tmp, valor)== 1) if (vazia(*lista) == 1){ return 1; // Erro //True=1 if (vazia(*lista) == 1) //True=1 tmp->prox = tmp; tmp->prox = tmp; *lista=tmp; else{ } else { tmp->prox = *lista->prox; *lista->prox = tmp; tmp->prox = *lista->prox; } *lista->prox = tmp; *lista = tmp; } return 0; //Ok return 0; } }
  • 61. Lista circular Ligada - 6. Eliminação de um nóoperações int apagaNo(LCSL *lista, LCSL no) { LCSL tmp; if(vazia(*lista) == 1) return 1; if(no==no->prox) *lista=NULL; else { for(tmp=*lista->prox; tmp!=*lista && tmp->prox!=no; tmp=tmp- >prox) ; if(tmp->prox !=no) return 1; tmp->prox = no->prox; if(no== *lista) *lista=tmp; } free(&no); return 0; }
  • 62. Pilha e Fila 1. Pilha • Implementação sequencial da pilha 2. Fila I. Operações básicas II. Implementação sequencial da fila
  • 63. Pilhas  Pilhas são listas onde a inserção ou a remoção de um item é feita no topo.  Definição: dada a pilha P= (a[1], a[2], …, a[n]), dizemos que a[1] é o elemento da base da pilha; a[n] é o elemento topo da pilha; e a[i+1] está acima de a[i].  Pilhas são conhecidas como listas LIFO (last in first out)
  • 64. Implementação de pilhas  Como lista sequencial ou ligada? ◦ No caso geral de listas ordenadas, a maior vantagem da alocação dinâmica sobre a sequencial, se a memória não for problema, é a eliminação de deslocamento na inserção ou eliminação dos elementos. No caso das pilhas, essas operações de deslocamento não ocorrem. Portanto, alocação sequencial é mais vantajosa na maioria das vezes. ◦ Como o acesso a pilha é limitado ao último elemento inserido, todos os elementos do meio do vector devem estar preenchidos. Há desvantagens quando a quantidade de dados a ser armazenados não é conhecido
  • 65. Pilha
  • 66. Implementação Sequencial #define MAXPILHA 50 #define tpilha(p) (p->topo – p->base) typedef struct pilha{ int base[MAXPILHA]; int *topo; }PILHA;
  • 67. Implementação Sequencial 1. Inicializa a pilha vazia int inicializa(PILHA *pp){ pp->topo = pp->base; return 1; } 2. Verifica se pilha está vazia int vazia(PILHA *pp){ return (pp->topo == pp->base) ? 1 : 0; } 3. Coloca dado na pilha. Apresenta erro se não haver espaço. int push(PILHA *pp, int dado){ if(tpilha(pp) == MAXPILHA) return 0; *pp->topo=dado; pp->topo++; return 1; }
  • 68. Implementação Sequencial 4. Retira o valor do topo da pilha a atribui a dado. int pop (PILHA *pp, int dado){ if(vazia(pp) == 1) return 0; pp->topo--; *dado = *pp->topo return 1; } 5. Retorna o valor do topo da pilha se removê-lo. int topo(PILHA *pp, int *dado){ if(pop(pp, dado) == 0) return 0; return push (pp, dado) }
  • 69. Fila  Uma Fila é uma estrutura de dados do tipo FIFO (First In First Out), cujo funcionamento é inspirado no de uma fila “natural”, na qual o primeiro elemento a ser inserido é sempre o primeiro elemento a ser retirado.
  • 70. Fila Uma fila tem por norma as seguintes funcionalidade:  Colocar e retirar dados da fila: ◦ insere – guardar um elemento na fila ◦ remove – retirar um elemento da fila ◦ topo – retornar o elemento do topo da fila.  Testar se a fila está vazia ou cheia: ◦ cheia – Verificar se a fila está cheia (não pode guardar mais elementos). ◦ vazia – Verificar se a fila está vazia (não contém elementos)  Inicializar ou limpar: ◦ inicializa – Colocar a fila num estado “pronto” a ser utilizada
  • 71. Fila – Outras Operações  Buscar por elementos que coincidam com certo padrão (casamento de padrão)  Ordenar uma lista  Intercalar duas listas  Concatenar duas listas  Dividir uma lista em duas  Fazer cópia da lista
  • 72. Fila  A implementação de uma fila pode ser efectuada através da utilização de diferentes estruturas de dados (vectores, listas ligadas, árvores, etc.). De seguida, apresenta-se duas implementação de uma fila através da utilização de vectores e listas ligadas.
  • 73. Fila  Características das filas: ◦ Os dados são armazenados pela ordem de entrada  Tipos de filas: ◦ Filas de espera (queues)  com duplo fim (deque “double-ended queue) ◦ Filas de espera com prioridades (priority queues)  Implementação das filas: ◦ usando vectores/arrays (circulares ou não) ◦ utilizando um apontador para nós de
  • 74. Fila  Implementação em C usando arrays  Vantagens: ◦ Facilidade de implementação.  Desvantagens: ◦ Vectores possuem um espaço limitado para armazenamento de dados. ◦ Necessidade de definir um espaço grande o suficiente para a fila ◦ Necessidade de um indicador para o inicio e para o fim da fila  Método de Implementação ◦ A adição de elementos à fila resulta no incremento do indicador do fim da fila ◦ A remoção de elementos da fila resulta no incremento do indicador do inicio da fila
  • 75. Implementação de Filas em C  Usamos o vector para armazenar os elementos da fila e duas variáveis, inic e fim, para armazenar as posições dentro do vector do primeiro e último elementos da fila: #define MAXFILA 100 struct fila{ int elem[MAXFILA]; int inic, fim; }f;  É evidente que usar vector para armazenar uma fila introduz a possibilidade de estouro, caso a fila fique maior que o tamanho do vector.
  • 76. Capitulo III: Árvores Docente: Dikiefu Fabiano, Msc
  • 77. Sumário 3.1 Introdução 3.2 Tipos de árvores 3.3 Árvores binárias 3.3.1 Estrutura de uma árvore binária 3.3.2 Descrição 3.3.3 Altura 3.3.4 Representação em C 3.4 Árvores Genéricas 3.4.1 Estrutura de uma árvore genérica 3.4.2 Representação em C 3.4.3 Problemas com Representação
  • 78. Introdução  As estruturas anteriores são chamadas de unidimensionais (ou lineares) ◦ Exemplo são listas sequenciais e ligadas  Não podem ser usadas como hierarquias. ◦ Exemplo: árvore de directórios  Árvore é uma estrutura adequada para representar hierarquias  A forma mais natural para definirmos uma estrutura de árvore é usando
  • 79. Estrutura de Árvores  Uma árvore é composta por um conjunto de nós.  Existe um nó r, denominado raiz, que contém zero ou mais sub-árvores, cujas raízes são ligadas a r.  Esses nós raízes das sub-árvores são ditos filhos do nó pai, no caso r.  Nós com filhos são chamados nós internos e nós que não têm filhos são chamados folhas, ou nós externos.  É tradicional desenhar as árvores com a raiz para cima e folhas para baixo, ao contrário do que seria de se esperar.
  • 80. Estrutura de Árvores  Por adoptarmos essa forma de representação gráfica, não representamos explicitamente a direcção dos ponteiros, subentendendo que eles apontam sempre do pai para os filhos.
  • 81. Tipos de Árvores  O número de filhos permitido por nó e as informações armazenadas em cada nó diferenciam os diversos tipos de árvores existentes.  Existem dois tipos de árvores: ◦ árvores binárias, onde cada nó tem, no máximo, dois filhos. ◦ árvores genéricas, onde o número de filhos é indefinido.  Estruturas recursivas serão usadas como base para o estudo e a implementação das operações com árvores.
  • 82. Árvore Binária  Um exemplo de utilização de árvores binárias está na avaliação de expressões.  Como trabalhamos com operadores que esperam um ou dois operandos, os nós da árvore para representar uma expressão têm no máximo dois filhos.  Nessa árvore, os nós folhas representam operandos e os nós internos operadores.
  • 83. Árvores Binárias Esta árvore representa a expressão: (3+6)*(4-1)+5
  • 84. Estrutura de uma AB  Numa árvore binária, cada nó tem zero, um ou dois filhos.  De maneira recursiva, podemos definir uma árvore binária como sendo: ◦ uma árvore vazia; ou ◦ um nó raiz tendo duas sub-árvores, identificadas como a sub-árvore da direita (sad) e a sub-árvore da esquerda (sae).
  • 85. Estrutura de uma AB • Os nós a, b, c, d, e, f formam uma árvore binária da seguinte maneira: • A árvore é composta do nó a, da subárvore à esquerda formada por b e d, e da sub- árvore à direita formada por c, e e f. • O nó a representa a raiz da árvore e os nós b e c as raízes das sub-árvores. • Finalmente, os nós d, e e f são folhas da árvore.
  • 86. Descrição de AB  Para descrever árvores binárias, podemos usar a seguinte notação textual: ◦ A árvore vazia é representada por <>, ◦ e árvores não vazias por <raiz sae sad>.  Com essa notação, a árvore da Figura anterior é representada por: ◦ <a<b<><d<><>>> <c<e<><>><f<><>>>>.
  • 87. Descrição de AB  Uma sub-árvore de uma árvore binária é sempre especificada como sendo a sae ou a sad de uma árvore maior, e qualquer das duas sub- árvores pode ser vazia.  As duas árvores da Figura abaixo são distintas.
  • 88. Altura de uma AB  Uma propriedade fundamental de todas as árvores é que só existe um caminho da raiz para qualquer nó.  Podemos definir a altura de uma árvore como sendo o comprimento do caminho mais longo da raiz até uma das folhas.  A altura de uma árvore com um único nó raiz é zero e, por conseguinte, dizemos que a altura de uma árvore vazia é negativa e vale -1.
  • 89. Representação em C  Podemos definir um tipo para representar uma árvore binária.  Vamos considerar que a informação a ser armazenada são valores de caracteres simples.  Vamos inicialmente discutir como podemos representar uma estrutura de árvore binária em C.  Que estrutura podemos usar para representar um nó da árvore?  Cada nó deve armazenar três informações: a informação propriamente dita, no caso um caractere, e dois ponteiros para as sub- árvores, à esquerda e à direita.
  • 90. Representação em C struct arv { char info; struct arv* esq; struct arv* dir; }; typedef struct arv Arv; Da mesma forma que uma lista encadeada é representada por um ponteiro para o primeiro nó, a estrutura da árvore como um todo é representada por um ponteiro para o nó raiz.
  • 91. Criando Árvores Arv* inicializa(void) { return NULL; } Arv* cria(char c, Arv* sae, Arv* sad) { Arv* p=(Arv*)malloc(sizeof(Arv)); p->info = c; p->esq = sae; p->dir = sad; return p; }
  • 92. Árvore Vazia int vazia(Arv* a) { return a==NULL; }
  • 93. Exemplo  Exemplo: Usando as operações inicializa e cria, crie uma estrutura que represente a seguinte árvore.
  • 94. Exemplo /* sub-árvore com 'd' */ Arv* a1= cria('d',inicializa(),inicializa()); /* sub-árvore com 'b' */ Arv* a2= cria('b',inicializa(),a1); /* sub-árvore com 'e' */ Arv* a3= cria('e',inicializa(),inicializa()); /* sub-árvore com 'f' */ Arv* a4= cria('f',inicializa(),inicializa()); /* sub-árvore com 'c' */ Arv* a5= cria('c',a3,a4); /* árvore com raiz 'a'*/ Arv* a = cria('a',a2,a5 );
  • 95. Exemplo Alternativamente, a árvore poderia ser criada recursivamente com uma única atribuição, seguindo a sua estrutura. Como isso pode ser feito?
  • 96. Exemplo Arv* a = cria('a', cria('b', inicializa(), cria('d', inicializa(), inicializa()) ), cria('c', cria('e', inicializa(), inicializa()), cria('f', inicializa(), inicializa()) ) );
  • 97. Exibe Conteúdo da Árvore void imprime (Arv* a) { if (!vazia(a)){ printf("%c ", a->info); /* mostra raiz */ imprime(a->esq); /* mostra sae */ imprime(a->dir); /* mostra sad */ } } Como é chamada essa forma de exibição? E para exibir na forma in-fixada? E na pós- fixada?
  • 98. Liberando Memória Arv* libera (Arv* a){ if (!vazia(a)){ libera(a->esq); /* libera sae */ libera(a->dir); /* libera sad */ free(a); /* libera raiz */ } return NULL; }
  • 99. Criação e Liberação Vale a pena notar que a definição de árvore, por ser recursiva, não faz distinção entre árvores e sub-árvores. Assim, cria pode ser usada para acrescentar uma sub-árvore em um ramo de uma árvore, e libera pode ser usada para remover uma sub-árvore qualquer de uma árvore dada.
  • 100. Criação e Liberação Considerando a criação da árvore feita anteriormente, podemos acrescentar alguns nós, com: a->esq->esq = cria('x', cria('y',inicializa(),inicializa()), cria('z',inicializa(),inicializa()) ); E podemos liberar alguns outros, com: a->dir->esq = libera(a->dir->esq); Deixamos como exercício a verificação do resultado final dessas operações.
  • 101. Buscando um Elemento  Essa função tem como retorno um valor booleano (um ou zero) indicando a ocorrência ou não do carácter na árvore. int busca (Arv* a, char c){ if (vazia(a)) return 0; /* não encontrou */ else return a->info==c ||busca(a->esq,c) ||busca(a->dir,c); }
  • 102. Buscando um Elemento  Podemos dizer que a expressão: return c==a->info || busca(a->esq,c) || busca(a->dir,c); é equivalente a: if (c==a->info) return 1; else if (busca(a->esq,c)) return 1; else return busca(a->dir,c);
  • 103. Árvores Genéricas  Como vimos, numa árvore binária o número de filhos dos nós é limitado em no máximo dois.  No caso da árvore genérica, esta restrição não existe.  Cada nó pode ter um número arbitrário de filhos.  Essa estrutura deve ser usada, por exemplo, para representar uma árvore de directórios.  Supor que não há árvore vazia.
  • 105. Representação em C  Devemos levar em consideração o número de filhos que cada nó pode apresentar.  Se soubermos que numa aplicação o número máximo de filhos é 3, podemos montar uma estrutura com 3 campos apontadores, digamos, f1, f2 e f3.  Os campos não utilizados podem ser preenchidos com o valor nulo NULL, sendo sempre utilizados os campos em ordem.  Assim, se o nó n tem 2 filhos, os campos f1 e f2 seriam utilizados, nessa ordem, ficando f3 vazio.
  • 106. Representação em C Prevendo um número máximo de filhos igual a 3, e considerando a implementação de árvores para armazenar valores de caracteres simples, a declaração do tipo que representa o nó da árvore poderia ser: struct arv3 { char val; struct arv3 *f1, *f2, *f3; };
  • 108. Problemas com Representação  Como se pode ver no exemplo, em cada um dos nós que tem menos de três filhos, o espaço correspondente aos filhos inexistentes é desperdiçado.  Além disso, se não existe um limite superior no número de filhos, esta técnica pode não ser aplicável.  O mesmo acontece se existe um limite no número de nós, mas esse limite será raramente alcançado, pois estaríamos tendo um grande desperdício de espaço de memória com os campos não utilizados.  Há alguma solução para contornar tal problema?
  • 109. Solução Uma solução que leva a um aproveitamento melhor do espaço utiliza uma “lista de filhos”: um nó aponta apenas para seu primeiro (prim) filho, e cada um de seus filhos, excepto o último, aponta para o próximo (prox) irmão. A declaração de um nó pode ser: struct arvgen { char info; struct arvgen *prim; struct arvgen *prox; }; typedef struct arvgen ArvGen;
  • 111. Exemplo da Solução  Uma das vantagens dessa representação é que podemos percorrer os filhos de um nó de forma sistemática, de maneira análoga ao que fizemos para percorrer os nós de uma lista simples.  Com o uso dessa representação, a generalização da árvore é apenas conceitual, pois, concretamente, a árvore foi transformada em uma árvore binária, com filhos esquerdos apontados por prim e direitos apontados por prox.
  • 112. Criação de uma ÁrvoreGen ArvGen* cria (char c) { ArvGen *a =(ArvGen *)malloc(sizeof(ArvGen)); a->info = c; a->prim = NULL; a->prox = NULL; return a; }
  • 113. Inserção  Como não vamos atribuir nenhum significado especial para a posição de um nó filho, a operação de inserção pode inserir a sub- árvore em qualquer posição.  Vamos optar por inserir sempre no início da lista que, como já vimos, é a maneira mais simples de inserir um novo elemento numa lista ligada. void insere (ArvGen* a, ArvGen* f) { f->prox = a->prim; a->prim = f; }
  • 114. Exemplo Criação ÁrvoreGen  Com as funções cria e insere como construir a árvore abaixo?
  • 115. Exemplo Criação ÁrvoreGen /* cria nós como folhas */ ArvGen* a = cria('a'); /* monta a hierarquia */ ArvGen* b = cria('b'); insere(c,d); ArvGen* c = cria('c'); insere(b,e); ArvGen* d = cria('d'); insere(b,c); ArvGen* e = cria('e'); insere(i,j); ArvGen* f = cria('f'); insere(g,i); ArvGen* g = cria('g'); insere(g,h); ArvGen* h = cria('h'); insere(a,g); ArvGen* i = cria('i'); insere(a,f); ArvGen* j = cria('j'); insere(a,b); ...
  • 116. Impressão (pré-ordem) void imprime (ArvGen* a) { ArvGen* p; printf("%cn",a->info); for (p=a->prim; p!=NULL; p=p- >prox) imprime(p); }
  • 117. Busca de Elemento int busca (ArvGen* a, char c) { ArvGen* p; if (a->info==c) return 1; else { for (p=a->prim; p!=NULL; p=p->prox) { if (busca(p,c)) return 1; } } return 0; }
  • 118. Liberação de Memória O único cuidado que precisamos tomar na programação dessa função é a de liberar as subárvores antes de liberar o espaço associado a um nó (isto é, usar pós-ordem).
  • 119. Liberação de Memória void libera (ArvGen* a){ ArvGen* p = a->prim; while (p!=NULL) { ArvGen* t = p->prox; libera(p); p = t; } free(a); }
  • 120. Capitulo IV - Ordenação Docente: Dikiefu Fabiano, Msc
  • 121. Sumário 4. Ordenação Interna 4.1 Ordenação por Selecção 4.2 Ordenação por Inserção 4.3 Shellsort 4.4 Quicksort 4.5 Heapsort 121
  • 122. Conceitos Básicos  Ordenar: processo de rearranjar um conjunto de objectos em uma ordem ascendente ou descendente.  A ordenação visa facilitar a recuperação posterior de itens do conjunto ordenado.  A maioria dos métodos de ordenação é baseada em comparações das chaves.  Existem métodos de ordenação que utilizam o princípio da distribuição. . 122
  • 123. Conceitos Básicos  Notação utilizada nos algoritmos: • Os algoritmos trabalham sobre os registos de um arquivo. • Cada registo possui uma chave utilizada para controlar a ordenação. • Podem existir outros componentes em um registo
  • 124. Conceitos básicos Um método de ordenação é estável se a ordem relativa dos itens com chaves iguais não se altera durante a ordenação. • Alguns dos métodos de ordenação mais eficientes não são estáveis. • A estabilidade pode ser forçada quando o método é não-estável. • Sedgewick (1988) sugere agregar um pequeno índice a cada chave antes de ordenar, ou então aumentar a chave de alguma outra forma. 124
  • 125. Classificação dos métodos de ordenação 1. Ordenação interna: arquivo a ser ordenado cabe todo na memória principal. 2. Ordenação externa: arquivo a ser ordenado não cabe na memória principal. Diferenças entre os métodos:  Em um método de ordenação interna, qualquer registo pode ser imediatamente cessado.  Em um método de ordenação externa, os registos são cessados sequencialmente ou em grandes blocos. 125
  • 126. Classificação dos métodos de ordenação interna  Métodos simples: • Adequados para pequenos arquivos. • Produzem programas pequenos.  Métodos eficientes: • Adequados para arquivos maiores. • Usam menos comparações. • As comparações são mais complexas nos detalhes.  Métodos simples são mais eficientes para pequenos arquivos. 126
  • 127. Ordenação por Selecção Um dos algoritmos mais simples de ordenação.  Algoritmo:  Seleccione o menor item do vector.  Troque-o com o item da primeira posição do vector.  Repita essas duas operações com os n − 1 itens restantes, depois com os n − 2 itens, até que reste apenas um elemento. • As chaves em negrito sofreram uma troca entre si. 127
  • 128. Ordenação por Selecção Vantagens:  Custo linear no tamanho da entrada para o número de movimentos de registos.  É o algoritmo a ser utilizado para arquivos com registos muito grandes.  É muito interessante para arquivos pequenos. Desvantagens:  O facto de o arquivo já estar ordenado não ajuda em nada, pois o custo continua quadrático.  O algoritmo não é estável. 128
  • 129. Ordenação por Selecção void selectionSort(int vector[],int tam) { int i, j, min, aux; for(i=0; i<tam-1; i++) { min = i; aux = vector[i]; for(j=i+1; j<tam; j++) { if (vector[j] < aux) { min=j; aux=vector[j]; } } aux = vector[i]; vector[i] = vector[min]; vector[min] = aux; } } 129
  • 130. Código da ordenação SelectionSort com strings void ordenar_seleccao() { int j; for(i=0; i<n-1; i++) { for(j=i+1; j<n; j++) { if(strcmpi(vector[i], vector[j])>0) { strcpy(aux_char, vector[i]); strcpy(vector[i], vector[j]); strcpy(vector[j], aux_char); } } } }
  • 131. Bubble Sort  O bubble sort, ou ordenação por flutuação (literalmente "por bolha"), é um algoritmo de ordenação dos mais simples. A ideia é percorrer o vector diversas vezes, a cada passagem fazendo flutuar para o topo o maior elemento da sequência. Essa movimentação lembra a forma como as bolhas em um tanque de água procuram seu próprio nível, e disso vem o nome do algoritmo
  • 132. void bubble( int v[], int qtd ) { int i; int j, aux, k = qtd - 1 ; for(i = 0; i < qtd; i++) { for(j = 0; j < k; j++) { if(v[j] > v[j+1]) { aux = v[j]; v[j] = v[j+1]; v[j+1]=aux; } } } }
  • 133. void swapbubble( int v[], int i ) { aux = v[i]; v[i] = v[i+1]; v[i+1]=aux; } void bubble( int v[], int qtd ) { int i,j; for( j = 0; j < qtd; j++ ) { for( i = 0; i < qtd - 1; i++ ) { if( v[i] > v[i+1] ) { swapbubble( v, i ); } } } }
  • 134.  Método de ordenação Bolha com ordenação de strings. void bubble(int v[], int qtd){ int i, trocou; char aux; do { qtd--; trocou = 0; for(i = 0; i < qtd; i++) if(strcasecmp(v[i],v[i + 1])>0) { strcpy(aux, v[i]); strcpy(v[i], v[i + 1]); strcpy(v[i + 1], aux); trocou = 1; } }while(trocou==1); }
  • 135. Ordenação por Inserção  Método preferido dos jogadores de cartas.  Algoritmo: • Em cada passo a partir de i =2 faça: • Seleccione o i-ésimo item da sequência fonte. • Coloque-o no lugar apropriado na sequência destino de acordo com o critério de ordenação. • As chaves em negrito representam a sequência destino. 135
  • 136. Ordenação por Inserção void insertionSort(int vector[], int tam) { int i, j; int key; for (j = 1; j < tam; ++j) { key = vector[j]; i = j - 1; while (vector[i] > key && i >= 0) { vector[i+1] = vector[i]; --i; } vector[i+1] = key; } } A colocação do item no seu lugar apropriado na sequência destino é realizada movendo-se itens com chaves maiores para a direita e então inserindo o item na posição deixada vazia. 136
  • 137. Ordenação por Inserção Considerações sobre o algoritmo:  O processo de ordenação pode ser terminado pelas condições: • Um item com chave menor que o item em consideração é encontrado. • O final da sequência destino é atingido à esquerda.  Solução: • Utilizar um registo sentinela na posição zero do vector. 137
  • 138. Ordenação por Inserção  O número mínimo de comparações e movimentos ocorre quando os itens estão originalmente em ordem.  O número máximo ocorre quando os itens estão originalmente na ordem reversa.  É o método a ser utilizado quando o arquivo está “quase” ordenado.  É um bom método quando se deseja adicionar uns poucos itens a um arquivo ordenado, pois o custo é linear.  O algoritmo de ordenação por inserção é estável. 138
  • 139. Shellsort Proposto por Shell em 1959.  É uma extensão do algoritmo de ordenação por inserção.  Problema com o algoritmo de ordenação por inserção: • Troca itens adjacentes para determinar o ponto de inserção. • São efectuadas n − 1 comparações e movimentações quando o menor item está na posição mais à direita no vector.  O método de Shell contorna este problema permitindo trocas de registos distantes um do outro. 139
  • 140. Shellsort  Os itens separados de h posições são rearranjados.  Todo h-ésimo item leva a uma sequência ordenada.  Tal sequência é dita estar h-ordenada.  Exemplo de utilização: • Quando h = 1 Shellsort corresponde ao algoritmo de 140
  • 141. Shellsort • Como escolher o valor de h:  Sequência para h: h(s) = 3h(s − 1) + 1, para s > 1 h(s) = 1, para s = 1.  Knuth (1973, p. 95) mostrou experimentalmente que esta sequência é difícil de ser batida por mais de 20% em eficiência.  A sequência para h corresponde a 1, 4, 13, 40, 121, 364, 1.093, 3.280, . . . 141
  • 142. Shellsort void shellsort ( Int v [ ] , int n) { int i,j,x,h = 1; do h = h *3 + 1; while (h < n) ; do { h /= 3; for ( i = h + 1; i <= n; i ++) { x = v[ i ] ; j = i ; while (v[ j − h] > x > 0) { v[ j ] = v[ j − h] ; j −= h; i f ( j <= h) break; } v[ j ] = x; } } while (h != 1) ; }  A implementação do Shellsort não utiliza registos sentinelas.  Seriam necessários h registos sentinelas, uma para cada h-ordenação. 142
  • 143. Shellsort  Vantagens: • Shellsort é uma óptima opção para arquivos de tamanho moderado. • Sua implementação é simples e requer uma quantidade de código pequena.  Desvantagens: • O tempo de execução do algoritmo é sensível à ordem inicial do arquivo. • O método não é estável, 143
  • 144. Quicksort  Proposto por Hoare em 1960 e publicado em 1962.  É o algoritmo de ordenação interna mais rápido que se conhece para uma ampla variedade de situações.  Provavelmente é o mais utilizado.  A ideia básica é dividir o problema de ordenar um conjunto com n itens em dois problemas menores.  Os problemas menores são ordenados independentemente.  Os resultados são combinados para produzir a solução final. 144
  • 145. Quicksort  A parte mais delicada do método é relativa ao método partição.  O vector v[esq..dir ] é rearranjado por meio da escolha arbitrária de um pivô x.  O vector v é particionado em duas partes: • A parte esquerda com chaves menores ou iguais a x. • A parte direita com chaves maiores ou iguais a x. 145
  • 146. Quicksort Algoritmo para o particionamento: 1. Escolha arbitrariamente um pivô x. 2. Percorra o vector a partir da esquerda até que v[i] x. 3. Percorra o vector a partir da direita até que v[j] x. 4. Troque v[i] com v[j]. 5. Continue este processo até os apontadores i e j se cruzarem. • Ao final, o vector v[esq..dir ] está particionado de tal forma que: • Os itens em v[esq], v[esq + 1], . . . , v[j] são menores ou iguais a x. • Os itens em v[i], v[i + 1], . . . , v[dir ] são maiores ou iguais a x. 146
  • 147. Quicksort • Ilustração do processo de partição:  O pivô x é escolhido como sendo v[(i+j) / 2].  Como inicialmente i = 1 e j = 6, então x = v[3] = D.  Ao final do processo de partição i e j se cruzam em i = 3e j = 2. 147
  • 148. Quicksort void swap(int* a, int* b) { int tmp; tmp = *a; *a = *b; *b = tmp; } int partition(int vec[], int left, int right) { int i, j; i = left; for (j = left + 1; j <= right; ++j) { if (vec[j] < vec[left]) { ++i; swap(&vec[i], &vec[j]); } } swap(&vec[left], &vec[i]); return i; } 148
  • 149. Quicksort Método ordena e algoritmo Quicksort : void quickSort(int vec[], int left, int right) { int r; if (right > left) { r = partition(vec, left, right); quickSort(vec, left, r - 1); quickSort(vec, r + 1, right); } } 149
  • 150.  Implementação usando 'fat pivot': void sort(int array[], int begin, int end) { int pivot = array[begin]; int i = begin + 1, j = end, k = end, t; while (i < j) { if (array[i] < pivot) i++; else if (array[i] > pivot) { j--; k--; t = array[i]; array[i] = array[j]; array[j] = array[k]; array[k] = t;
  • 151. } else { j--; swap(array[i], array[j]); } } i--; swap(array[begin], array[i]); if (i - begin > 1) sort(array, begin, i); if (end - k > 1) sort(array, k, end); }
  • 152. Lembrando que quando você for chamar a função recursiva terá que chamar a mesma da seguinte forma ordenar_quicksort(0,n-1). O 0(zero) serve para o início receber a posição zero do vector e o fim será o tamanho do vector -1.
  • 153. void ordenar_quicksort(int ini, int fim) { int i = ini, f = fim; char pivo[50]; strcpy(pivo,vector[(ini+fim)/2]); if (i<=f) { while (strcmpi(vector[i],pivo)<0) i++; while (strcmpi(vetor[f],pivo)>0) f--; if (i<=f) { strcpy (aux_char,vetor[i]); strcpy (vetor[i],vetor[f]); strcpy (vetor[f],aux_char); i++; f--; } }
  • 154. if (f>ini) ordenar_quicksort_nome(ini,f); if (i<fim) ordenar_quicksort_nome(i,fim); }
  • 155. Quicksort Exemplo do estado do vector em cada chamada recursiva do procedimento Ordena: • O pivô é mostrado em negrito. 155
  • 156. Quicksort  Vantagens: • É extremamente eficiente para ordenar arquivos de dados. • Necessita de apenas uma pequena pilha como memória auxiliar.  Desvantagens: • Sua implementação é muito delicada e difícil: Um pequeno engano pode levar a efeitos inesperados para algumas entradas de dados. • O método não é estável. 156
  • 157. Heapsort  Possui o mesmo princípio de funcionamento da ordenação por selecção.  Algoritmo: 1. Seleccione o menor item do vector. 2. Troque-o com o item da primeira posição do vector. 3. Repita estas operações com os n − 1 itens restantes, depois com os n − 2 itens, e assim sucessivamente.  O custo para encontrar o menor (ou o maior) item entre n itens é n − 1 comparações.  Isso pode ser reduzido utilizando uma fila de prioridades. 157
  • 158. Heaps  É uma sequência de itens com chaves c[1], c[2], . . . , c[n], tal que: c[i] c[2i], c[i] c[2i + 1], para todo i = 1, 2, . . . , n/2.  A definição pode ser facilmente visualizada em uma árvore binária completa:  árvore binária completa: • Os nós são numerados de 1 a n. • O primeiro nó é chamado raiz. • O nó k/2 é o pai do nó k, para 1 < k n. • Os nós 2k e 2k + 1 são os filhos à esquerda e à direita do nó k, para 1 k k/2 158
  • 159. Heaps  As chaves na árvore satisfazem a condição do heap.  As chaves em cada nó são maiores do que as chaves em seus filhos.  A chave no nó raiz é a maior chave do conjunto.  Uma árvore binária completa pode ser representada por um arranjo:  A representação é extremamente compacta.  Permite caminhar pelos nós da árvore facilmente.  Os filhos de um nó i estão nas posições 2i e 2i + 1.  O pai de um nó i está na posição i/2. 159
  • 160. Heaps  Na representação do heap em um arranjo, a maior chave está sempre na posição 1 do vector.  Os algoritmos para implementar as operações sobre o heap operam ao longo de um dos percursos da árvore.  Um algoritmo elegante para construir o heap foi proposto por Floyd em 1964.  O algoritmo não necessita de nenhuma memória auxiliar.  Dado um vector v[1], v[2], . . . , v[n].  Os itens v[n/2 + 1], v[n/2 + 2], . . . , v[n] formam um heap: • Neste intervalo não existem dois índices i e j tais que j = 2i ou j = 2i + 1. 160
  • 161. Heapsort void heapsort(tipo a[], int n) { int i = n/2, pai, filho; tipo t; for (;;) { if (i > 0) { i--; t = a[i]; } else { n--; if (n == 0) return; t = a[n]; a[n] = a[0]; } 161
  • 162. pai = i; filho = i*2 + 1; while (filho < n) { if ((filho + 1 < n) && (a[filho + 1] > a[filho])) filho++; if (a[filho] > t) { a[pai] = a[filho]; pai = filho; filho = pai*2 + 1; } else break; } a[pai] = t; } } 162
  • 163. Heapsort Algoritmo:  Os itens de v[4] a v[7] formam um heap.  O heap é estendido para a esquerda (esq = 3), englobando o item v[3], pai dos itens v[6] e v[7].  A condição de heap é violada: • O heap é refeito trocando os itens D e S.  O item R é incluindo no heap (esq = 2), o que não viola a condição de heap.  O item O é incluindo no heap (esq = 1).  A Condição de heap violada: • O heap é refeito trocando os itens O e S, encerrando o processo. 163
  • 164. Heapsort Exemplo da operação de aumentar o valor da chave do item na posição i: O tempo de execução do procedimento AumentaChave em um item do heap é O(log n). 164
  • 165. Construção de Heap Algoritmo: 1. Construir o heap. 2. Troque o item na posição 1 do vector (raiz do heap) com o item da posição n. 3. Use o procedimento Refaz para reconstituir o heap para os itens v[1], v[2], . . . , v[n − 1]. 4. Repita os passos 2 e 3 com os n − 1 itens restantes, depois com os n − 2, até que reste apenas um item.
  • 166. Heapsort Exemplo de aplicação do Heapsort : 1 2 3 4 5 6 7 S R O E N A D R N O E D A S O N A E D R N E A D O E D A N D A E A D • O caminho seguido pelo procedimento Refaz para reconstituir a condição do heap está em negrito. • Por exemplo, após a troca dos itens S e D na segunda linha da Figura, o item D volta para a posição 5, após passar pelas posições 1 e 2.
  • 167. Referências 1. Levitin, Anany “Introduction to the design and analysis of algorithm” Addison-Wesley 2003 2. Pedro Neto, João “Programação, algoritmo e estrutura de dados” escola editora 2008 3. Damas, Luís “Programação em C” … 4. Ziviani, Nivio e Botelho, Fabiano Cupertino, “Projecto de Algoritmos com implementação em Pascal e C” editora Thomson, 2007 5. http://www.icmc.usp.br/~sce182/arvore.html 6. http://w3.ualg.pt/~hshah/ped 7. http://www.liv.ic.unicamp.br/~bergo/mc202/