Ir para o conteúdo
TI - Desenvolvimento de Sistemas

Algoritmos e Lógica de Programação

O que mais cai

Parte do tópico Importância O que a banca cobra
Definição de Algoritmos e Estruturas de Controle Alta Características do algoritmo; sequência, seleção e repetição; enquanto, repita e para; teste de mesa
Variáveis, Subprogramas e Passagem de Parâmetro Alta Escopo; procedimento e função; parâmetro formal e real; passagem por valor e por referência
Linguagens Educacionais e Pseudolinguagens Média Portugol e VisuAlg; fluxograma e seus símbolos; Pascal, Logo e Scratch no ensino
Algoritmos Diversos Alta Recursão; busca sequencial e binária; métodos de ordenação; complexidade em notação O

Do que trata o tópico

Este tópico ensina a base de toda a programação: o que é um algoritmo, como ele se escreve com as estruturas de controle, como as variáveis e os subprogramas guardam e trocam dados, quais pseudolinguagens e linguagens servem ao ensino e quais algoritmos clássicos (recursão, busca e ordenação) aparecem nas provas.

Conteúdo

Definição de Algoritmos e Estruturas de Controle

Um servidor precisa calcular a média de 300 candidatos e dizer quem foi aprovado. Se cada colega fizer a conta do seu jeito, o resultado muda de pessoa para pessoa. A solução é uma sequência de passos que qualquer pessoa, ou computador, execute sempre da mesma forma.

Essa sequência de passos é o algoritmo: um conjunto finito de instruções bem definidas, em ordem, que resolve um problema em um tempo finito. A receita de bolo é o exemplo do dia a dia, mas o computador não interpreta frases vagas como "asse até ficar bom".

Características do algoritmo. Donald Knuth descreve cinco características que a banca cobra com frequência:

  1. Finitude: o algoritmo termina após um número finito de passos.
  2. Definição: cada passo é preciso e sem ambiguidade, isto é, só admite uma interpretação.
  3. Entrada: o algoritmo tem zero ou mais entradas, os dados que recebe antes ou durante a execução.
  4. Saída: o algoritmo tem uma ou mais saídas, os resultados que entrega.
  5. Efetividade: cada passo é simples o bastante para ser executado de fato, em tempo finito.

A entrada pode ser zero (um algoritmo que imprime os números de 1 a 10 não recebe nada), mas a saída não, porque um algoritmo sem resultado não resolve problema algum. Um laço que nunca termina pode estar num programa, mas não é algoritmo, porque viola a finitude.

Formas de representação. A descrição narrativa usa a língua natural ("leia as duas notas, some e divida por dois") e permite ambiguidade. O fluxograma usa símbolos ligados por setas, e o pseudocódigo usa uma linguagem estruturada em português. A parte de pseudolinguagens detalha os dois.

A programação estruturada. Corrado Böhm e Giuseppe Jacopini mostraram que qualquer algoritmo pode ser escrito com só três estruturas de controle: a sequência, a seleção (decisão) e a repetição (laço). Edsger Dijkstra criticou o desvio incondicional (o goto), que salta para qualquer ponto do código. A programação estruturada usa só as três estruturas, cada uma com uma entrada e uma saída.

Sequência. Na sequência, os comandos executam um depois do outro, na ordem escrita.

Seleção. A seleção escolhe qual bloco executar a partir de uma condição lógica, isto é, uma expressão que vale verdadeiro ou falso. Há quatro formas:

  1. Seleção simples (se ... entao ... fimse): se a condição é verdadeira, o bloco executa; se é falsa, nada acontece.
  2. Seleção composta (se ... entao ... senao ... fimse): um bloco para o verdadeiro e outro para o falso. Sempre um dos dois executa.
  3. Seleção encadeada: um se dentro do senao de outro, para testar várias faixas (nota abaixo de 5, de 5 a 7, acima de 7).
  4. Seleção múltipla (escolha ... caso, o switch de C e Java): compara uma variável com vários valores fixos e executa o caso que bate.

No switch de C e Java, sem o break no fim do case, a execução continua nos casos de baixo, mesmo que o valor não bata com eles. Esse efeito se chama queda (fall-through).

Repetição. A repetição executa o mesmo bloco várias vezes: o cálculo da média dos 300 candidatos é escrito uma vez, dentro de um laço. Há três tipos de laço, que diferem pelo momento do teste da condição:

  1. Enquanto (enquanto ... faca, o while): testa a condição antes de cada volta. Se a condição é falsa logo na primeira vez, o bloco não executa nenhuma vez.
  2. Repita ... até (repita ... ate): testa a condição depois de cada volta. O bloco executa pelo menos uma vez, e o laço para quando a condição fica verdadeira.
  3. Para (para i de 1 ate 10 faca, o for): usa uma variável de controle que começa num valor, avança a cada volta e para no valor final. Serve quando o número de voltas é conhecido antes de o laço começar.

Em C e Java, o laço com teste no fim é o do ... while, que continua enquanto a condição é verdadeira. O repita ... ate do Portugol faz o contrário: continua enquanto a condição é falsa e para quando ela fica verdadeira. As duas estruturas executam o bloco pelo menos uma vez.

O contador soma sempre o mesmo valor (cont <- cont + 1) e conta ocorrências. O acumulador soma um valor que muda (soma <- soma + nota) e guarda um total. Os dois recebem um valor inicial antes do laço, em geral zero.

Se o bloco não altera nenhuma variável da condição, o laço nunca termina: é o laço infinito. Para parar a leitura de dados sem saber quantos virão, o algoritmo usa um valor sentinela, um valor especial (como -1 numa lista de idades) que avisa o fim.

Operadores. As condições usam três grupos de operadores. Os aritméticos fazem conta: além de soma, subtração, multiplicação e divisão, há div (quociente da divisão inteira) e mod (resto da divisão inteira). Os relacionais comparam (=, <>, <, >, <=, >=), e os lógicos juntam condições: e (verdadeiro só se as duas partes são verdadeiras), ou (verdadeiro se ao menos uma é verdadeira) e nao (inverte o valor).

