Introdução à Ordenação de Vetores

O que é um vetor?

Vetor é uma estrutura de dados que serve para substituir várias variáveis. Para um problema pequeno onde desejo armazenar dois inteiros e tirar o MMC deles eu posso usar duas variáveis: n1 e n2. Mas existem casos em que seria um número muito grande de variáveis (e em alguns deles nem sabemos ao certo, porque faremos uma alocação a partir de um número que o usuário pedir), por isso vetores são extremamente úteis.

No que consiste a ordenação?

Os algoritmos de ordenação tem como objetivo permutar uma seqüência n_1,n_2,n_3,n\_{1}, n\_{2}, n\_{3}, \ldots{} de forma que n_1n_2n_3n\_{1} \leq{} n\_{2} \leq{} n\_{3} \leq{} \ldots{}. A ordenação não precisa ser exatamente de um vetor, mas vetor é geralmente a estrutura que usamos para guardar uma lista de números para podermos ordená-los.

Por que ordenar?

Citando o Cormen:

  • Às vezes, a necessidade de ordenar informações é inerente a uma aplicação. Por exemplo, para preparar os extratos de clientes, os bancos precisam ordenar os cheques pelo número do cheque.
  • Os algoritmos freqüentemente usam a ordenação como uma sub-rotina chave. Por exemplo, um programa que apresenta objetos gráficos dispostos em camadas uns sobre os outros talvez tenha de ordenar os objetos de acordo com uma relação “acima”, de forma a poder desenhar esses objetos de baixo para cima.
  • Existe uma ampla variedade de algoritmos de ordenação, e eles empregam um rico conjunto de técnicas. De fato, muitas técnicas importantes usadas ao longo do projeto de algoritmos são representadas no corpo de algoritmos de ordenação que foram desenvolvidos ao longo dos anos. Desse modo, a ordenação também é um problema de interesse histórico.

Recursão

A recursão é uma das técnicas mais simples e úteis que existem para usarmos em nossos algoritmos. Consiste em uma função (denominada recursiva) chamar a si mesmo, até que o retorno seja trivial. Resolvi abordá-la aqui porque alguns algoritmos que estudaremos mais para frente usam funções recursivas.

Gostaria de, antes de falar sobre o assunto, contar um pouco da minha história com recursão, porque foi meu início no mundo dos algoritmos.

Junho de 2004, tinha eu 13 anos e fui premiado com medalha de ouro na modalidade iniciação da Olimpíada de Informática. Fui convidado para o curso de introdução a programação na UNICAMP e, extremamente geek como eu era (e ainda sou), falei para os professores que já programava em C e então eles sugeriram que eu fosse para o curso de programação avançada.

No entanto, eu ainda achava muito complicado os algoritmos da modalidade de programação da OBI, por isso pedi pra ficar num “meio-termo”. Eles toparam numa boa e com isso passei a ter aulas com um monitor excelente (aluno da UNICAMP) chamado Ribamar. Esse cara foi extremamente importante para minha iniciação na programação de verdade.

Na primeira tarde que tive aula com ele, ele perguntou se eu sabia o que era recursão. Respondi que não e, a partir daquele dia e depois de ele me ensinar também de grafos e programação dinâmica, além de me apresentar o Slackware, me tornei um amante de algoritmos. A recursão, portanto, mesmo sendo algo simples, é um assunto especial pra mim porque representa a mudança de “nível”.

Então, mãos à obra!


Em matemática, o número fatorial de nn é igual a: n×n1×n2×2×1n \times{} n-1 \times{} n-2 \times{} \ldots{} 2 \times{} 1.

Logo, por exemplo, 5!5! (cinco fatorial) seria igual a: 5×4×3×2×1=1205 \times{} 4 \times{} 3 \times{} 2 \times{} 1 = 120.


Um exemplo bom e simples de recursão é um algoritmo para determinar números fatoriais:

função fatorial (n)
se n=1n = 1, então
  retorna 11
senão
  retorna n×fatorial(n1)n \times{} fatorial(n-1)
fim-se
fim-função

Domínio de nossa função: nN/n1n \in{} \mathbf{N} / n \geq{} 1.

Qual o custo desse algoritmo?

Vamos abrir um grande parênteses aqui até a próxima linha horizontal para descobrir qual o custo do nosso algoritmo antes de continuar com a conversa sobre recursão e relembrar/reforçar o post sobre Análise de Algoritmos. Vou colocar o número de vezes que cada instrução é executada, usando o esquema que será o padrão para as próximas vezes que veremos custos:

