// Definição da estrutura do nó
typedef struct No {
int dado; // Valor armazenado
struct No *esquerda; // Ponteiro para filho esquerdo
struct No *direita; // Ponteiro para filho direito
} No;
// Função para criar um novo nó
No *criarNo(int valor) {
No *novoNo = (No*)malloc(sizeof(No));
if (novoNo == NULL) {
printf("Erro: Falha na alocação de memória!\n");
exit(1);
}
novoNo->dado = valor;
novoNo->esquerda = NULL;
novoNo->direita = NULL;
return novoNo;
}
No *inserir(No *raiz, int valor) {
// Caso base: posição encontrada
if (raiz == NULL) {
return criarNo(valor);
}
// Escolher direção baseada no valor
if (valor < raiz->dado) {
raiz->esquerda = inserir(raiz->esquerda, valor);
} else if (valor > raiz->dado) {
raiz->direita = inserir(raiz->direita, valor);
}
return raiz;
}
Passo 1: Inserir 50 Passo 2: Inserir 30 Passo 3: Inserir 70
50 50 50
/ / \
30 30 70
Passo 4: Inserir 20 Passo 5: Inserir 40
50 50
/ \ / \
30 70 30 70
/ / \
20 20 40
Dada a sequência de inserção: [25, 15, 35, 10, 20, 30, 40]
Desenhe a BST resultante e identifique:
bool buscar(No *raiz, int valor) {
// Caso base: árvore vazia ou valor não encontrado
if (raiz == NULL) {
return false;
}
// Valor encontrado
if (raiz->dado == valor) {
return true;
}
// Escolher direção da busca
if (valor < raiz->dado) {
return buscar(raiz->esquerda, valor);
} else {
return buscar(raiz->direita, valor);
}
}
50
/ \
30 70 ← Altura: log₂(n)
/ \ / \
20 40 60 80
50
\
60 ← Altura: n
\
70
\
80
Operação | Array Ordenado | Lista Ligada | BST (médio) | BST (pior) |
---|---|---|---|---|
Busca | O(log n) | O(n) | O(log n) | O(n) |
Inserção | O(n) | O(1) | O(log n) | O(n) |
Remoção | O(n) | O(n) | O(log n) | O(n) |
Espaço | O(n) | O(n) | O(n) | O(n) |
50 ← 40 < 50, vai para esquerda
/ \
30 70 ← 40 > 30, vai para direita
/ \ / \
20 40 60 80 ← 40 = 40, ENCONTRADO!
n (elementos) | Altura | Comparações máximas |
---|---|---|
7 | 3 | 3 |
15 | 4 | 4 |
31 | 5 | 5 |
63 | 6 | 6 |
127 | 7 | 7 |
// Testando diferentes valores
int valorBusca[] = {40, 25, 80, 100};
for (int i = 0; i < 4; i++) {
if (buscar(raiz, valorBusca[i])) {
printf("Valor %d: ENCONTRADO\n", valorBusca[i]);
} else {
printf("Valor %d: NÃO ENCONTRADO\n", valorBusca[i]);
}
}
Para a BST abaixo, determine quantas comparações são necessárias para:
15
/ \
10 20
/ \ \
5 12 25
Algoritmos para visitar todos os nós de uma árvore de forma sistemática.
void percursoEmOrdem(No *raiz) {
if (raiz != NULL) {
percursoEmOrdem(raiz->esquerda); // Esquerda
printf("%d ", raiz->dado); // Raiz
percursoEmOrdem(raiz->direita); // Direita
}
}
50
/ \
30 70
/ \ / \
20 40 60 80
void percursoPreOrdem(No *raiz) {
if (raiz != NULL) {
printf("%d ", raiz->dado); // Raiz
percursoPreOrdem(raiz->esquerda); // Esquerda
percursoPreOrdem(raiz->direita); // Direita
}
}
50
/ \
30 70
/ \ / \
20 40 60 80
void percursoPosOrdem(No *raiz) {
if (raiz != NULL) {
percursoPosOrdem(raiz->esquerda); // Esquerda
percursoPosOrdem(raiz->direita); // Direita
printf("%d ", raiz->dado); // Raiz
}
}
50
/ \
30 70
/ \ / \
20 40 60 80
50
/ \
30 70
/ \ / \
20 40 60 80
Percurso | Sequência | Aplicação Principal |
---|---|---|
Em Ordem | 20 30 40 50 60 70 80 | Dados ordenados |
Pré-Ordem | 50 30 20 40 70 60 80 | Copiar estrutura |
Pós-Ordem | 20 40 30 60 80 70 50 | Liberar memória |
1. Vai até o nó mais à esquerda (20)
2. Visita o nó (20)
3. Sobe e visita o pai (30)
4. Vai para direita e visita (40)
5. Continue o padrão...
1. Visita a raiz imediatamente (50)
2. Desce para esquerda (30)
3. Continue recursivamente...
1. Vai até as folhas primeiro
2. Visita filhos antes dos pais
3. Raiz é sempre a última
printf("Em Ordem: ");
percursoEmOrdem(raiz);
printf("\nPré-Ordem: ");
percursoPreOrdem(raiz);
printf("\nPós-Ordem: ");
percursoPosOrdem(raiz);
25
/ \
15 35
/ \ / \
10 20 30 40
Complete:
A remoção é a operação mais complexa em BST pois precisamos manter a propriedade de ordenação.
// Exemplo: Remover 20
50 50
/ \ / \
30 70 → 30 70
/ \ / \ \ / \
20 40 60 80 40 60 80
if (raiz->esquerda == NULL && raiz->direita == NULL) {
free(raiz);
return NULL;
}
// Exemplo: Remover 30 (que tem apenas filho direito 40)
50 50
/ \ / \
30 70 → 40 70
\ / \ / \
40 60 80 60 80
else if (raiz->esquerda == NULL) {
No *temp = raiz->direita;
free(raiz);
return temp;
} else if (raiz->direita == NULL) {
No *temp = raiz->esquerda;
free(raiz);
return temp;
}
// Exemplo: Remover 50
50 60
/ \ / \
30 70 → 30 70
/ \ / \ / \ \
20 40 60 80 20 40 80
No *remover(No *raiz, int valor) {
if (raiz == NULL) return raiz;
if (valor < raiz->dado) {
raiz->esquerda = remover(raiz->esquerda, valor);
} else if (valor > raiz->dado) {
raiz->direita = remover(raiz->direita, valor);
} else {
// Nó encontrado - aplicar um dos três casos
// Caso 1: Nó folha
if (raiz->esquerda == NULL && raiz->direita == NULL) {
free(raiz);
return NULL;
}
// Caso 2: Um filho
else if (raiz->esquerda == NULL) {
No *temp = raiz->direita;
free(raiz);
return temp;
} else if (raiz->direita == NULL) {
No *temp = raiz->esquerda;
free(raiz);
return temp;
}
// Caso 3: Dois filhos
else {
No *sucessor = encontrarMinimo(raiz->direita);
raiz->dado = sucessor->dado;
raiz->direita = remover(raiz->direita, sucessor->dado);
}
}
return raiz;
}
int altura(No *raiz) {
if (raiz == NULL) return -1;
int altEsq = altura(raiz->esquerda);
int altDir = altura(raiz->direita);
return 1 + (altEsq > altDir ? altEsq : altDir);
}
int contarNos(No *raiz) {
if (raiz == NULL) return 0;
return 1 + contarNos(raiz->esquerda) + contarNos(raiz->direita);
}
void liberarArvore(No *raiz) {
if (raiz != NULL) {
liberarArvore(raiz->esquerda);
liberarArvore(raiz->direita);
free(raiz); // Liberar depois dos filhos!
}
}
int main() {
No *raiz = NULL;
// Inserções
int valores[] = {50, 30, 70, 20, 40, 60, 80};
for (int i = 0; i < 7; i++) {
raiz = inserir(raiz, valores[i]);
}
// Informações
printf("Altura: %d\n", altura(raiz));
printf("Total de nós: %d\n", contarNos(raiz));
// Percursos
printf("Em ordem: ");
percursoEmOrdem(raiz);
// Buscas
printf("\nBuscar 40: %s\n", buscar(raiz, 40) ? "Encontrado" : "Não encontrado");
// Remoção
raiz = remover(raiz, 30);
// Limpeza
liberarArvore(raiz);
return 0;
}
Critério | Array | Lista Ligada | Hash Table | BST |
---|---|---|---|---|
Busca | ||||
Inserção | ||||
Ordenação | Prévia | |||
Memória | Compacta | Extra | Extra | Moderada |
Range Query |
*Array ordenado | **Caso médio
Implemente um programa que:
### 💡 Dica Pedagógica - Desenhe a árvore no quadro conforme insere - Mostre como a propriedade BST é mantida - Destaque a natureza recursiva do algoritmo
- ###### Altura da árvore: $h = log_₂(n)$ - ###### 💡 Insight: Com 1 milhão de elementos, máximo 20 comparações!
### 🎮 Interação com a turma - Peça aos alunos para prever o resultado - Mostre o caminho percorrido para cada busca - Conte as comparações realizadas
- ⏰ Tempo sugeriro era de 25 minutos! - 🏆 **Apresentação dos melhores códigos**
--- # 🎯 Próximos Passos ## 📖 **Para Estudar:** 1. **Árvores AVL** - Balanceamento automático 2. **Árvores Red-Black** - Usadas em bibliotecas 3. **B-Trees** - Para bases de dados 4. **Heap** - Árvore binária completa ## 💻 **Para Praticar:** 1. Implementar validador de BST 2. Converter BST para array ordenado 3. Encontrar k-ésimo menor elemento 4. Implementar iterator para BST ## 🔬 **Para Pesquisar:** - Como Java TreeSet funciona? - Por que C++ std::map usa Red-Black? - Como bancos de dados usam B-Trees?