A ordem de avaliação, sem parênteses, é: primeiro os aritméticos, depois os relacionais e por último os lógicos. Entre os lógicos, o nao vem antes do e, e o e vem antes do ou. Assim, nao V e F ou V vale verdadeiro: nao V dá falso, falso e F dá falso e falso ou V dá verdadeiro.

Exemplo resolvido: o algoritmo abaixo soma os números pares de 1 a 6. A técnica para descobrir o que ele mostra é o teste de mesa, isto é, executar o algoritmo à mão e anotar o valor de cada variável a cada passo.

soma <- 0
i <- 1
enquanto i <= 6 faca
   se i mod 2 = 0 entao
      soma <- soma + i
   fimse
   i <- i + 1
fimenquanto
escreva(soma)
  1. Antes do laço: soma vale 0 e i vale 1.
  2. Com i = 1, o resto de 1 por 2 é 1, a condição do se é falsa e soma continua 0. O i passa a 2.
  3. Com i = 2, o resto é 0 e soma passa a 0 + 2 = 2. O i passa a 3.
  4. Com i = 3, nada muda na soma. Com i = 4, soma passa a 2 + 4 = 6.
  5. Com i = 5, nada muda. Com i = 6, soma passa a 6 + 6 = 12.
  6. O i passa a 7, a condição 7 <= 6 é falsa e o laço termina.
  7. Resultado: o algoritmo mostra 12.

Na prática, um setor de protocolo conta os processos atrasados de uma lista com um laço para que percorre a lista, um se que testa o prazo e um contador zerado antes do laço. A diferença que a banca mais troca é entre o enquanto, que pode não executar nenhuma vez, e o repita, que executa ao menos uma vez.

O erro comum é esquecer de atualizar a variável da condição dentro do laço (laço infinito) ou de iniciar o acumulador com zero.

x = 10
cont = 0
while x > 1:
    if x % 2 == 0:
        x = x // 2
    else:
        x = 3 * x + 1
    cont = cont + 1
print(x, cont)

Considere o código acima, escrito em Python, em que % é o resto da divisão e // é a divisão inteira. Ao final da execução, o programa imprime:

  1. A) 1 5
  2. B) 2 6
  3. C) 1 6
  4. D) 1 7
  5. E) nada, porque o laço não termina

Comentário:

A) Errada.

Conta: o valor de x passa por 10, 5, 16, 8, 4, 2 e 1, e cada passagem soma 1 em cont, o que dá 6 voltas.

A alternativa erra em 1 5: são 6 voltas, e não 5. O erro vem de esquecer a volta em que x passa de 2 para 1.

B) Errada.

Conta: na sexta volta, x vale 2, é par e passa a 2 // 2 = 1; o teste x > 1 fica falso e o laço para com x igual a 1.

A alternativa erra em 2 6: o laço só para quando x deixa de ser maior que 1, e por isso x termina valendo 1.

C) Certa.

Conta: 10 vira 5 (1ª volta), 5 vira 16 (2ª), 16 vira 8 (3ª), 8 vira 4 (4ª), 4 vira 2 (5ª) e 2 vira 1 (6ª).

O laço termina com x igual a 1 e cont igual a 6, e o comando print mostra os dois valores separados por espaço.

D) Errada.

Regra: o laço while testa a condição antes de cada volta; quando x chega a 1, o teste falha e nenhuma volta a mais acontece.

A alternativa erra em 1 7: não há sétima volta, porque o teste x > 1 falha logo depois da sexta.

E) Errada.

Regra: o laço termina quando o bloco altera a variável da condição até ela ficar falsa.

A alternativa erra em o laço não termina: a variável x muda a cada volta e chega a 1 depois de 6 voltas.

Resultado: alternativa C.

int op = 2;
int r = 0;
switch (op) {
    case 1: r = r + 1;
    case 2: r = r + 10;
    case 3: r = r + 100;
        break;
    default: r = r + 1000;
}
System.out.println(r);

Considere o código acima, escrito em Java. O valor impresso é:

  1. A) 10
  2. B) 0
  3. C) 1110
  4. D) 111
  5. E) 110

Comentário:

A) Errada.

Regra: no switch, o case sem break deixa a execução cair nos casos de baixo até achar um break ou o fim do bloco.

A alternativa erra em 10: o case 2 não tem break, e a execução cai no case 3, que soma mais 100.

B) Errada.

Regra: o switch executa o case cujo valor é igual ao da variável testada.

A alternativa erra em 0: op vale 2, o case 2 bate e r recebe pelo menos 10.

C) Errada.

Regra: o break encerra o switch, e os casos depois dele não executam.

A alternativa erra em 1110: o break do case 3 impede a soma do default, e o case 1 fica acima do ponto de entrada, por isso não executa.

D) Errada.

Regra: a execução entra no case que bate e segue para baixo, nunca para cima.

A alternativa erra em 111: o case 1 está acima do case 2 e não executa, e por isso o valor 1 não entra na soma.

E) Certa.

Conta: r começa em 0; o case 2 soma 10 (r = 10); sem break, o case 3 soma 100 (r = 110); o break encerra o switch.

O valor impresso é 110, porque a execução entra no case 2 e cai no case 3, que tem o break.

Resultado: alternativa E.

Na estrutura repita...até, o bloco de comandos é repetido enquanto a condição for verdadeira e pode não ser executado nenhuma vez, caso a condição já seja verdadeira na primeira avaliação.

Comentário:

Regra: o repita...até testa a condição no fim de cada volta, executa o bloco ao menos uma vez e para quando a condição fica verdadeira.

A afirmação erra em é repetido enquanto a condição for verdadeira: o repita continua enquanto a condição é falsa. Ela erra também em pode não ser executado nenhuma vez: o teste vem depois do bloco, e por isso a primeira volta sempre acontece. A descrição da afirmação mistura o repita com o enquanto.

Resultado: errado.

Como a banca cobra: a banca dá um trecho de código ou de pseudocódigo com laço e seleção e pede o valor impresso, ou troca o momento do teste entre o enquanto e o repita, ou esconde a falta de break num switch.

Variáveis, Subprogramas e Passagem de Parâmetro