Número da linha: Número de vezes que é executada..

  1. nn
  2. n1n-1
  3. 11
  4. n1n-1
  5. n1n-1

T(n)=(n)+(n1)+(1)+(n1)+(n1)=4n2T(n) = (n) + (n-1) + (1) + (n-1) + (n-1) = 4n - 2

Uma função linear, o tipo de algoritmo mais simples que podemos encontrar, com excessão dos que são uma função constante. Mas o parênteses na verdade não serviu só pra isso. Eu queria aproveitar pra escovar uns bits de nosso código. Você percebeu que o primeiro condicional é executado n1n-1 vezes, mas só entramos nele uma vez? Então vamos inverter nosso condicional.

função fatorial (n)
se n2n \geq{} 2, então
  retorna n×fatorial(n1)n \times{} fatorial(n-1)
senão
  retorna 11
fim-se
fim-função

Novo custo

  1. nn
  2. n1n-1
  3. n1n-1
  4. 11
  5. 11

T(n)=(n)+(n1)+(n1)+(1)+(1)=3nT(n) = (n) + (n-1) + (n-1) + (1) + (1) = 3n

Claro que continua uma função linear, não houve nenhuma grande mudança. Os dois continuam com a mesma ordem de crescimento e tal… 4n+24n+2 comparado com 3n3n é uma diferença pequena, mas essa solução ficou bem mais elegante. ;) Poxa, diminuímos o custo do algoritmo em 14\frac{1}{4}! Hehehe…

Agora, antes de continuar, só vamos definir a notação assintótica desse nosso novo custo!

A fórmula do Θ\Theta{} é: 0c_1g(n)f(n)c_2g(n)0 \leq{} c\_{1} g(n) \leq{} f(n) \leq{} c\_{2} g(n)

Substituindo pela nossa função, temos: c_1n3nc_2nc\_{1}n \leq{} 3n \leq{} c\_{2}n. É trivial, que podemos escolher para as duas constantes c_1=c_2=3c\_{1}=c\_{2}=3 e para n_0=0n\_{0}=0. Com isso pretendi mostrar-lhes uma conclusão óbvia que no outro artigo não tinha mostrado para não complicar muito: uma função reta (linear) pertence sempre a notação Θ(n)\Theta{}(n) e uma função quadrática pertence sempre a notação Θ(n2)\Theta{}(n^{2}) (ora, façam um gráfico das funções e vejam se isso não é óbvio!). Mas vamos aprendendo mais sobre análise de algoritmos com o tempo…


Bom… Continuando com a recursão… Nossa função cria um loop consigo mesma, ao invés de usar um para (for) ou enquanto (while). Ela se repete diminuindo um de nn a cada iteração, até que chegue ao valor mínimo 11 aonde a resposta é trivial: 11.

O que é necessário para criarmos uma recursão? Apenas um ponto de parada! Temos que tomar cuidado para não criarmos loops infinitos cuidando sempre com cada entrada que o usuário pode colocar. Nesse caso, eu determinei que o domínio da função é nN/n1n \in{} \mathbf{N} / n \geq{} 1. Se o cara colocasse n=0n=0, minha função iria diminuindo infinitamente… e nunca iria retornar nada!

Para fazer a recursão portanto, precisamos analisar o domínio de nossa função e mais: precisamos conhecer um valor (que vai ser o limite; no caso do fatorial, o valor que sabíamos é que 1!=11! = 1).

Acredito que vocês tenham achado tudo simples e que não tenham problema com isso. Funções recursivas vão ser extremamente úteis para nós nos próximos artigos. Vou finalizar mostrando-lhes alguns casos básicos de algoritmos em que podemos usar a recursão:

Números de Fibonacci

função fibonacci (n)
se n3n \geq{} 3, então
  retorna fibonacci(n1)+fibonacci(n2)fibonacci(n-1) + fibonacci(n-2)
senão
  retorna 11
fim-se
fim-função

Domínio de fibonacci(n): nN/n1n \in{} N / n \geq{} 1

Depois descobriremos como calcular os números de Fibonacci mais rápido, mas por enquanto nosso objetivo é a recursão!

Substituir um loop

Vamos supor que você quer imprimir os números de n a 1 e esqueceu a sintaxe do para… :D

função imprimeate_ (n)
imprima nn
se n>1n>1, então
  imprime_ate(n-1)
