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 04 AULA 04
– Data structures
Estrutura de Dados Estrutura de Dados
Uma estrutura de dados é uma maneira de armazenar dados na memória de um computador. É importante escolher uma estrutura de dados adequada para um problema, pois cada estrutura de dados tem suas próprias vantagens e desvantagens. A questão crucial é: quais operações são eficientes na estrutura de dados escolhida?
Vamos ver as estruturas de dados mais importantes
na biblioteca padrão C++. É uma boa ideia usar a
biblioteca padrão sempre que possível, porque
economizará muito tempo.
Vetor Dinâmico Vetor Dinâmico
Um vetor dinâmico é um vetor cujo tamanho pode ser alterado durante a execução do programa. O vetor dinâmico mais popular em C++ é a estrutura vector, que pode ser usada quase como um vetor comum, como fizemos na aula 2.
O código a seguir cria um vetor vazio:
vector<int> v;
vector<float> v;
vector<double> v;
vector<char> v;
vector<string> v;
Problema URI 1237 Problema URI 1237
Comparação de Substring
Encontre a maior substring comum entre as duas strings informadas. A substring pode ser qualquer parte da string, inclusive ela toda. Se não houver subsequência comum, a saída deve ser “0”. A comparação é case sensitive ('x' != 'X').
Entrada
A entrada contém vários casos de teste. Cada caso de teste é composto por duas linhas, cada uma contendo uma string. Ambas strings de entrada contém entre 1 e 50 caracteres ('A'-'Z','a'-'z' ou espaço ' '), inclusive, ou no mínimo uma letra ('A'-'Z','a'-'z').
Saída
O tamanho da maior subsequência comum entre as
duas Strings.
Problema URI 1237 Problema URI 1237
#include <bits/stdc++.h>
using namespace std;
vector<int> compute_z(const string &s) {
int n = s.size();
vector<int> z(n,0);
int l,r;
r = l = 0;
for(int i = 1; i < n; ++i) {
if(i > r) {
l = r = i;
while(r < n and s[r - l] == s[r])r++;
z[i] = r - l;r--;
Problema URI 1237 Problema URI 1237
} else {
int k = i-l;
if(z[k] < r - i +1) z[i] = z[k];
else { l = i;
while(r < n and s[r - l] == s[r])r++;
z[i] = r - l;r--;
} }
}
return z;
}
Problema URI 1237 Problema URI 1237
int main() {
string a, b;
while (getline(cin, a) && getline(cin, b)) { int ans = 0;
for (int i = 0; i < a.size(); ++i) {
string c = a.substr(i);
int t = c.size();
c = c + '$' + b;
vector<int> z = compute_z(c);
for (int j = t; j < c.size(); ++j) {
ans = max(ans, z[j]);
} }
cout << ans << endl;
}
return 0;
}
Estrutura Set Estrutura Set
Um conjunto é uma estrutura de dados que mantém uma coleção de elementos. As operações básicas dos conjuntos são a inserção, busca e remoção de elementos. A biblioteca padrão C++ contém duas implementações definidas: o conjunto de estrutura é baseado em uma árvore binária balanceada e suas operações funcionam no tempo O(log n). A estrutura unordered_set usa hash, e suas operações funcionam em tempo de O(1) em média.
A escolha da implementação definida para usar é
muitas vezes uma questão de gosto. O benefício da
estrutura definida é que ele mantém a ordem dos
elementos e fornece funções que não estão disponíveis
no unordered_set. Por outro lado, unordered_set pode
ser mais eficiente.
Estrutura Set Estrutura Set
O código a seguir cria um conjunto que contém
números inteiros e mostra algumas das operações. A
inserção de função adiciona um elemento ao
conjunto, a contagem de função retorna o número de
ocorrências de um elemento no conjunto e a função
apaga remove um elemento do conjunto.
Estrutura Set Estrutura Set
#include <bits/stdc++.h>
using namespace std;
int main() {
set<int> s;
s.insert(3);
s.insert(2);
s.insert(5);
printf("%d\n", s.count(3));
printf("%d\n", s.count(4));
s.erase(3);
s.insert(4);
printf("%d\n", s.count(3));
printf("%d\n", s.count(4));
return 0;
}
Estrutura Set Estrutura Set
Um conjunto pode ser usado principalmente como
um vetor, mas não é possível acessar os elementos
usando a notação []. O código a seguir cria um
conjunto, imprime o número de elementos e itera
através de todos os elementos:
Estrutura Set Estrutura Set
#include <bits/stdc++.h>
using namespace std;
int main() {
set<int> s = {2, 5, 6, 8};
printf("Tamanho do Conjunto: %d\n", s.size());
printf("s = {");
for (auto x : s) {
printf(" %d", x);
}
printf("}\n");
return 0;
} Tamanho do Conjunto: 4
s = { 2 5 6 8}
Estrutura Set Estrutura Set
Uma propriedade importante dos conjuntos é que
todos os seus elementos são distintos. Assim, a
contagem de função sempre retorna 0 (o elemento
não está no conjunto) ou 1 (o elemento está no
conjunto) e a inserção de função nunca adiciona um
elemento ao conjunto se ele já estiver lá. O código a
seguir ilustra isso:
Estrutura Set Estrutura Set
Tamanho do Conjunto: 4 s = { 2 5 6 8}
Tamanho do Conjunto: 5 s = { 2 5 6 7 8}
#include <bits/stdc++.h>
using namespace std;
int main() {
set<int> s = {2, 5, 6, 8};
printf("Tamanho do Conjunto: %d\n", s.size());
printf("s = {");
for (auto x : s) {
printf(" %d", x);
}
printf("}\n");
s.insert(5);
s.insert(7);
s.insert(8);
printf("Tamanho do Conjunto: %d\n", s.size());
printf("s = {");
for (auto x : s) {
printf(" %d", x);
}
printf("}\n");
return 0;
}
Estrutura Multiset Estrutura Multiset
C++ também contém as estruturas multiset e unordered_multiset que, de outra forma, funcionam como set e unordered_set, mas podem conter várias instâncias de um elemento. Por exemplo, no seguinte código, as três instâncias do número 5 são adicionadas a um multiset:
multiset<int> s;
s.insert(5); s.insert(5); s.insert(5);
printf("Quantidade de 5: %d\n", s.count(5));
Estrutura Multiset Estrutura Multiset
A função apagar remove todas as instâncias de um elemento de um multiset:
Muitas vezes, apenas uma instância deve ser removida, o que pode ser feito da seguinte maneira:
s.erase(5);
printf("Quantidade de 5: %d\n", s.count(5));
s.erase(s.find(5));
printf("Quantidade de 5: %d\n", s.count(5));
Estrutura Multiset Estrutura Multiset
#include <bits/stdc++.h>
using namespace std;
int main() {
multiset<int> s;
s.insert(5); s.insert(5); s.insert(5);
printf("Quantidade de 5: %d\n", s.count(5));
printf("s = {");
for (auto x : s) {
printf(" %d", x);
}
printf("}\n");
s.erase(5);
printf("Quantidade de 5: %d\n", s.count(5));
Estrutura Multiset Estrutura Multiset
printf("s = {");
for (auto x : s) {
printf(" %d", x);
}
printf("}\n");
s.insert(5); s.insert(5); s.insert(5);
s.erase(s.find(5));
printf("Quantidade de 5: %d\n", s.count(5));
printf("s = {");
for (auto x : s) {
printf(" %d", x);
}
printf("}\n");
return 0;
}
Quantidade de 5: 3 s = { 5 5 5}
Quantidade de 5: 0 s = {}
Quantidade de 5: 2 s = { 5 5}
Estrutura Map Estrutura Map
Um mapa é uma matriz generalizada que consiste em pares de valores chave. Enquanto as teclas em uma matriz comum são sempre os inteiros consecutivos 0, 1, ... , N - 1, onde n é o tamanho da matriz, as chaves em um mapa podem ser de qualquer tipo de dados e não precisam ser valores consecutivos.
A biblioteca padrão C++ contém duas implementações de mapa que correspondem às implementações definidas: o mapa de estrutura é baseado em uma árvore binária equilibrada e os elementos de acesso levam tempo O(log n), enquanto a estrutura unordered_map usa hash e acessar elementos leva O(1) de tempo em média.
O código a seguir cria um mapa onde as chaves são
strings e os valores são inteiros:
Estrutura Map Estrutura Map
#include <bits/stdc++.h>
using namespace std;
int main() {
map<string,int> m;
m["monkey"] = 4;
m["banana"] = 3;
m["harpsichord"] = 9;
printf("monkey: %d\n", m["monkey"]);
printf("banana: %d\n", m["banana"]);
printf("harpsichord: %d\n", m["harpsichord"]);
return 0;
}
monkey: 4 banana: 3
harpsichord: 9
Estrutura Map Estrutura Map
O código a seguir imprime todas as chaves e valores em um mapa e se o valor de uma chave for solicitado, mas o mapa não o contém, a chave é automaticamente adicionada ao mapa com um valor padrão. Por exemplo, no código a seguir, a chave
"roberto" com valor 0 é adicionada ao mapa.
Estrutura Map Estrutura Map
roberto - 0 banana - 3
harpsichord - 9 monkey - 4 roberto - 0
#include <bits/stdc++.h>
using namespace std;
int main() {
map<string,int> m;
m["monkey"] = 4;
m["banana"] = 3;
m["harpsichord"] = 9;
printf("roberto - %d\n", m["roberto"]);
for (auto x : m) {
cout << x.first << " - " << x.second << "\n";
}
return 0;
}
Estrutura Bitset Estrutura Bitset
Um bitset é um vetor cujo cada valor seja 0 ou 1.
O benefício do uso de bitsets é que eles exigem menos memória do que matrizes comuns, porque cada elemento em um bitset usa apenas um bit de memória. Por exemplo, se n bits forem armazenados em uma matriz int, serão utilizados 32n bits de memória, mas um bitetro correspondente apenas requer n bits de memória. Além disso, os valores de um bitset podem ser manipulados de forma eficiente usando operadores de bits, o que permite otimizar algoritmos usando conjuntos de bits.
O código a seguir mostra como manipular o bitset:
Estrutura Bitset Estrutura Bitset
#include <bits/stdc++.h>
using namespace std;
int main() {
bitset<10> s(string("0010011010"));
// from right to left
cout << "s = (" << s << ")\n";
cout << "s[4] = " << s[4] << "\n";
cout << "s[5] = " << s[5] << "\n";
printf("Total = %d\n", s.count());
bitset<10> a(string("0010110110"));
bitset<10> b(string("1011011000"));
cout << "a = (" << a << ")\n";
cout << "b = (" << b << ")\n";
cout << "a&b = (" << (a&b) << ")\n";
cout << "a|b = (" << (a|b) << ")\n";
cout << "a^b = (" << (a^b) << ")\n";
return 0;
}
s = (0010011010) s[4] = 1
s[5] = 0 Total = 4
a = (0010110110) b = (1011011000) a&b = (0010010000) a|b = (1011111110) a^b = (1001101110)
Estrutura Deque Estrutura Deque
Um deque é um vetor dinâmica cujo tamanho pode ser eficientemente alterado nas duas extremidades da matriz. Como um vetor, um deque fornece as funções push_back e pop_back, mas também inclui as funções push_front e pop_front que não estão disponíveis em um vetor.
Um deque pode ser usado da seguinte forma:
Estrutura Deque
Estrutura Deque
Estrutura Deque Estrutura Deque
#include <bits/stdc++.h>
using namespace std;
int main() {
deque<int> d;
d.push_back(5);
printf("d = {");
for (auto x : d) {
printf(" %d", x);
}
printf("}\n");
d.push_back(2);
printf("d = {");
for (auto x : d) {
printf(" %d", x);
}
printf("}\n");
d.push_front(3);
printf("d = {");
for (auto x : d) {
printf(" %d", x);
}
printf("}\n");
d.pop_back();
printf("d = {");
for (auto x : d) {
printf(" %d", x);
}
printf("}\n");
d.pop_front();
printf("d = {");
for (auto x : d) {
printf(" %d", x);
}
printf("}\n");
return 0;
}
d = { 5}
d = { 5 2}
d = { 3 5 2}
d = { 3 5}
d = { 5}
Estrutura Deque Estrutura Deque
A implementação interna de um deque é mais
complexa do que a de um vetor, e por esse motivo, um
deque é mais lento do que um vetor. Ainda assim, a
adição e a remoção de elementos levam o tempo de
O(1) em média nas duas extremidades.
Estrutura Stack Estrutura Stack
Uma pilha é uma estrutura de dados que fornece duas operações de tempo de O(1): adicionando um elemento na parte superior e removendo um elemento da parte superior. Só é possível acessar o elemento superior de uma pilha.
O código a seguir mostra como uma pilha pode ser
usada:
Estrutura Stack
Estrutura Stack
Estrutura Stack Estrutura Stack
#include <bits/stdc++.h>
using namespace std;
int main() {
stack<int> s;
s.push(3);
printf("Elemento do topo: %d\n", s.top());
s.push(2);
printf("Elemento do topo: %d\n", s.top());
s.push(5);
printf("Elemento do topo: %d\n", s.top());
s.pop();
printf("Elemento do topo: %d\n", s.top());
return 0;
}
Elemento do topo: 3 Elemento do topo: 2 Elemento do topo: 5 Elemento do topo: 2
Estrutura Queue Estrutura Queue
Uma fila também fornece duas operações de tempo de O(1): adicionando um elemento no final da fila e removendo o primeiro elemento na fila. Só é possível acessar o primeiro e último elemento de uma fila.
O código a seguir mostra como uma fila pode ser
usada:
Estrutura Queue
Estrutura Queue
Estrutura Queue Estrutura Queue
#include <bits/stdc++.h>
using namespace std;
int main() {
queue<int> q;
q.push(3);
printf("Elemento do inicio: %d\n", q.front());
q.push(2);
printf("Elemento do inicio: %d\n", q.front());
q.push(5);
printf("Elemento do inicio: %d\n", q.front());
q.pop();
printf("Elemento do inicio: %d\n", q.front());
return 0;
}
Elemento do inicio: 3 Elemento do inicio: 3 Elemento do inicio: 3 Elemento do inicio: 2
Prioridade queue Prioridade queue
Uma fila de prioridade mantém um conjunto de elementos. As operações suportadas são inserção e, dependendo do tipo de fila, recuperação e remoção do elemento mínimo ou máximo. A inserção e remoção levam o tempo O(log n) e a recuperação leva O(1) tempo.
Enquanto um conjunto ordenado suporta de forma
eficiente todas as operações de uma fila de
prioridade, o benefício de usar uma fila de prioridade
é que ele tem fatores menores e constantes. Uma fila
de prioridade geralmente é implementada usando
uma estrutura de heap que é muito mais simples do
que uma árvore binária equilibrada usada em um
conjunto ordenado.
Prioridade queue Prioridade queue
Por padrão, os elementos em uma fila de prioridade
C++ são classificados em ordem decrescente, e é
possível encontrar e remover o maior elemento na
fila. O código a seguir ilustra isso:
Prioridade queue Prioridade queue
Elemento do inicio: 3 Elemento do inicio: 5 Elemento do inicio: 7 Elemento do inicio: 7 Elemento do inicio: 5 Elemento do inicio: 3 Elemento do inicio: 6
#include <bits/stdc++.h>
using namespace std;
int main() {
priority_queue<int> q;
q.push(3);
printf("Elemento do inicio: %d\n", q.top());
q.push(5);
printf("Elemento do inicio: %d\n", q.top());
q.push(7);
printf("Elemento do inicio: %d\n", q.top());
q.push(2);
printf("Elemento do inicio: %d\n", q.top());
q.pop();
printf("Elemento do inicio: %d\n", q.top());
q.pop();
printf("Elemento do inicio: %d\n", q.top());
q.push(6);
printf("Elemento do inicio: %d\n", q.top());
return 0;
}
Prioridade queue Prioridade queue
Se quisermos criar uma fila de prioridade que suporte a descoberta e remoção do elemento mais pequeno, podemos fazê-lo da seguinte maneira:
priority_queue<int,vector<int>,greater<int>> q;