Um algoritmo de folha de pagamento precisa guardar o salário, os descontos e o líquido de cada servidor. A variável é o lugar para isso: um espaço da memória com um nome, que guarda um valor e permite trocá-lo durante a execução.

Toda variável tem quatro elementos: o nome (identificador), o tipo (que valores ela aceita), o valor atual e o endereço na memória. A constante é parecida, mas o valor é fixado uma vez e não muda durante a execução, como o valor de pi.

Tipos de dados. Os tipos primitivos do pseudocódigo são quatro: inteiro (números sem parte decimal, como 25), real (números com parte decimal, como 7,5), caractere (texto, como "Ana") e lógico (verdadeiro ou falso). Os compostos juntam vários valores: o vetor (mesmo tipo, acesso por índice), a matriz (vetor de duas ou mais dimensões) e o registro (campos de tipos diferentes, como nome e salário).

Identificadores. O nome da variável segue regras comuns à maioria das linguagens: começa com letra ou sublinhado, não tem espaço nem caractere especial e não pode ser uma palavra reservada, isto é, uma palavra que a linguagem usa como comando (se, enquanto, inicio). Assim, salario_base e _total são nomes válidos, mas 2nota, nota final e enquanto não são.

Declaração e atribuição. Antes do uso, a variável é declarada: o algoritmo informa o nome e o tipo, e a linguagem reserva o espaço. A atribuição grava um valor na variável e, no pseudocódigo, usa a seta (soma <- 0). A seta não é igualdade matemática: x <- x + 1 lê o valor atual de x, soma 1 e grava o resultado de volta em x.

Escopo e tempo de vida. O escopo é a região do programa em que a variável pode ser usada. A variável global é declarada fora de qualquer subprograma e vale em todo o programa. A variável local é declarada dentro de um subprograma, só vale dentro dele e some quando o subprograma termina.

Quando uma variável local tem o mesmo nome de uma global, dentro do subprograma vale a local, e a global fica escondida: é o sombreamento. Alterar a local não muda a global.

Subprogramas. Um sistema de folha calcula o desconto de previdência em vários pontos, e copiar o cálculo em cada ponto gera erro quando a regra muda. O subprograma (sub-rotina ou módulo) é um bloco com nome, escrito uma vez e chamado quantas vezes for preciso. Ele traz reaproveitamento, leitura mais fácil, teste em separado e manutenção num lugar só. Há dois tipos:

  1. Procedimento: executa uma tarefa e não devolve valor para quem o chamou (por exemplo, imprimir um relatório). A chamada aparece sozinha, como um comando.
  2. Função: executa uma tarefa e devolve um valor (por exemplo, calcular a média). A chamada aparece dentro de uma expressão, como m <- media(n1, n2).

Em C e Java, o procedimento é a função de retorno void, que não devolve nada. Em Pascal e no Portugol, os nomes são procedure e function, ou procedimento e funcao.

Parâmetros formais e reais. Os parâmetros formais são os nomes declarados no cabeçalho do subprograma (funcao media(a, b: real)). Os parâmetros reais, também chamados de argumentos, são os valores ou variáveis passados na chamada (media(n1, n2)). Na chamada, cada argumento se liga ao parâmetro formal da mesma posição.

Como funciona a chamada. A cada chamada, o sistema monta na pilha de execução um registro de ativação com os parâmetros, as variáveis locais e o ponto de retorno. Ao terminar, o subprograma devolve o valor (se for função), o registro sai da pilha e a execução volta ao comando seguinte ao da chamada.

Passagem de parâmetro. A forma como o argumento chega ao parâmetro formal decide se a alteração feita dentro do subprograma aparece fora dele:

  1. Por valor: o parâmetro formal recebe uma cópia do valor do argumento. Alterar o parâmetro dentro do subprograma não muda a variável de quem chamou. O argumento pode ser uma constante ou uma expressão (dobra(3 + 2)).
  2. Por referência: o parâmetro formal recebe o endereço da variável. Alterar o parâmetro altera a própria variável de quem chamou. O argumento precisa ser uma variável, porque uma constante ou expressão não tem endereço para ser alterado.
  3. Por valor-resultado (cópia e restauração): o parâmetro recebe uma cópia na entrada, e o valor final é copiado de volta para a variável quando o subprograma termina.

No Portugol e no Pascal, a palavra var antes do parâmetro formal marca a passagem por referência. Em C, toda passagem é por valor, e a passagem por referência é simulada com ponteiros: a função recebe o endereço (&x) e altera o conteúdo pelo ponteiro (*p). O C++ tem a referência de fato, marcada com & no parâmetro.

Em Java, toda passagem é por valor. Quando o argumento é um objeto, o método recebe uma cópia da referência, e por isso pode alterar os atributos do objeto original, mas não consegue fazer a variável de quem chamou apontar para outro objeto. Em Python, o parâmetro recebe o mesmo objeto do argumento: uma lista alterada por append muda também fora, mas uma nova atribuição ao parâmetro só troca o nome local.

Exemplo resolvido: o procedimento abaixo recebe x por valor e y por referência (marcado com var).

procedimento dobra(x: inteiro; var y: inteiro)
inicio
   x <- x * 2
   y <- y * 2
fimprocedimento

a <- 3
b <- 5
dobra(a, b)
escreva(a, " ", b)
  1. Antes da chamada, a vale 3 e b vale 5.
  2. Na chamada, x recebe uma cópia de a (3), e y passa a apontar para a própria variável b.
  3. O comando x <- x * 2 faz x valer 6, mas a cópia some ao fim do procedimento e a continua 3.
  4. O comando y <- y * 2 grava 10 na variável b, porque y é o endereço de b.
  5. Resultado: o algoritmo mostra 3 e 10.

Na prática, a função calculaDesconto(salario) recebe o salário por valor e devolve o desconto sem risco de alterar o salário guardado. Já o procedimento reajusta(var salario, percentual) recebe o salário por referência, porque a tarefa dele é alterar o valor guardado.

A diferença que a banca mais troca é entre parâmetro formal (no cabeçalho) e parâmetro real (na chamada). O erro comum é achar que Java passa objetos por referência: Java passa a referência por valor.