fim-se
fim-função

Domínio de imprime_ate(n): nN/n1n \in{} N / n \geq{} 1

Todo loop pode ser uma recursão e tem alguns que ficam bem mais fáceis se forem! Nesse caso, é claro que seria mais simples usarmos um para!

Outros exemplos, para concluir

  • Ordenação por Intercalação (Merge Sort)
  • Busca em Profundidade (Depth-First Search) em Grafos
  • Union/Find
  • … entre vários outros casos!

Análise de Algoritmos

Analisar um algoritmo é prever o que o algoritmo irá precisar. Às vezes o hardware é importante, mas acho que o que acontece com mais freqüência, ao menos em olimpíadas, maratonas e problemas em casa, é precisarmos medir o tempo que ele irá demorar.

Eu expliquei em algum dos artigos anteriores que o tempo de um algoritmo depende geralmente do tamanho de sua entrada. Com este artigo, pretendo explicar como analisamos um algoritmo baseado nesse tamanho de sua entrada para compará-lo com outros algoritmos e ter uma noção de quanto tempo ele vai demorar.

Para o entendimento ficar mais fácil, vamos partir do seguinte algoritmo (que vamos chamar de Algoritmo 1):

para i \leftarrow{} 1 até n, faça
para j \leftarrow{} 1 até i, faça
  imprima i ×\times{} j ×\times{} n
fim-para
fim-para

O que este algoritmo faz é, depois de receber a entrada nn do usuário, imprimir o produto de nn com todos dois números ii e jj, tal que jinj \leq{} i \leq{} n.

Para medir o custo do algoritmo, nossa análise consistirá em ver quantas vezes cada passo é executado. Mediremos o custo de cada linha (cada passo a ser executado), sempre em função de n, que para este algoritmo é a variável mais importante (aliás, a única variável). Por isso o pseudocódigo do Algoritmo 1 está com suas linhas numeradas. Vamos analisar…

  • Linha 1: Será executada n+1n + 1 vezes.
  • Linha 2: Será executada n×_i=1n+nn \times{} \sum\_{i=1}^{n} + n vezes.
  • Linha 3: Será executada n×_i=1nn \times{} \sum\_{i=1}^{n} vezes.
  • Linhas 4 e 5: Não tem custo. :)

Por que estes números de execução?

Se você já entendeu por que cada passo é executado este número de vezes, pode pular essa parte (continuar a ler a partir da linha horizontal).

Linha 1

O loop para voltará para si mesmo nn vezes, isso é, testará novamente sua condicional e incrementará um. Por sempre testar um condicional, no final ele terá que testar novamente para dizer que já passou de nn. Por isso, ele será executado n+1n+1 vezes, ao invés de simplesmente nn.

Linha 2

Este loop para será executado um número de vezes variável (ii), que irá de 11 a nn. Portanto, ele será executado duas vezes (1 mais “o último condicional”) no primeiro loop de ii, três (2 mais “o último condicional”) no segundo, e por aí vai. Com isso, ele será executado o número de vezes que equivale a soma de 11 a nn, mais nn que são “os últimos condicionais”.

Linha 3

Exatamente o mesmo número que a Linha 2, mas sem “os últimos condicionais” (n-n).


Imprimir algo na tela pode demorar mais do que fazer uma operação, mas a análise de algoritmos é uma coisa bem rústica. Desprezamos todas as constantes, com isso só levando a sério a informação importante: neste caso, apenas nn. Então agora, vamos escrever o tempo de execução do algoritmo, que é a soma dos tempos de execução para cada instrução executada.

T(n)=(n+1)+(_i=1n+n)+(_i=1n)T(n) = (n + 1) + (\sum\_{i=1}^{n} + n) + (\sum\_{i=1}^{n})

Os parênteses não são necessários, mas coloquei para ajudar na visualização separando o custo de cada instrução.

Simplificando esta operação, teremos:

T(n)=n2+3nT(n) = n^{2} + 3n, uma função quadrática.

Ordem de Crescimento

Como eu já disse antes, descobrir o custo de um algoritmo é uma coisa feita sem precisão, porque para entradas realmente grandes (que são casos onde precisamos do computador!) as constantes não importam. Agora vamos determinar a ordem de crescimento de um algoritmo resgatando do nosso algoritmo apenas o valor mais importante, o maior expoente de nn nele, neste caso, n2n^{2}. Se tivéssemos 2n22 n^{2}, por exemplo, também usaríamos apenas n2n^{2} porque o 22 que multiplica também é desprezível!

