CIC 110
Análise e Projeto de Análise e Projeto de
Algoritmos I Algoritmos I
Universidade Federal de Itajubá
Prof. Roberto Affonso da Costa Junior
AULA 02 AULA 02
– C++ e bibliotecas
– Vector
C++ C++
Os programas que iremos usar vai ser em C++ e ter o seguinte cabeçalho e corpo:
#include <bits/stdc++.h>
using namespace std;
int main() {
// Aqui você coloca a solução do problema return 0;
Ordem do programa Ordem do programa
Qual a ordem do programa a seguir?
Qual o tempo para n = 100? n = 1000?
#include <bits/stdc++.h>
using namespace std;
int main() { int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) { printf("%d\n", i);
}return 0;
}
Arquivo sh Arquivo sh
Salve o programa anterior com aula.cpp
Escreva o seguinte programa e salve como aula.sh
#!/bin/bash g++ aula.cpp
time ./a.out <entrada.txt >s g++ -O1 aula.cpp
time ./a.out <entrada.txt >s g++ -O2 aula.cpp
time ./a.out <entrada.txt >s g++ -O3 aula.cpp
Tempo com n = 100 Tempo com n = 100
Vá ao terminal e rode o ./aula.sh O que significa os tempos:
real 0m0.010s user 0m0.002s sys 0m0.004s real 0m0.007s user 0m0.003s sys 0m0.003s real 0m0.006s
user 0m0.001s sys 0m0.005s real 0m0.011s user 0m0.002s sys 0m0.005s
Tempo com n = 1000 Tempo com n = 1000
E agora?
Como fica o tempo?
Será que tem diferença com
n = 10000?
Programa 2 Programa 2
Repita o procedimento para o programa.
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
printf("%d\n", i);
} }
return 0;
}
Ordem da magnitude Ordem da magnitude
Repita o procedimento para os programas.
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= 3*n; i++) {
printf("%d\n", i);
}
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n + 5; i++) {
printf("%d\n", i);
}
Ordem da magnitude Ordem da magnitude
Repita o procedimento para os programas.
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i+=2) {
printf("%d\n", i);
}
return 0;
}
Fase Fase
Repita o procedimento para o programa.
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i+=2) {
printf("%d\n", i);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
printf("%d\n", i);
} }
for (int i = 1; i <= 3*n; i++) {
printf("%d\n", i);
}
Varias variáveis Varias variáveis
Repita o procedimento para o programa.
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
printf("%d\n", i);
} }
return 0;
}
Recursão Recursão
Repita o procedimento para o programa.
#include <bits/stdc++.h>
using namespace std;
int f(int n) {
if (n == 1) return 1;
f(n-1);
}
int main() {
int n;
scanf("%d", &n);
printf("%d\n", f(n));
Recursão Recursão
Repare que a função f do programa anterior é de ordem O(1). Contudo, a função recursiva chama o programa n vezes, tendo um tempo total de O(n).
Recursão Recursão
Repita o procedimento para o programa.
#include <bits/stdc++.h>
using namespace std;
int g(int n) {
if (n == 1) return 1;
g(n-1);
g(n-1);
}
int main() {
int n;
scanf("%d", &n);
printf("%d\n", g(n));
Recursão Recursão
Neste caso, cada chamada de função gera duas outras chamadas, exceto n = 1. Vamos ver o que acontece quando g é chamado com o parâmetro n. A tabela a seguir mostra as chamadas de função produzidas por esta única chamada:
Com base nisso, a complexidade do tempo é
função chamada número de chamadas
g(n) 1
g(n-1) 2
g(n-2) 4
… …
g(1) 2n-1
1+2+4+⋯+2n−1=2n−1=O(2n)
paper paper
Estude o assunto anterior, os programas e escreva um paper que explique os resultados. Não esqueça de que o paper não pode ter copia de texto. Deve ter no mínimo 2 ou 3 páginas.
Procure o professor se for o caso.
Vector Vector
A classe vector é uma alternativa à representação de array primitivo.
vector<int> v;
Alguns métodos:
v.size(); // retorna tamanho do vetor v
v.empty(); // determina se vetor v está vazio
vx.resize(novo_tamanho); // redimensiona vetor v
v2 = v; // cópia v em v2
Vector Vector
A classe vector é uma alternativa à representação de array primitivo.
vector<int> v;
Alguns métodos:
v.size(); // retorna tamanho do vetor v
v.empty(); // determina se vetor v está vazio
vx.resize(novo_tamanho); // redimensiona vetor v
v2 = v; // cópia v em v2
v.push_back(x); // inserir elemento no vector v.pop_back(x); // retira o ultimo elemento do vector
Iterator Iterator
É o mecanismo usado para "andar", elemento por elemento, por uma coleção de dados. É uma forma abstrata e genérica de tratar o avanço entre os elementos dessa coleção. Esse avanço pode se dar de várias formas, inclusive ao contrário.
O funcionamento exato depende de cada tipo de dado, o importante é que se um tipo possui um iterador em conformidade com a linguagem toda operação que iteração poderá ser feita com aquele objeto. Não importa para ele a complexidade da operação, nem como ela deverá ser feita. É uma forma independente da implementação de acessar os dados da coleção.
Ele possui os métodos begin() e end() pra indicar onde começa e onde termina a iteração.
Iterator Iterator
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, x;
vector <int> v;
vector <int>:: iterator it;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &x);
v.push_back(x);
}
for (it = v.begin(); it != v.end(); ++it) {
printf("%d ", *it);
}
printf("\n");
Problema Problema
Dado um vetor com números inteiros positivos e negativos, encontre a sublista contígua de maior soma a partir de uma lista de números. Por exemplo, dado o vetor V a seguir:
A sublista contígua de maior soma é:
V 10 5 -17 2020 50 -1 3 -30 10
V 10 5 -17 2020 50 -1 3 -30 10
Histórico Histórico
O problema de encontrar a sublista contígua de maior soma a partir de uma lista de números teve origem numa versão bidimensional mais complexa de um problema de emparelhamento de padrões inicialmente apresentado por Ulf Grenander, da Brown University.
Ao perceber que o algoritmo cúbico era impraticável para a resolução da versão unidimensional mais simples do problema de identificação de sequências contínuas de maior soma, Grenander desenvolveu a versão quadrática do algoritmo. Posteriormente, Michael Shamos, da atual Carnegie-Mellon
Histórico Histórico
Por fim, o estatístico J. (Jay) B. Kadane, da mesma Universidade de Shamos, desenvolveu dias depois a versão linear do algoritmo para o problema da sublista contígua de maior soma. Esse algoritmo permanece até os dias atuais como a versão mais eficiente e a melhor solução possível, pois qualquer algoritmo que pretenda resolver o problema em estudo deve necessariamente percorrer os N elementos da lista dada como entrada.
Kadane Kadane
O algoritmo é chamado de “Algoritmo de Kadane”.
Solução 1 Solução 1
Uma maneira direta de resolver o problema é passar por todos os subarray possíveis, calcular a soma dos valores em cada subarray e manter a soma máxima.
No programa a seguir, as variáveis i e j variam o primeiro e último índice do subarray e a soma dos valores é calculada para a variável “soma”. A variável melhor contém a soma máxima encontrada durante a pesquisa.
A complexidade do tempo do algoritmo é O(n3), porque consiste em três loops aninhados.
Solução de ordem 3 Solução de ordem 3
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, soma_maior = 0, x;
vector <int> v;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &x);
v.push_back(x);
}
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++)
Solução de ordem 3 Solução de ordem 3
int soma = 0;
for (int k = i; k <= j; k++) {
soma += v[k];
}
soma_maior = max(soma_maior, soma);
} }
printf("A soma maior da sublista eh: %d\n", soma_maior);
return 0;
}
Solução 2 Solução 2
É fácil tornar o Algoritmo anterior mais eficiente, basta remover um loop dele. Isto é possível calculando a soma da extremidade a direta conforme o subarray vai sendo movido.
Após essa mudança, a complexidade do tempo é O(n2).
Solução de ordem 2 Solução de ordem 2
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, soma_maior = 0, x;
vector <int> v;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &x);
v.push_back(x);
}
Solução de ordem 2 Solução de ordem 2
for (int i = 0; i < n; i++) {
int soma = 0;
for (int j = i; j < n; j++) {
soma += v[j];
soma_maior = max(soma_maior, soma);
} }
printf("A soma maior da sublista eh: %d\n", soma_maior);
return 0;
}
Solução 3 Solução 3
Outra estratégia é a de “Divisão e Conquista”
“Para resolver um problema de tamanho N, resolva recursivamente 'b' problemas de tamanho aproximado N/b e combine suas soluções para obter a resposta da versão completa do problema.”
Inicialmente, a lista de tamanho N é divida em dois subproblemas de tamanho aproximado N/2, os quais serão chamados, respectivamente, de A e B.
Depois, recursivamente o algoritmo vai encontrar a maior sublista contígua em A (max_A) e a maior sublista contígua em B (max_B).
Solução 3 Solução 3
No entanto, a maior sublista contígua também pode estar parte em A e parte em B, o que será computado pela variável max_C. Para a computação de max_C, é utilizado o fato de que sua maior parte em A deve começar exatamente na extremidade direita de A (a qual faz fronteira com B) e avançar para dentro da parte A e da mesma forma a maior parte em B deve começar na extremidade de B que faz fronteira com A e avançar para dentro da parte B. Assim, max_C corresponderá à soma dessas duas maiores partes definidas anteriormente. sucessivas divisões por 2 do problema inicial e dos subproblemas gerados a cada
Solução 3 Solução 3
No caso base do algoritmo, é tratado o caso da menor sublista possível, a qual corresponde à de apenas um elemento e nesse caso a maior soma é o próprio elemento ou zero se o mesmo for negativo. Ainda no caso base, também é dado o tratamento para o caso da lista vazia, ou seja, com nenhum elemento. Nesse caso, a saída é definida como zero.
Solução 3 Solução 3
O tempo de execução do algoritmo de divisão e conquista é O(n log n), significativamente mais rápido que o algoritmo quadrático anteriormente apresentado. Esse tempo de execução advém do fato de que o algoritmo faz O(n) operações a cada nível das chamadas recursivas e o número de níveis corresponde a O(log n), a altura de uma árvore binária completa resultante das sucessivas divisões por 2 do problema inicial e dos subproblemas gerados a cada chamada recursiva.
Solução de ordem n log n Solução de ordem n log n
#include <bits/stdc++.h>
using namespace std;
int divisao_conquista(vector <int> v, int a, int b) {
int c, max_A, max_B, max_C, soma;
if (a == b) {
return max(0, v[a]);
}
c = (a + b) / 2;
max_A = divisao_conquista(v, a, c);
max_B = divisao_conquista(v, c + 1, b);
max_A = soma = v[c];
for (int i = c - 1; i >= a; --i) {
soma += v[i];
max_A = max(max_A, soma);
}
Solução de ordem n log n Solução de ordem n log n
max_B = soma = v[c + 1];
for (int i = c + 2; i <= b; ++i) {
soma += v[i];
max_B = max(max_B, soma);
}
max_C = max_A + max_B;
max_C = max(max_A, max_C);
return max(max_C, max_B);
}
Solução de ordem n log n Solução de ordem n log n
int main() {
int n, soma_maior, x;
vector <int> v;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &x);
v.push_back(x);
}
soma_maior = divisao_conquista(v, 0, n);
printf("A soma maior da sublista eh: %d\n", soma_maior);
return 0;
}
Solução 4 Solução 4
Surpreendentemente, é possível resolver o problema em O(n), o que significa que apenas um loop é suficiente. A ideia é calcular, para cada posição da matriz, a soma máxima de um subarray que termina nessa posição. Depois disso, a resposta para o problema é o máximo dessas somas.
Considere o subproblema de encontrar o subarray da soma máxima que termina na posição i. Existem duas possibilidades:
1.O subarray apenas contém o elemento na posição i.
Solução 4 Solução 4
No último caso, uma vez que queremos encontrar um subarray com soma máxima, o subarray que termina na posição i - 1 também deve ter a soma máxima.
Assim, podemos resolver o problema de forma eficiente ao calcular a soma de subarray máxima para cada posição final da esquerda para a direita.
O algoritmo contém apenas um loop, de modo que a complexidade do tempo é O(n). Esta é também a melhor complexidade de tempo possível, porque qualquer algoritmo para o problema tem que examinar todos os elementos da matriz pelo menos uma vez.
Solução de ordem 1 Solução de ordem 1
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, soma_maior, soma, x;
vector <int> v;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &x);
v.push_back(x);
}
soma_maior = soma = 0;
for (int i = 0; i < n; i++) {
soma = max(v[i], soma + v[i]);
soma_maior = max(soma_maior, soma);
}
printf("A soma maior da sublista eh: %d\n", soma_maior);
Exercício Exercício
Timus 1146 UVA 108 UVA 11059 UVA 10827 UVA 507 UVA 10154 UVA 12640
Codeforces 327 A Codeforces 279 B URI 1310
URI 1932
SPOJ BAPOSTAS SPOJ SALDO
Você pode fazer eles no codepit.io
CIC 110 2 senha: unifei
Você pode fazer eles no codepit.io
CIC 110 2 senha: unifei