Em Java, quando um método recebe um objeto como argumento e altera um atributo desse objeto, a alteração é vista por quem chamou o método; ainda assim, a passagem de parâmetros em Java é sempre por valor, porque o método recebe uma cópia da referência ao objeto.

Comentário:

Regra: em Java, todo argumento é passado por valor; no caso de objeto, o valor copiado é a referência, e as duas cópias apontam para o mesmo objeto.

Como a cópia da referência aponta para o mesmo objeto, a alteração de um atributo aparece fora do método. Se o método atribuir um novo objeto ao parâmetro, só a cópia local muda, e é isso que mostra que a passagem é por valor.

Resultado: certo.

def altera(lista, n):
    lista.append(n)
    n = n + 1
    lista = [0]
    return n

v = [1, 2]
k = 5
r = altera(v, k)
print(v, k, r)

Considere o código acima, escrito em Python. O programa imprime:

  1. A) [1, 2, 5] 5 6
  2. B) [0] 6 6
  3. C) [1, 2, 5] 6 6
  4. D) [0] 5 6
  5. E) [1, 2] 5 6

Comentário:

A) Certa.

Regra: em Python, o parâmetro recebe o mesmo objeto do argumento; o append altera esse objeto, mas a nova atribuição ao parâmetro só troca o nome local.

O append põe 5 na lista de v. Depois, n passa a 6 e lista passa a [0] só dentro da função. A função devolve 6, e por isso v vale [1, 2, 5], k continua 5 e r vale 6.

B) Errada.

Regra: a atribuição lista = [0] liga o nome local a uma lista nova e não altera a variável v.

A alternativa erra em [0] 6 6: v continua apontando para a lista original, que ficou [1, 2, 5], e k continua 5.

C) Errada.

Regra: o número inteiro não muda no lugar; n = n + 1 cria um valor novo só para o nome local n.

A alternativa erra em [1, 2, 5] 6 6: k continua valendo 5, porque a soma alterou só o nome local n.

D) Errada.

Regra: dentro da função, a nova atribuição a um parâmetro não alcança a variável de quem chamou.

A alternativa erra em [0] 5 6: a lista [0] existe só dentro da função; v mostra [1, 2, 5].

E) Errada.

Regra: o método append altera o próprio objeto da lista, que é o mesmo objeto de v.

A alternativa erra em [1, 2] 5 6: o append feito antes da nova atribuição já pôs 5 na lista de v.

Resultado: alternativa A.

total = 100

def f(total):
    total = total + 1
    return total

def g():
    global total
    total = total + 10

g()
print(f(total), total)

Considere o código acima, escrito em Python, em que global indica que a função usa a variável global de mesmo nome. O programa imprime:

  1. A) 101 110
  2. B) 111 111
  3. C) 101 100
  4. D) 111 110
  5. E) 110 110

Comentário:

A) Errada.

Regra: a função g roda antes do print e altera a variável global, porque declara total como global.

A alternativa erra em 101 110: quando f é chamada, a global já vale 110, e f devolve 110 + 1 = 111.

B) Errada.

Regra: o parâmetro total de f é uma variável local que esconde a global de mesmo nome (sombreamento).

A alternativa erra em 111 111: f soma 1 só na variável local, e a global continua 110.

C) Errada.

Regra: a declaração global dentro de g faz a atribuição alcançar a variável de fora da função.

A alternativa erra em 101 100: g soma 10 à global, que passa de 100 para 110 antes do print.

D) Certa.

Conta: g faz a global valer 100 + 10 = 110; f recebe 110, soma 1 na local e devolve 111; a global continua 110.

O print mostra primeiro o retorno de f (111) e depois a global (110).

E) Errada.

Regra: a função f devolve o valor da variável local depois da soma.

A alternativa erra em 110 110: o primeiro valor é o retorno de f, que soma 1 ao argumento e dá 111.

Resultado: alternativa D.

Como a banca cobra: a banca mostra um subprograma com um parâmetro por valor e outro por referência e pede os valores depois da chamada, ou afirma que Java passa objetos por referência, ou troca parâmetro formal com parâmetro real.

Linguagens Educacionais e Pseudolinguagens

Quem começa a programar enfrenta dois problemas ao mesmo tempo: pensar a lógica e decorar a sintaxe de uma linguagem em inglês. As pseudolinguagens e as linguagens educacionais separam os dois problemas: o aluno pensa a lógica primeiro, em português ou com blocos visuais, e só depois aprende a sintaxe.

Pseudocódigo e Portugol. O pseudocódigo é uma forma de escrever o algoritmo com palavras da língua natural, numa estrutura parecida com a de uma linguagem de programação. Ele não tem um padrão único: cada livro e cada professor usam a sua variação. O Portugol, também chamado de português estruturado, é a pseudolinguagem com palavras em português mais usada no Brasil.

O pseudocódigo em si não é executado, porque não segue uma gramática fixa, mas algumas ferramentas executam um dialeto fixo de Portugol. As mais citadas são o VisuAlg, que interpreta o Portugol e mostra o valor das variáveis durante a execução, e o Portugol Studio, um ambiente de ensino do Portugol.

A estrutura do VisuAlg. Um algoritmo no VisuAlg tem três partes, nesta ordem:

  1. O cabeçalho, com algoritmo "nome".
  2. A área de declaração, aberta por var, com cada variável e o seu tipo (inteiro, real, caractere, logico).
  3. O corpo, entre inicio e fimalgoritmo, com os comandos.

Os comandos principais são leia (entrada), escreva e escreval (saída, o segundo pula de linha), <- (atribuição), se, escolha, enquanto, repita e para, cada um fechado pelo seu fim (fimse, fimenquanto). A igualdade usa = e a diferença usa <>. No VisuAlg, a divisão inteira usa a barra invertida (\) e o resto usa mod ou %. No Portugol dos livros e no Pascal, a divisão inteira usa div.

Exemplo resolvido: o algoritmo abaixo lê duas notas, calcula a média e mostra se o aluno foi aprovado com média 7 ou mais.

algoritmo "media"
var
   n1, n2, media: real