Vamos agora aprender como representar o custo desse algoritmo usando notações assintóticas com a ordem de crescimento do algoritmo.

Se você não entendeu alguma coisa aí em cima, sugiro reler antes de continuar…

Notações Assintóticas

Sugestão

Principalmente para pessoas pouco habituadas com matemática, essa parte é difícil e cansativa. Quando eu comecei a aprender isto, talvez por causa da matemática tão básica que é ensinada na escola, eu não entendia nada… Mas só quero dar uma dica: se você não entender direito ou achar muito complicado, pule para a próxima linha horizontal ao invés de desistir e dizer que “algoritmos são muito difíceis”. Tentei fazer o artigo para você poder pular essa parte e mesmo assim não parar no estudo dos algoritmos… Depois, com o tempo, você vai aprendendo isso.

As notações que usamos para descrever o tempo de execução de um algoritmo são cinco:

  • Θ\Theta{}
  • OO
  • Ω\Omega{}
  • oo
  • ω\omega{}

Embora essas notações sejam conjuntos, usamos o sinal de igualdade (=) para expressar que f(n)f(n) pertence a algum deles, ao invés de usar o sinal de pertinência (\in{}).

Vou explicá-las, omitindo alguns fatos para tentar facilitar o entendimento, porque eu acho que analisar algoritmos é meio complicado e nessa parte é extremamente difícil ser didático. Mas se você realmente se interessar, você pode me enviar um comentário pedindo mais um artigo sobre isso (e eu terei o prazer de até pesquisar para informar-lhes mais) ou então leia o Capítulo 3 do livro Algoritmos: Teoria e Prática, que acredito que seja bem completo. Gostaria de enfatizar aqui que meu objetivo com essa série é tornar uma introdução a algoritmos simples e não ser uma referência, como é o objetivo, por exemplo, do livro do Cormen [et al].

A notação Θ\Theta{}

Lê-se “theta de gê de ene”.

Θ(g(n))=f(n)\Theta{}(g(n)) = f(n), se existem constantes positivas c_1c\_{1}, c_2c\_{2} e n_0n\_{0} tais que 0c_1g(n)f(n)c_2g(n)0 \leq{} c\_{1} g(n) \leq{} f(n) \leq{} c\_{2} g(n) para todo nn_0n \geq{} n\_{0}.

A notação OO

Lê-se “ó maiúsculo de gê de ene”. Para quando há apenas um limite assintótico superior.

O(g(n))=f(n)O(g(n)) = f(n), se existem constantes positivas cc e n_0n\_{0} tais que 0f(n)cg(n)0 \leq{} f(n) \leq{} cg(n) para todo nn_0n \geq{} n\_{0}.

A notação Ω\Omega{}

Lê-se “omega maiúsculo de gê de ene”. Para quando há apenas um limite assintótico inferior.

Ω(g(n))=f(n)\Omega{}(g(n)) = f(n), se existem constantes positivas cc e n_0n\_{0} tais que 0cg(n)f(n)0 \leq{} cg(n) \leq{} f(n) para todo nn_0n \geq{} n\_{0}.

A notação oo

Lê-se “ó minúsculo de gê de ene”. Para quando há apenas um limite assintótico superior, sem permitir que f(n)=cg(n)f(n) = cg(n). Utiliza-se a notação oo para denotar um limite superior que não é assintoticamente restrito.

o(g(n))=f(n)o(g(n)) = f(n), se para qualquer constante c>0c > 0, existe uma constante n_0>0n\_{0} > 0 tal que 0f(n)cg(n)0 \leq{} f(n) \leq{} cg(n) para todo nn_0n \geq{} n\_{0}.

A notação ω\omega{}

Lê-se “omega minúsculo de gê de ene”. Para quando há apenas um limite assintótico inferior, sem permitir que cg(n)=f(n)cg(n) = f(n). Utiliza-se a notação ω\omega{} para denotar um limite inferior que não é assintoticamente restrito.

ω(g(n))=f(n)\omega{}(g(n)) = f(n), se para qualquer constante c>0c > 0, existe uma constante n_0>0n\_{0} > 0 tal que 0cg(n)f(n)0 \leq{} cg(n) \leq{} f(n) para todo nn_0n \geq{} n\_{0}.

