Skip to content

Latest commit

 

History

History
208 lines (158 loc) · 8.45 KB

Conjuntos.md

File metadata and controls

208 lines (158 loc) · 8.45 KB

Teoria dos Conjuntos

Conceitos Fundamentais

O principal axioma da Teoria dos Conjuntos, que relaciona os termos primitivos elemento e conjunto diz que a afirmação "Um elemento pertence a um conjunto" é uma proposição. A simplicidade aparente deste axioma esconde dois importante fatos: ao considerar como proposição a afirmação dada, decorre que

  1. a pertinência estabelece a relação entre elementos e conjuntos: dado um elemento qualquer e um conjunto qualquer, este elemento pertence (ou não) ao conjunto;
  2. a Teoria dos Conjuntos fica edificada sobre a Lógica, uma vez que o que vale para proposições valerá para este axioma também.

Em geral, elementos são representados por letras minúsculas e conjuntos por letras maiúsculas. Dizer que "um elemento está contido no conjunto A" ou que "o conjunto A pertence ao conjunto B" não só é impreciso como é logicamente falso: a relação de pertinência se dá entre conjuntos e elementos (a relação de subconjunto, associada ao termo "contido", se dá entre conjuntos; elementos se relacionam entre si por relação de igualdade).

Este axioma permite também definir precisamente um conjunto especial, denominado conjunto vazio: ∅ é um conjunto vazio se, para qualquer elemento a, a não pertence a ∅. Veja que esta definição não é baseada na ideia de cardinalidade (número de elementos de um conjunto), a qual nem mesmo foi definida ainda, e ainda permite provar fatos importantes, como o que o conjunto vazio é único e que todo conjunto contém o conjunto vazio.

Caracterização de Conjuntos

Uma vez definida a relação entre conjuntos e elementos, torna-se fundamental definir como um conjunto pode ser descrito. A Teoria do Conjunto nos fornece duas formas:

  1. a enumeração de todos os seus elementos;
  2. descrição das propriedades comuns a todos os elementos do conjunto. Mais formalmente, se P(x) é uma sentença aberta em x (isto é, uma sentença tal que, uma vez atribuí um valor específico para a variável x, tal sentença se torna uma proposição), então {x ∈ X | P(x) é verdadeira} é um conjunto, onde X é o conjunto de todos os possíveis valores de x.

Usando as duas formas acima, podemos descrever o conjunto A dos números pares positivos menores ou iguais a 10 como

  1. A = {2, 4, 6, 8, 10}, ou
  2. A = {x ∈ N | 2 ≤ x ≤ 10}

Operações em Conjuntos

Dados dois conjuntos A e B, é possível definir três novos conjuntos:

  1. conjunto união A ∪ B = {x | x ∈ A ou x ∈ B}
  2. conjunto interseção A ∩ B = {x | x ∈ A e x ∈ B}
  3. conjunto diferença A \ B = {x ∈ A | x ∉ B}

Observe que os três operadores acima são definidos em termos dos conectivos lógicos fundamentais:

  • disjunção, na união;
  • conjunção, na interseção; e
  • negação na diferença.

Esta relação permite a verificação das propriedades destes operadores e a relação entre eles (como o equivalente das Leis de Morgan para união e interseção).

C e C++

Há três maneiras de se representar conjuntos em C++: as classes set e bitset, e o tipo primitivo int, sendo que a última também é válida em C.