inicio
   leia(n1, n2)
   media <- (n1 + n2) / 2
   se media >= 7 entao
      escreval("Aprovado")
   senao
      escreval("Reprovado")
   fimse
fimalgoritmo
  1. O usuário digita 6 e 9, que vão para n1 e n2.
  2. A média é (6 + 9) / 2 = 15 / 2 = 7,5.
  3. A condição 7,5 >= 7 é verdadeira, e o bloco do entao executa.
  4. Resultado: o algoritmo mostra "Aprovado".

O fluxograma. O fluxograma desenha o algoritmo com símbolos ligados por setas que mostram a ordem de execução. Os símbolos mais cobrados são: o terminal (retângulo de cantos arredondados ou elipse), que marca o início e o fim; o processamento (retângulo), que representa um cálculo ou uma atribuição; a decisão (losango), que tem uma saída para o sim e outra para o não; a entrada e saída de dados (paralelogramo); e o conector (círculo), que liga partes do desenho. O algoritmo da média, em fluxograma, segue os passos abaixo.

  1. Início
  2. Ler as notas n1 e n2
  3. Calcular a média de n1 e n2
  4. A média é maior ou igual a 7?
    SimMostrar Aprovado
    NãoMostrar Reprovado
  5. Fim
Fluxograma do algoritmo da média

O diagrama de Nassi-Shneiderman (ou de Chapin) desenha o algoritmo em retângulos encaixados, sem setas, e só admite as estruturas da programação estruturada.

Linguagens educacionais. Algumas linguagens de programação reais nasceram para o ensino:

  1. Pascal: criada por Niklaus Wirth para ensinar programação estruturada, com tipagem forte e estática e blocos begin ... end. É a origem do estilo do Portugol, inclusive do var para passagem por referência.
  2. BASIC: criada por John Kemeny e Thomas Kurtz para que estudantes de áreas fora da computação programassem com facilidade.
  3. Logo: ligada a Seymour Papert, conhecida pela tartaruga, um cursor que anda e risca a tela com comandos como avançar e girar. Ensina geometria e lógica para crianças.
  4. Scratch: linguagem visual por blocos, criada no MIT, em que o aluno encaixa blocos coloridos em vez de digitar comandos, o que elimina o erro de sintaxe.

Na prática, uma escola de governo que oferece um curso de lógica a servidores começa com o Portugol no VisuAlg, porque o aluno escreve em português e vê o valor das variáveis a cada passo. Depois, a mesma lógica é reescrita numa linguagem real, e o aluno aprende só a sintaxe nova.

A diferença que a banca troca é entre pseudocódigo, que descreve a lógica sem padrão único, e linguagem de programação, que tem gramática fixa. O VisuAlg fica no meio: interpreta um dialeto fixo de Portugol.

O erro comum é usar = para atribuir no Portugol. No VisuAlg, o = compara, e a atribuição usa <-.

Sobre pseudolinguagens e linguagens usadas no ensino de programação, assinale a alternativa correta.

  1. A) O pseudocódigo segue uma sintaxe única e padronizada, o que permite executá-lo em qualquer computador sem adaptação.
  2. B) O Portugol usa palavras em português e serve para escrever a lógica do algoritmo antes de traduzi-la para uma linguagem de programação.
  3. C) O Scratch é uma linguagem textual em que o aluno digita comandos em inglês, com ponto e vírgula no fim de cada linha.
  4. D) A linguagem Logo foi criada para o ensino de banco de dados e usa a tartaruga para representar as tabelas.
  5. E) O Pascal foi criado para uso comercial, sem preocupação com o ensino da programação estruturada.

Comentário:

A) Errada.

Regra: o pseudocódigo não tem padrão único; cada autor usa a sua variação, e só dialetos fixos, como o do VisuAlg, são executados por ferramentas próprias.

A alternativa erra em segue uma sintaxe única e padronizada: não existe um padrão único de pseudocódigo, e por isso ele não é executado em qualquer computador.

B) Certa.

Regra: o Portugol é o português estruturado, usado para escrever a lógica com palavras em português antes da sintaxe de uma linguagem real.

A alternativa descreve a função do Portugol no ensino: separar a lógica do algoritmo da sintaxe da linguagem.

C) Errada.

Regra: o Scratch é uma linguagem visual, em que o aluno monta o programa encaixando blocos.

A alternativa erra em linguagem textual em que o aluno digita comandos em inglês: no Scratch, o aluno não digita comandos, e por isso não há ponto e vírgula.

D) Errada.

Regra: a Logo ensina geometria e lógica com a tartaruga, um cursor que anda e risca a tela.

A alternativa erra em criada para o ensino de banco de dados: a Logo não trata de banco de dados, e a tartaruga desenha figuras, não tabelas.

E) Errada.

Teoria: Niklaus Wirth criou o Pascal para ensinar programação estruturada, com tipagem forte.

A alternativa erra em criado para uso comercial, sem preocupação com o ensino: o ensino da programação estruturada é a origem do Pascal.

Resultado: alternativa B.

No VisuAlg, o comando de atribuição é escrito com o sinal de igual, e o sinal formado pelo menor e pelo hífen é usado para comparar dois valores na condição de uma estrutura se.

Comentário:

Regra: no VisuAlg, a atribuição usa a seta formada pelo menor e pelo hífen, e o sinal de igual compara dois valores.

A afirmação erra em o comando de atribuição é escrito com o sinal de igual: o sinal de igual é o operador relacional de igualdade. A afirmação inverte os dois papéis: a seta grava o valor, e o igual compara.

Resultado: errado.

Como a banca cobra: a banca pede o símbolo de fluxograma de cada ação (losango para decisão, paralelogramo para entrada e saída), mostra um trecho de Portugol para leitura ou atribui a uma linguagem educacional a característica de outra.

Algoritmos Diversos

Um órgão tem um cadastro com 100 mil processos e precisa achar um processo em segundos, e procurar um por um demora. Os algoritmos clássicos de busca, de ordenação e de recursão resolvem esse tipo de problema com menos passos.