Para analisar problemas mais complexos como, por exemplo, recorrências, existem métodos bastante interessantes, como o Teorema Mestre que o Cormen apresenta no Capítulo 4. É uma boa leitura pra quem se interessou.


Podemos criar várias comparações entre estas funções, mas isto não vem ao caso. O importante é saber em que notação a nossa função se encontra. Com o tempo vamos compreendendo melhor essas fórmulas.

Vamos relembrar o custo de nosso algoritmo… T(n)=n2+3nT(n) = n^{2} + 3n.

Vamos ver em que notação ele pode se encaixar, sabendo que g(n)g(n) seria a ordem de crescimento (parte importante) do nosso custo; no caso, n2n^{2}.

Testamos primeiro se ele encaixa na função Θ(n2)\Theta{}(n^{2}). Vamos substituir f(n)f(n) e g(n)g(n) (naquela função ali em cima, onde diz A notação Θ\Theta{}) pelos valores que conhecemos.

c_1n2n2+3nc_2n2c\_{1}n^{2} \leq{} n^{2} + 3 n \leq{} c\_{2} n^{2}

Se dividirmos tudo por n2n^{2}, obteremos:

c_11+3nc_2c\_{1} \leq{} 1 + \frac{3}{n} \leq{} c\_{2}

Agora separaremos as inequações.

Inequação 1: c_11+3nc\_{1} \leq{} 1 + \frac{3}{n}

Inequação 2: 1+3nc_21 + \frac{3}{n} \leq{} c\_{2}

Para satisfazer a Inequação 1, podemos quase automaticamente perceber que para qualquer n1n \geq{} 1, é válido c_1=1c\_{1} = 1 (ora, por mais que 3n\frac{3}{n} chegue perto de 0, sempre ainda vamos ter a constante 1 adicionada a ele). Para satisfazer a Inequação 2, podemos perceber facilmente que para qualquer n1n \geq{} 1, é válido c_2=4c\_{2} = 4 (a função só tende a diminuir a partir que nn vai aumentando e com n=1n=1, c_2=4c\_{2}=4). Com isso, agora chegamos as três constantes que precisávamos.

n_0n\_{0} (o menor valor de nn) =1= 1; c_1=1c\_{1} = 1; c_2=4c\_{2} = 4.

Logo, concluímos que f(n)=n2+3n=Θ(n2)f(n) = n^{2} + 3n = \Theta{}(n^{2}). Uma função que pertence a Θ\Theta{}, tem um limite assintótico superior e inferior e, portanto, pertenceria também a O(n2)O(n^{2}) e Ω(n2)\Omega{}(n^{2}), mas nem é necessário testar os outros valores porque já identificamos nossa função como “theta de ene ao quadrado”, que é a função mais “retinha” que podemos esperar.

Bom… Nos próximos artigos, veremos que um mesmo problema pode ter várias soluções diferentes com custos ainda mais diferentes! Por isso, é crucial sabermos analisar, mesmo que por cima, qual o algoritmo que é mais eficiente. Vou ficando por aqui…

Sobre as mudanças

Estou em Florianópolis e passarei aqui mais uma semana, o que é excelente. Agora estou na casa da minha tia, que, além de ficar do lado da praia, tem ADSL (e uma placa de rede sobrando no computador para o laptop aproveitar a internet… :))

Eu planejava postar um artigo por dia da Série Algoritmos, mas o artigo que estou escrevendo agora é a parte de algoritmos que eu considero a mais complicada (muito mais complicada que os algoritmos em si que vamos estudar depois): análise de algoritmos. E estou fazendo um esforço para deixar a parte matemática bem leve e introduzir essa matéria de maneira bem simples, por isso a demora.

Aí eu resolvi escrever um artigo inteiro só pra comentar sobre as mudanças no blog.

Leitores do meu feed, me desculpem pelas várias atualizações de ontem. Além de mudar a estrutura da página (tirando, por exemplo, os comentários da página inicial), eu estou reorganizando os artigos a partir de agora, separando-os em categorias (cada um com só uma categoria) e usando o plugin Simple Tags para a tagsonomia.

Se vocês observarem, incluí links para a validação de WAI e da Section 508 na minha sidebar. Meu site tá bem acessível com esse novo design e estrutura. :) Além disso, enviei-o para algumas galerias e por isso as visitas estão mais em alta do que já estavam antes nessa semana.