A biblioteca padrão do C++ traz a implementação da classe set (#include <set>), que abstrai a ideia de conjuntos, e provê operações elementares (como inserção e remoção de elementos, através dos métodos insert() e erase(), ou relações de pertinência, com o método count()). Nesta classe os elementos são únicos, e armazenados ordenadamente (uma travessia padrão é feita do menor para o maior elemento). Contudo, as operações de união, interseção e diferença de conjuntos são feitas em qualquer contêiner ordenado, através das funções set_union(), set_intersection() e set_difference(), disponíveis na biblioteca de algoritmos (#include <algorithm>) e exemplificadas abaixo.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
    vector<int> A { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };    // Conjunto A
    vector<int> B { 2, 3, 5, 7, 11, 13 };               // Conjunto A

    vector<int> C;
    
    set_union(A.begin(), A.end(), B.begin(), B.end(), back_inserter(C));

    cout << "union = ";
    for (auto x : C)
        cout << x << " ";
    cout << endl;                 // C = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13 }

    C.clear();
    set_intersection(A.begin(), A.end(), B.begin(), B.end(), back_inserter(C));

    cout << "intersection = ";
    for (auto x : C)
        cout << x << " ";
    cout << endl;                 // C = { 2, 3, 5, 7 }

    C.clear();
    set_difference(A.begin(), A.end(), B.begin(), B.end(), back_inserter(C));

    cout << "difference = ";
    for (auto x : C)
        cout << x << " ";
    cout << endl;                 // C = { 1, 4, 6, 8, 9, 10 }

    return 0;
}

O tipo de dados primitivo int (ou sua variante long long, com maior capacidade de armazenamento) também pode ser usado para uma interpretação mais compacta e eficiente de conjuntos. Em geral o tipo int tem 32 bits de tamanho, e podemos cada elemento do conjunto universo (que contém todos os elementos possíveis, numa quantidade menor ou igual a 32) com cada bit, de modo que, se o bit está ligado, o elemento pertence ao conjunto; e se desligado, não pertence.

Fora a restrição em relação ao número de elementos do conjunto universo, esta representação tem várias vantagens:

  • ocupa pouco espaço em memória (4 bytes a cada 32 elementos);
  • permite responder relações de pertinência em complexidade O(1);
  • permite operações de união, interseção e diferença também em O(1).

A última característica se dá por conta da definição de tais operações em termos dos conectivos lógicos, e pelo fato das linguagens C e C++ disponibilizarem tais operações bit a bit. Veja o exemplo anterior reescrito em termos desta nova representação. Considere que o conjunto universo U = {1, 2, 3, ..., 32}.

#include <iostream>

using namespace std;

int main()
{
    int A = 2046;       // A = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, bits correspondentes ligados
    int B = 10412;      // B = { 2, 3, 5, 7, 11, 13 }, mesmo motivo

    int C = A | B;      // C = 12286

    cout << "union = ";
    for (int x = 1; x <= 16; ++x)
        if (C & (1 << x))
            cout << x << " ";
    cout << endl;                 // C = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13 }

    C = A & B;          // C = 172

    cout << "intersection = ";
    for (int x = 1; x <= 16; ++x)
        if (C & (1 << x))
            cout << x << " ";
    cout << endl;                 // C = { 2, 3, 5, 7 }

    C = A & ~B;       // C = 1874

    cout << "difference = ";
    for (int x = 1; x <= 16; ++x)
        if (C & (1 << x))
            cout << x << " ";
    cout << endl;                 // C = { 1, 4, 6, 8, 9, 10 }

    return 0;
}

Para conjuntos universos com mais de 32 elementos (ou 64, no caso de variáveis long long), ou se usa um vetor de inteiros, ou a classe bitset (#include <bitset>), que pode armazenar uma quantidade arbitrária de bits (que deve ser conhecida em tempo de compilação) e que traz em sua interface as operações básicas dos conjuntos e suporte para os operadores lógicos bit a bit.

A linguagem C++ também oferece, em sua biblioteca padrão, a classe multiset (#include <multiset>), que permite o armazenamento de elementos repetidos (embora seja uma extrapolação da ideia básica de um conjunto, tal representação pode ser útil em alguns contextos).

Exercícios

  1. Codeforces
    1. 228A - Is your horseshoe on the other hoof?
    2. 236A - Boy or Girl
  2. UVA
    1. 501 - Black Box
    2. 11849 - CD

Referências

HALE, Margie. Essentials of Mathematics: Introduction to Theory, Proof, and the Professional Culture. Mathematical Association of America, 2003.