Complexidade e notação O. Para comparar dois algoritmos, conta-se o número de passos em função do tamanho da entrada, chamado de n. A notação O (O grande) mostra como esse número cresce quando n fica muito grande, no pior caso, ignorando constantes. Do mais rápido para o mais lento, as classes mais cobradas são:

  1. O(1), constante: o tempo não depende de n (acessar o elemento de um vetor pelo índice).
  2. O(log n), logarítmica: a cada passo, o problema cai pela metade (busca binária).
  3. O(n), linear: um passo por elemento (busca sequencial).
  4. O(n log n): ordenação eficiente (merge sort e heapsort).
  5. O(n²), quadrática: dois laços encaixados sobre os dados (bolha, seleção e inserção no pior caso).
  6. O(2ⁿ), exponencial: o número de passos dobra a cada elemento novo (Fibonacci recursivo simples).

O algoritmo é analisado no melhor caso, no caso médio e no pior caso, e a notação O descreve em geral o pior caso.

Recursão. A função recursiva é a que chama a si mesma para resolver uma versão menor do mesmo problema. Ela tem duas partes obrigatórias: o caso base, que resolve o problema sem nova chamada e encerra a recursão, e o passo recursivo, que chama a função com uma entrada menor, mais perto do caso base. Sem caso base, a função chama a si mesma até lotar a pilha de execução, o erro chamado de estouro de pilha (stack overflow).

O fatorial é o exemplo clássico: o fatorial de n é n vezes o fatorial de n - 1, e o fatorial de 0 é 1 (caso base). Cada chamada fica parada na pilha esperando o resultado da chamada seguinte:

  1. fatorial(4) espera 4 × fatorial(3).
  2. fatorial(3) espera 3 × fatorial(2).
  3. fatorial(2) espera 2 × fatorial(1).
  4. fatorial(1) espera 1 × fatorial(0).
  5. fatorial(0) chega ao caso base e devolve 1.
  6. Na volta, as contas se resolvem: 1 × 1 = 1, 2 × 1 = 2, 3 × 2 = 6 e 4 × 6 = 24.

Toda função recursiva pode ser reescrita com laço (forma iterativa). A recursão encurta o código de problemas que se dividem em partes iguais, mas gasta memória com a pilha. O Fibonacci recursivo simples recalcula os mesmos valores e tem custo exponencial; a versão com laço é linear.

Busca sequencial e busca binária. A busca sequencial (ou linear) compara o valor procurado com cada elemento do vetor, do primeiro ao último. Funciona em qualquer ordem e, no pior caso, faz n comparações: O(n).

A busca binária exige o vetor ordenado. Ela funciona assim:

  1. Marca o início e o fim da faixa de busca (todo o vetor).
  2. Compara o valor procurado com o elemento do meio da faixa.
  3. Se é igual, achou e termina.
  4. Se o valor procurado é menor, a nova faixa é a metade da esquerda; se é maior, a metade da direita.
  5. Repete os passos 2 a 4 até achar ou até a faixa ficar vazia.

Como a faixa cai pela metade a cada comparação, a busca binária é O(log n). No pior caso, faz a parte inteira do logaritmo de n na base 2, mais 1, comparações: com 1.000 elementos, no máximo 10.

Exemplo resolvido: procurar o valor 23 no vetor ordenado [2, 5, 8, 12, 16, 23, 38, 56, 72, 91], com as posições numeradas de 0 a 9 e o meio calculado como a divisão inteira de (início + fim) por 2.

  1. Faixa de 0 a 9: meio = (0 + 9) div 2 = 4, e o elemento da posição 4 é 16. Como 23 > 16, a nova faixa começa em 5.
  2. Faixa de 5 a 9: meio = (5 + 9) div 2 = 7, e o elemento é 56. Como 23 < 56, a nova faixa termina em 6.
  3. Faixa de 5 a 6: meio = (5 + 6) div 2 = 5, e o elemento é 23. Achou.
  4. Resultado: a busca binária faz 3 comparações. A busca sequencial faria 6, uma para cada posição de 0 a 5.

Ordenação. Os métodos simples de ordenação são três:

  1. Bolha (bubble sort): percorre o vetor comparando cada par de vizinhos e troca o par fora de ordem. Ao fim de cada passagem, o maior elemento restante chega à última posição livre, como uma bolha que sobe. Com um aviso de "nenhuma troca nesta passagem", para cedo, e o melhor caso (vetor já ordenado) cai para O(n).
  2. Seleção (selection sort): procura o menor elemento do trecho não ordenado e o troca com o primeiro desse trecho. Faz sempre o mesmo número de comparações, O(n²), mesmo com o vetor já ordenado, mas faz poucas trocas.
  3. Inserção (insertion sort): pega cada elemento e o insere na posição certa entre os já ordenados à esquerda, como quem arruma cartas na mão. Com o vetor já ordenado, faz só n - 1 comparações (O(n)), e por isso é bom para vetores quase ordenados.

Os métodos eficientes usam a estratégia de dividir para conquistar, que divide o problema em partes menores, resolve cada parte e junta os resultados:

  1. Merge sort (intercalação): divide o vetor ao meio até sobrar um elemento em cada parte e depois intercala as partes em ordem. É O(n log n) em todos os casos, mas precisa de memória auxiliar proporcional a n.
  2. Quicksort: escolhe um elemento, o pivô, e separa os menores à esquerda e os maiores à direita; depois ordena cada lado do mesmo jeito. É O(n log n) no caso médio e O(n²) no pior caso, que ocorre quando o pivô é sempre o menor ou o maior elemento, como no vetor já ordenado com o pivô no extremo.
  3. Heapsort: monta uma estrutura chamada heap, em que o maior elemento fica no topo, e retira o topo repetidas vezes. É O(n log n) em todos os casos e ordena no próprio vetor.

Um método é estável quando mantém a ordem original dos elementos de mesmo valor: bolha, inserção e merge sort são estáveis; seleção, quicksort e heapsort, nas versões comuns, não são. Um método é in-place quando ordena no próprio vetor, com memória extra fixa.