Hmmm… Para os que ainda não perceberam, troquei as antigas páginas Biografia e Portifólio por apenas uma seção, denominada Currículo. Essa mudança foi para que eu não precisasse de grandes atualizações em textos (o que é uma coisa muito chata de fazer!) e ter minhas informações profissionais separadas em um só lugar. Embora eu não tenha quase experiência alguma (quer dizer, experiência até eu tenho, mas não tenho cursos), espero que o currículo também me ajude com os negócios.

Adicionei um link para meus feeds no Bloglines também e dei uma arrumada na seção de problemas lógicos.

Espero que estejam gostando das mudanças… Agora estou me preparando para mudanças mais radicais, como transformar o site inteiro em Ajax (o que não é difícil no WordPress, mas vou esperar no mínimo esperar a série de algoritmos pra não ficar atrasado com ela)

Como representar um algoritmo?

No primeiro artigo desta série, expliquei o que é um algoritmo e até citei exemplos do cotidiano, como acordar ou falar com outra pessoa. Talvez você nem tenha se dado conta, mas usando listas numeradas eu representei os algoritmos ali presentes, inclusive destacando a entrada e a saída de cada situação-problema. Porém, não é sempre assim que representamos algoritmos.

Não existe uma regra padrão para a representação de algoritmos. Cada pessoa escreve de forma diferente, mas o importante é ser legível e convencionado. Por exemplo, o livro Algoritmos: Teoria e Prática* traz nas páginas 14 e 15 convenções do pseudocódigo que utiliza no livro inteiro. Já eu, quando vou passar o mesmo algoritmo, utilizaria outro tipo de código, você pode utilizar outro, e por aí vai. Mas todos têm que ter o mesmo foco: legibilidade e fácil implementação para qualquer linguagem.

* A partir deste artigo, sempre que eu falar “Cormen”, “CLRS”, “Introduction to Algorithms” ou “Algoritmos: Teoria e Prática” estarei me referindo a um livro que é referência essencial nessa área. A versão que tenho (de onde tiro o número das páginas) é a tradução da segunda edição americana publicada pela Elsevier em 2002.

Os pseudocódigos costumam parecer um código em linguagem Pascal traduzido para a sua língua. :) Usam quase sempre estruturas que estamos acostumados a usar na programação, como se, enquanto, para, arrays, etc. Eles existem para que o algoritmo seja de fácil leitura para qualquer programador, que programe em qualquer linguagem “normal”. Veja o pseudocódigo do Insertion Sort, um algoritmo de ordenação de vetores bastante simples:

para j \leftarrow{} 2 até comprimento do vetor, faça
elemento \leftarrow{} vetor[j]
i \leftarrow{} j - 1
enquanto i > 0 e vetor[i] > elemento, faça
  vetor[i + 1] \leftarrow{} vetor[i]
  i \leftarrow{} i - 1
fim-enquanto
vetor[i + 1] \leftarrow{} elemento
fim-para

(Não se preocupe em entender o que ele faz, AINDA, pois veremos isso mais adiante)

Se você programa em qualquer linguagem, você não terá dificuldade em traduzir esse pseudocódigo para ela. O pseudocódigo é sempre uma maneira bem simples de escrever o código. Veja por exemplo, o mesmo código em C:

for (j=2; vetor[j]!=NULL; j++) {
    elemento = vetor[j];
    for (i = j-1; i > 0 && vetor[i] > elemento; i--) {
        vetor[i+1] = vetor[i];
    }
    vetor[i+1] = elemento;
}

Você deve ter percebido que ao invés de usar três linhas com uma declaração, um condicional e um incremento, eu juntei todos num só for. Mas por isso o algoritmo é bem simples e sempre parte do princípio de que a sua linguagem é simples. Veja só a implementação do código em Pascal e compare-a com a do pseudocódigo:

for j:=2 to comprimento, do begin
    elemento := vetor[j];
    i := j-1;
    while i>0 && vetor[i] > elemento, do begin
        vetor[i+1] := vetor[i];
        i := i-1;
    end;
    vetor[i] := elemento;
end;

Linha por linha ela é exatamente igual! A única diferença é que o pseudocódigo é traduzido… Geralmente os pseudocódigos são parecidos sempre com essa base e suas implementações não são muito diferentes. E vai ser sempre dessa maneira que eu vou representar os algoritmos (usando pseudocódigos e alguns traduzindo para C para mostrar implementações). No entanto, qualquer dúvida sobre essa representação, fique a vontade para perguntar através dos comentários.

© 2005–2020 Tiago Madeira