Exemplo resolvido: ordenar o vetor [5, 3, 8, 1] pelo método da bolha, em ordem crescente.

  1. Primeira passagem: 5 e 3 trocam ([3, 5, 8, 1]); 5 e 8 ficam; 8 e 1 trocam ([3, 5, 1, 8]). O 8 chegou ao fim.
  2. Segunda passagem: 3 e 5 ficam; 5 e 1 trocam ([3, 1, 5, 8]). O 5 chegou ao lugar.
  3. Terceira passagem: 3 e 1 trocam ([1, 3, 5, 8]).
  4. Resultado: o vetor fica [1, 3, 5, 8], depois de 3 passagens e 4 trocas.

Outros algoritmos que caem. Alguns algoritmos curtos aparecem em questões de teste de mesa:

  1. Troca de duas variáveis: usa uma variável auxiliar (aux <- a, a <- b, b <- aux). Sem a auxiliar, o primeiro valor se perde.
  2. Maior elemento de um vetor: guarda o primeiro elemento como maior e percorre o resto, trocando o maior quando acha um valor acima dele.
  3. MDC de Euclides: o máximo divisor comum de a e b é o de b e do resto de a por b, até o resto dar zero. Para 48 e 18: 48 mod 18 = 12; 18 mod 12 = 6; 12 mod 6 = 0, e o MDC é 6.

Na prática, um tribunal mantém a lista de processos ordenada pelo número e usa busca binária para achar cada um. Para pôr poucos processos novos no lugar, a inserção é a escolha, porque a lista já está quase ordenada. Para reordenar a base pela data, o merge sort garante O(n log n) e, por ser estável, mantém a ordem anterior entre processos da mesma data.

A diferença que a banca mais troca é entre o quicksort e o merge sort: só o merge sort garante O(n log n) no pior caso, mas o quicksort ordena no próprio vetor. O erro comum é aplicar a busca binária a um vetor fora de ordem ou esquecer o caso base na recursão.

A busca binária, aplicada a um vetor ordenado de 1.000 elementos, faz no pior caso no máximo 10 comparações entre o valor procurado e os elementos do vetor, enquanto a busca sequencial pode precisar de 1.000 comparações no mesmo vetor.

Comentário:

Conta: o pior caso da busca binária é a parte inteira do logaritmo de 1.000 na base 2, mais 1, o que dá 9 + 1 = 10, porque 2 elevado a 9 é 512 e 2 elevado a 10 é 1.024.

A busca binária corta a faixa pela metade a cada comparação, e 10 cortes bastam para 1.000 elementos. A busca sequencial, no pior caso, compara com todos os 1.000 elementos.

Resultado: certo.

def f(n):
    if n <= 1:
        return 1
    return f(n - 1) + f(n - 2)

print(f(5))

Considere o código acima, escrito em Python. O valor impresso e o número total de chamadas da função f durante a execução, contando a primeira, são, respectivamente:

  1. A) 5 e 9 chamadas
  2. B) 8 e 9 chamadas
  3. C) 13 e 15 chamadas
  4. D) 8 e 5 chamadas
  5. E) 8 e 15 chamadas

Comentário:

A) Errada.

Conta: com o caso base f(0) = f(1) = 1, os valores são f(2) = 2, f(3) = 3, f(4) = 5 e f(5) = 5 + 3 = 8.

A alternativa erra em 5 e 9 chamadas: 5 e 9 são o valor e o número de chamadas de f(4), e não de f(5).

B) Errada.

Conta: o número de chamadas de f(n) é 1 mais as chamadas de f(n - 1) e de f(n - 2); com 1 chamada para f(0) e para f(1), dá 3 para f(2), 5 para f(3), 9 para f(4) e 15 para f(5).

A alternativa erra em 9 chamadas: f(5) gera 1 + 9 + 5 = 15 chamadas.

C) Errada.

Conta: f(5) = f(4) + f(3) = 5 + 3 = 8.

A alternativa erra em 13: 13 seria f(6), um termo à frente. O número de chamadas, 15, está certo.

D) Errada.

Regra: cada chamada com n maior que 1 faz duas chamadas novas, e por isso a recursão se abre em árvore e repete cálculos.

A alternativa erra em 5 chamadas: 5 chamadas seria o caso de uma recursão em linha, de f(5) até f(1). Aqui, f(3) é calculada duas vezes e f(2) três vezes.

E) Certa.

Conta: o valor é f(5) = 8, e as chamadas são 1 + 9 (de f(4)) + 5 (de f(3)) = 15.

O print mostra 8, e a execução fez 15 chamadas de f, porque a recursão dupla recalcula os mesmos valores.

Resultado: alternativa E.

O quicksort tem complexidade O(n log n) em todos os casos e, por isso, é preferível ao merge sort quando a aplicação exige garantia de tempo no pior caso.

Comentário:

Regra: o quicksort é O(n log n) no caso médio e O(n²) no pior caso, quando o pivô é sempre o menor ou o maior elemento; o merge sort é O(n log n) em todos os casos.

A afirmação erra em em todos os casos: o pior caso do quicksort é quadrático. Por isso, quando a aplicação exige garantia no pior caso, a escolha é o merge sort ou o heapsort, e não o quicksort.

Resultado: errado.

Acerca dos métodos de ordenação, assinale a alternativa correta.

  1. A) A ordenação por seleção faz um número linear de comparações quando o vetor já está ordenado.
  2. B) O merge sort ordena no próprio vetor, sem memória auxiliar proporcional ao número de elementos.
  3. C) A ordenação por inserção é estável e, com o vetor já ordenado, faz apenas n - 1 comparações.
  4. D) O método da bolha não é estável, porque troca de posição os elementos vizinhos iguais.
  5. E) O heapsort tem pior caso quadrático, assim como o quicksort.

Comentário:

A) Errada.

Regra: a seleção procura o menor elemento do trecho não ordenado em toda passagem, e por isso faz O(n²) comparações em qualquer caso.

A alternativa erra em faz um número linear de comparações: a seleção não percebe que o vetor já está ordenado e compara tudo de novo em cada passagem.

B) Errada.

Regra: o merge sort intercala as metades num vetor auxiliar, que tem o tamanho dos dados.

A alternativa erra em sem memória auxiliar proporcional ao número de elementos: a intercalação precisa de memória extra proporcional a n.

C) Certa.

Regra: a inserção só move um elemento quando acha um maior à esquerda, e por isso mantém a ordem dos iguais e, no vetor ordenado, faz uma comparação por elemento a partir do segundo.

Com o vetor já ordenado, cada elemento do segundo ao último é comparado uma vez com o vizinho da esquerda, o que dá n - 1 comparações.

D) Errada.

Regra: a bolha só troca vizinhos quando o da esquerda é maior, e dois elementos iguais nunca trocam.

A alternativa erra em não é estável, porque troca de posição os elementos vizinhos iguais: a bolha é estável, justamente porque não troca iguais.

E) Errada.

Regra: o heapsort é O(n log n) em todos os casos, inclusive no pior.

A alternativa erra em tem pior caso quadrático: só o quicksort, entre os dois, tem pior caso O(n²).

Resultado: alternativa C.

Como a banca cobra: a banca pede a saída de uma função recursiva, o número de comparações de uma busca binária, o estado do vetor depois de uma passagem de um método de ordenação ou a complexidade e a estabilidade de cada método.

  • Algoritmos e lógica de programação
    • Algoritmo
      • Finitude, definição, efetividade
      • Entrada e saída
      • Narrativa, fluxograma, pseudocódigo
    • Estruturas de controle
      • Sequência
      • Seleção simples, composta, múltipla
      • Enquanto, repita, para
      • Contador e acumulador
    • Variáveis e subprogramas
      • Nome, tipo, valor, endereço
      • Escopo global e local
      • Procedimento e função
      • Parâmetro formal e real
      • Passagem por valor e referência
    • Pseudolinguagens e ensino
      • Portugol e VisuAlg
      • Símbolos do fluxograma
      • Pascal, BASIC, Logo, Scratch
    • Algoritmos diversos
      • Notação O
      • Recursão e caso base
      • Busca sequencial e binária
      • Bolha, seleção, inserção
      • Merge sort, quicksort, heapsort

Quadros e comparações

A tabela abaixo compara as estruturas de repetição.

Laço Onde testa Execuções mínimas Quando para
Enquanto (while) Antes de cada volta Zero Quando a condição fica falsa
Repita ... até Depois de cada volta Uma Quando a condição fica verdadeira
Faça ... enquanto (do while) Depois de cada volta Uma Quando a condição fica falsa
Para (for) Antes de cada volta Zero Quando o contador passa do valor final

A tabela abaixo compara as formas de passagem de parâmetro.

Aspecto Por valor Por referência
O que o parâmetro recebe Uma cópia do valor O endereço da variável
Alteração dentro do subprograma Não muda a variável de fora Muda a variável de fora
Argumento aceito Variável, constante ou expressão Só variável
Como aparece no Portugol e no Pascal Parâmetro sem marca Parâmetro com var
Em Java Tudo, inclusive a referência ao objeto Não existe

A tabela abaixo reúne os métodos de ordenação.

Método Melhor caso Pior caso Estável
Bolha (com aviso de troca) O(n) O(n²) Sim
Seleção O(n²) O(n²) Não
Inserção O(n) O(n²) Sim
Merge sort O(n log n) O(n log n) Sim
Quicksort O(n log n) O(n²) Não
Heapsort O(n log n) O(n log n) Não

Para decorar

  • Características do algoritmo (Knuth): finitude, definição, entrada (zero ou mais), saída (uma ou mais) e efetividade.
  • Programação estruturada (Böhm e Jacopini): sequência, seleção e repetição bastam.
  • Enquanto: testa antes, pode executar zero vezes.
  • Repita ... até: testa depois, executa ao menos uma vez e para com a condição verdadeira.
  • Precedência: aritméticos, depois relacionais, depois lógicos; nao, depois e, depois ou.
  • Procedimento: não devolve valor. Função: devolve valor.
  • Parâmetro formal: no cabeçalho. Parâmetro real (argumento): na chamada.
  • Por valor: cópia. Por referência: endereço, só aceita variável.
  • Java: sempre por valor, inclusive a referência ao objeto.
  • VisuAlg: atribuição com seta, comparação com igual.
  • Recursão: caso base mais passo recursivo; sem caso base, estouro de pilha.
  • Busca binária: vetor ordenado, O(log n); 1.000 elementos, no máximo 10 comparações.
  • O(n log n) sempre: merge sort e heapsort.
  • Estáveis: bolha, inserção e merge sort.

Pegadinhas

# Errado Certo
1 Todo algoritmo precisa de pelo menos uma entrada. O algoritmo tem zero ou mais entradas e uma ou mais saídas.
2 O laço enquanto executa o bloco ao menos uma vez. O enquanto testa antes e pode executar zero vezes. Quem executa ao menos uma vez é o repita.
3 O repita ... até continua enquanto a condição é verdadeira. O repita para quando a condição fica verdadeira.
4 No switch, a execução para sozinha ao fim do case que bateu. Sem break, a execução cai nos casos de baixo.
5 No Portugol, o operador e é avaliado depois do ou. O nao vem primeiro, depois o e, e por último o ou.
6 A função não devolve valor, e o procedimento devolve. Só a função devolve valor.
7 Na passagem por referência, o argumento pode ser uma constante. A passagem por referência exige uma variável, que tem endereço.
8 Java passa objetos por referência. Java passa tudo por valor; no objeto, copia a referência.
9 A busca binária funciona em qualquer vetor. A busca binária exige o vetor ordenado.
10 O quicksort é O(n log n) no pior caso. O pior caso do quicksort é O(n²). O merge sort garante O(n log n).
11 A seleção é O(n) com o vetor já ordenado. A seleção faz O(n²) comparações em qualquer caso. A inserção é O(n) no vetor ordenado.

Fontes e data

  • [1] Conhecimento consolidado de livro didático de algoritmos e lógica de programação (características do algoritmo segundo Knuth, programação estruturada de Böhm e Jacopini, passagem de parâmetros, Portugol e VisuAlg, recursão, busca e ordenação).

Texto conferido em 15/10/2026.

Conferido em 14/10/2026.

Estude todos os tópicos do seu edital assim.

A assinatura custa R$ 50 por mês ou R$ 500 por ano. Você tem garantia de 7 dias.