Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas


Introdução à Complexidade de Algoritmos e Estruturas de Dados, Esquemas de Algoritmos

resumo do conteúdo disponibilizado pela universidade no SIA

Tipologia: Esquemas

2024

Compartilhado em 02/09/2024

lais-costa-90
lais-costa-90 🇧🇷

1 documento

1 / 15

Toggle sidebar

Esta página não é visível na pré-visualização

Não perca as partes importantes!

bg1
Algoritmo e Complexidade: Resumo
1. Algoritmos e Complexidade
Definição de Algoritmo: Sequência finita de passos bem definidos para
solucionar um problema.
Complexidade: Relacionada à quantidade de situações diferentes a serem
tratadas no algoritmo.
Redução da Complexidade: Pode ser feita através da decomposição de
problemas em sub-rotinas menores e mais simples.
2. Sub-rotinas
Definição: Blocos de código que realizam tarefas específicas dentro de um
programa.
Vantagens: Simplificam o programa principal, diminuem a chance de erro e
organizam o código.
Estrutura: Sub-rotinas possuem um início, fim, e podem receber parâmetros e
retornar valores.
3. Decomposição de Problemas (Top-Down)
Método Top-Down: Divisão de problemas em refinamentos sucessivos, criando
módulos ou sub-algoritmos menores.
Exemplo: Algoritmo para somar dois números usando uma sub-rotina.
4. Parametrização de Sub-rotinas
Parâmetros: Valores que uma sub-rotina recebe para processar e gerar
resultados.
Passagem de Parâmetros: Pode ser por valor (não altera a variável original) ou
por referência (altera a variável original).
5. Estruturas de Dados
Tipos Primitivos: Inteiro, real, caractere e lógico.
Vetores: Arranjos unidimensionais que armazenam múltiplos valores do mesmo
tipo.
Registros: Estruturas que armazenam dados de diferentes tipos dentro de uma
única variável, útil para representar entidades complexas como "Aluno" com
nome e notas.
6. Declaração e Uso de Vetores e Registros
Vetores: Definidos por um tipo e tamanho, manipulados individualmente
através de índices.
Registros: Estruturas heterogêneas que podem conter diversos tipos de dados,
organizados em campos, facilitando a manipulação de informações relacionadas.
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff

Pré-visualização parcial do texto

Baixe Introdução à Complexidade de Algoritmos e Estruturas de Dados e outras Esquemas em PDF para Algoritmos, somente na Docsity!

Algoritmo e Complexidade: Resumo

1. Algoritmos e Complexidade

Definição de Algoritmo: Sequência finita de passos bem definidos para solucionar um problema.  Complexidade: Relacionada à quantidade de situações diferentes a serem tratadas no algoritmo.  Redução da Complexidade: Pode ser feita através da decomposição de problemas em sub-rotinas menores e mais simples.

2. Sub-rotinas

Definição: Blocos de código que realizam tarefas específicas dentro de um programa.  Vantagens: Simplificam o programa principal, diminuem a chance de erro e organizam o código.  Estrutura: Sub-rotinas possuem um início, fim, e podem receber parâmetros e retornar valores.

3. Decomposição de Problemas (Top-Down)

Método Top-Down: Divisão de problemas em refinamentos sucessivos, criando módulos ou sub-algoritmos menores.  Exemplo: Algoritmo para somar dois números usando uma sub-rotina.

4. Parametrização de Sub-rotinas

Parâmetros: Valores que uma sub-rotina recebe para processar e gerar resultados.  Passagem de Parâmetros: Pode ser por valor (não altera a variável original) ou por referência (altera a variável original).

5. Estruturas de Dados

Tipos Primitivos: Inteiro, real, caractere e lógico.  Vetores: Arranjos unidimensionais que armazenam múltiplos valores do mesmo tipo.  Registros: Estruturas que armazenam dados de diferentes tipos dentro de uma única variável, útil para representar entidades complexas como "Aluno" com nome e notas.

6. Declaração e Uso de Vetores e Registros

Vetores: Definidos por um tipo e tamanho, manipulados individualmente através de índices.  Registros: Estruturas heterogêneas que podem conter diversos tipos de dados, organizados em campos, facilitando a manipulação de informações relacionadas.

Algoritmos: Resolução de Problemas Computacionais

Um algoritmo é um conjunto de passos usados para resolver um problema computacional. Ao criar um algoritmo, você deve considerar:

  1. Especificação: Quais propriedades ele deve ter.
  2. Arquitetura: Como os dados serão organizados.
  3. Complexidade: Quanto tempo e espaço ele usará.
  4. Implementação : Como será programado. 5. Testes: Verificar se ele funciona corretamente.

Características dos Algoritmos

Um bom algoritmo deve ser:

  • Eficiente : Executar rapidamente.
  • Simplicidade : Fácil de implementar.
  • Clareza : Fácil de entender e manter.
  • Segurança : Manipular dados com segurança.
  • Funcional : Realizar todas as tarefas necessárias.
  • Modular : Fácil de manter e reutilizar.
  • Amigável : Fácil para os usuários.

Corretude e Eficiência

Um algoritmo é correto se sempre dá a resposta certa. Porém, ele também deve ser eficiente – rápido e usar poucos recursos. Alguns problemas são tão complexos que levam muito tempo para serem resolvidos, mesmo com um algoritmo correto.

Complexidade e Análise de Algoritmos

A complexidade de um algoritmo refere-se ao tempo e espaço que ele precisa para ser executado. Analisar a complexidade é importante para garantir que o algoritmo escolhido seja o mais eficiente possível, especialmente para problemas grandes.

  • Complexidade Espacial: Quanto de memória o algoritmo usa.
  • Complexidade Temporal: Quanto tempo o algoritmo leva para rodar.

Notação Big-O

A Notação O (Big-O) é usada para descrever a complexidade de um algoritmo, especialmente no pior caso. Ela ajuda a comparar diferentes algoritmos e escolher o melhor para um determinado problema.

Exemplos:

  1. Função MinMax: Para encontrar o mínimo e o máximo de um vetor, o algoritmo tem complexidade (O(n)) porque percorre todos os elementos uma vez.
  2. Operação com Matrizes: Dependendo de como os laços são estruturados, a complexidade pode ser (O(n)) para laços simples ou (O(n^2)) para laços aninhados.

Complexidade Comum:

Constante ((O(1))): Tempo fixo, independente do tamanho da entrada.  Logarítmica ((O(\log n))): Usado em algoritmos que dividem o problema repetidamente.  Linear ((O(n))): Cada elemento é processado uma vez.  Quadrática ((O(n^2))): Processamento em pares, comum em laços aninhados.  Cúbica ((O(n^3))): Usado em problemas complexos como multiplicação de matrizes.  Exponencial e Fatorial: Crescem rapidamente e são usados em algoritmos de força bruta, mas são impraticáveis para grandes entradas. Esses conceitos ajudam a entender a eficiência dos algoritmos e escolher o mais adequado para resolver um problema específico, considerando sempre o pior caso possível. Recursividade é um método utilizado em matemática e programação para definir um problema ou calcular algo quebrando-o em subproblemas menores até chegar a um caso base, que é trivialmente resolvido. Existem dois componentes principais em uma definição recursiva:

  1. Base : Define explicitamente os casos mais simples.
  2. Passo recursivo : Define os outros casos em termos dos anteriores. Exemplo de Recursividade:Sequências Recursivas : Um exemplo clássico é a sequência de Fibonacci, onde cada número é a soma dos dois anteriores. Funções Recursivas:Fatorial : A função fatorial de um número nnn (escrito como n!n!n!) é definida como n!=n×(n−1)!n! = n \times (n-1)!n!=n×(n−1)! com o caso base 0!=10! = 10! =1. Indução Matemática:  A recursão também pode ser usada em provas matemáticas, como na indução, para mostrar que uma sequência ou função está bem definida.

Tipos de Recursividade:

  1. Recursão em cauda : Quando a chamada recursiva é a última operação a ser executada.
  2. Recursão indireta : Uma função chama outra, que eventualmente chama a primeira novamente.
  3. Recursão aninhada : Funções recursivas que chamam outras funções recursivas como parte de seus argumentos, como a função de Ackermann. Diferença entre Recursão e Iteração:  Na recursão, uma função chama a si mesma, enquanto na iteração, são utilizados loops como "for" ou "while" para repetição. Leis da Recursão:
  4. Deve haver um caso base.
  5. A função deve modificar seu estado e se aproximar do caso base.
  6. A função deve se chamar recursivamente. Recursividade é muito usada em computação, especialmente em problemas complexos onde a solução é mais simples e elegante do que usar loops.

Ordenação de Dados: Conceito e Algoritmos

1. O que é Ordenação? Ordenação é o processo de organizar um conjunto de dados em uma ordem específica, como numérica (1, 2, 3,...) ou alfabética (Ana, Bianca, ...). Essa organização facilita a recuperação de informações, como encontrar um nome em uma lista telefônica. 2. Chave de Ordenação A chave de ordenação é o critério usado para comparar e ordenar os dados. Por exemplo, em uma lista de nomes, a chave seria o nome em si. 3. Tipos de OrdenaçãoInterna (in-place): Ocorre na memória principal, ideal para conjuntos de dados pequenos.  Externa: Ocorre em armazenamento secundário (como discos), usada quando o conjunto de dados é muito grande para caber na memória principal. 4. Algoritmos de Ordenação Os algoritmos de ordenação organizam dados em uma sequência predefinida. Vamos discutir três tipos principais:

c Copiar código void selecao (int *v) { int i, j, aux, minimo, pos_minimo; for (i = 0 ; i < TAMANHO-1; i++) { minimo = v[i]; pos_minimo = i; for (j = i+ 1 ; j < TAMANHO; j++) { if (minimo > v[j]) { minimo = v[j]; pos_minimo = j; } } if (pos_minimo != i) { aux = v[pos_minimo]; v[pos_minimo] = v[i]; v[i] = aux; } } }

Resumo das Complexidades:

Bubble Sort: O(n²) para todos os casos.  Insertion Sort: O(n) no melhor caso (já ordenado) e O(n²) no pior caso.  Selection Sort: O(n²) para todos os casos, independentemente da ordem inicial. Esses algoritmos são fundamentais, especialmente para conjuntos de dados menores ou como base para entender métodos mais avançados.

Merge Sort (Ordenação por Intercalação)

Ideia principal: Divide o problema em partes menores, resolve essas partes, e depois combina as soluções. Isso é chamado de "dividir para conquistar".  Passos básicos:

  1. Dividir: Quebra o array original ao meio repetidamente até ter vários arrays com apenas um elemento.
  2. Conquistar: Ordena cada um desses pequenos arrays (o que é fácil, pois eles têm só um elemento).
  3. Combinar: Junta esses pequenos arrays ordenados em um maior, sempre mantendo a ordem.  Vantagens:
  4. Funciona bem com grandes quantidades de dados.
  5. Pode ser eficiente em programação paralela.  Complexidade: O Merge Sort sempre leva O(n log n) para ordenar n elementos.

Exemplo:

Se você tem o array [7, 2, 9, 4, 3, 8, 6, 1], o Merge Sort o dividiria assim:

  1. [7, 2, 9, 4] e [3, 8, 6, 1]
  1. [7, 2] e [9, 4] e assim por diante, até ter vários arrays de um elemento.
  2. Então, começa a combinar [7] e [2] em [2, 7], depois [9, 4] em [4, 9] e assim por diante, até ter o array ordenado.

Quick Sort

Ideia principal: Também usa a técnica de "dividir para conquistar", mas de uma maneira diferente.  Passos básicos:

  1. Escolher um Pivô: Seleciona um elemento do array para ser o pivô (geralmente o primeiro ou último).
  2. Particionar: Reorganiza o array para que todos os elementos menores que o pivô fiquem à esquerda e os maiores à direita.
  3. Recursão: Aplica o mesmo processo para as partes esquerda e direita do pivô até que o array esteja ordenado.  Vantagens:
  4. Muito rápido para a maioria dos casos.
  5. É o mais utilizado na prática devido à sua eficiência.  Complexidade: No pior caso (quando o pivô escolhido é sempre o menor ou maior valor), leva O(n²) , mas em média é O(n log n).

Exemplo:

Se você tem o array [23, 11, 66, 75, 6, 53, 21, 56], o Quick Sort escolheria um pivô, digamos 23 , e reorganizaria o array em [11, 6, 21, 23, 75, 66, 53, 56], depois repetiria o processo nas partes à esquerda e à direita de 23.

Resumo:

Merge Sort : Divide tudo em pequenas partes, ordena e depois junta. Sempre eficiente.  Quick Sort : Escolhe um pivô, organiza e repete o processo. Geralmente rápido, mas pode ser lento em alguns casos. Esses dois algoritmos são fundamentais para a ordenação de dados em ciência da computação. Shell Sort Simplificado: O que é: O Shell Sort é um algoritmo de ordenação criado por Donald Shell. Ele é uma versão melhorada da ordenação por inserção, baseada no conceito de "diminuição dos incrementos". Como funciona:

  1. Passos Iniciais: Ele começa ordenando elementos que estão a uma certa distância uns dos outros, por exemplo, elementos que estão três posições afastados.

o Pré-ordem : Visita-se o nó raiz, depois a subárvore esquerda, e por último a subárvore direita. o Em ordem simétrica : Visita-se primeiro a subárvore esquerda, depois o nó raiz, e por último a subárvore direita. o Pós-ordem : Visita-se primeiro a subárvore esquerda, depois a direita, e por último o nó raiz.

3. Exemplo de Pré-ordem:

 Dada uma árvore onde o nó raiz tem valor 10 e tem dois filhos (8 à esquerda e 4 à direita), o percurso em pré-ordem seria: 10, 8, 2, 6, 4.

4. Complexidade dos Algoritmos:

Complexidade : O tempo de execução para percorrer n nós em uma árvore é O(n) , pois cada nó é acessado três vezes (para cada subárvore e o nó em si).

5. Implementação em Pseudocódigo:

 Os percursos em pré-ordem, em ordem simétrica e pós-ordem podem ser implementados de forma recursiva, visitando os nós de acordo com as regras de cada tipo de percurso. Este resumo simplifica os conceitos principais e a lógica dos percursos em árvores binárias, sem entrar em detalhes técnicos complexos.

1. Percurso em Árvores e Complexidade

Pré-ordem, Ordem Simétrica, e Pós-ordem : São três tipos de percursos que você pode realizar em uma árvore binária.  Complexidade : O tempo que o algoritmo leva para percorrer a árvore é proporcional ao número de nós, ou seja, O(n), onde n é o número de nós.

2. Algoritmo Não Recursivo

Objetivo : Percorrer uma árvore binária sem usar recursão (ou seja, sem chamar a função dentro dela mesma).  Pilha : Usamos uma pilha para manter o controle de quais nós visitamos e em qual "momento" (1, 2 ou 3) estamos durante a visita. o Momento 1 : Visitamos o nó pela primeira vez. o Momento 2 : Terminamos de explorar o lado esquerdo do nó. o Momento 3 : Terminamos de explorar o lado direito do nó.

3. Funcionamento do Algoritmo

Passo a Passo : Inserimos o nó raiz na pilha e seguimos removendo e inserindo nós na pilha conforme o momento. Dependendo do percurso (pré-ordem, ordem simétrica ou pós-ordem), a visita ao nó acontece em um momento específico.  Pré-ordem : Visitamos o nó no momento 1.

Ordem Simétrica : Visitamos o nó no momento 2.  Pós-ordem : Visitamos o nó no momento 3.

4. Exemplo de Execução

 A pilha vai sendo manipulada para seguir a ordem correta de visitação dos nós, conforme o percurso desejado.  A impressão da árvore em pré-ordem vai seguir a sequência dos nós visitados.

5. Complexidade do Algoritmo

Inserções na Pilha : Cada nó é inserido na pilha até três vezes, mas isso não altera a complexidade total, que permanece sendo O(n).

6. Dicas para Outros Percursos

 Para converter o algoritmo para ordem simétrica ou pós-ordem , basta mudar o momento em que a função de visita é chamada.

7. Motivação para Árvores Binárias de Busca

Eficiência na Busca : Para tornar a busca mais eficiente e alcançar uma complexidade de O(log n), usamos árvores binárias de busca, onde os elementos estão organizados de maneira que facilita a busca rápida.

Árvore Binária de Busca (BST)

Definição: Uma árvore binária de busca (BST) é uma estrutura de dados onde cada nó tem no máximo dois filhos: um à esquerda e outro à direita.  Regra Principal: o Para cada nó, todos os valores na subárvore à esquerda são menores que o valor do nó. o Todos os valores na subárvore à direita são maiores que o valor do nó.

Algoritmo de Busca

Objetivo: Encontrar um valor específico na árvore.  Funcionamento:

  1. Comece na raiz da árvore.
  2. Compare o valor procurado com o valor da raiz:  Se for igual, a busca termina.  Se for menor, continue a busca na subárvore à esquerda.  Se for maior, continue a busca na subárvore à direita.
  3. Repita até encontrar o valor ou chegar a um ponto onde não há mais subárvores para explorar.
  4. Complexidade: No pior caso (quando a árvore é uma linha reta, tipo zig- zag), a busca pode ter que visitar todos os nós, resultando em uma complexidade de O(n).

automaticamente ajustada por rotações (simples ou duplas) para restaurar o balanceamento. Rotações: São as operações que corrigem o balanceamento. Existem rotações simples (para a esquerda ou direita) e rotações duplas (esquerda-direita ou direita-esquerda), dependendo de como a árvore se desbalanceia após uma inserção ou remoção. Altura das Árvores AVL: A altura de uma árvore AVL é proporcional ao logaritmo do número de nós, garantindo que o tempo para buscar, inserir ou remover um nó permaneça eficiente. Mesmo nos piores casos, a altura das árvores AVL está dentro de limites que asseguram o balanceamento. Operações:Busca: Funciona como em qualquer árvore binária de busca, com complexidade O(log n) devido ao balanceamento.  Inserção: Após inserir um novo nó, a árvore pode precisar de rotações para restaurar o balanceamento.  Remoção: Similar à inserção, mas o foco é garantir que a remoção não desbalanceie a árvore, usando rotações se necessário. Árvores de Fibonacci: Representam os piores cenários em termos de altura máxima, mas mesmo assim, a altura dessas árvores ainda é proporcional a O(log n), o que mantém a eficiência. Esses conceitos garantem que as árvores AVL sejam balanceadas e eficientes, melhorando significativamente o desempenho em relação às árvores binárias desbalanceadas.

Conceitos Básicos de Grafos

Teoria dos Grafos :  Originada no século XVII, a teoria dos grafos começou com o problema das "Pontes de Königsberg" de Euler, que questionava se era possível atravessar todas as pontes da cidade sem repetir nenhuma. Essa situação introduz o conceito de Caminho Euleriano. Definição de Grafo :  Um grafo é composto por vértices (nós) e arestas (conexões entre os vértices). As arestas podem ser direcionadas (chamado de dígrafo ) e podem ter pesos ou direções associadas.  Exemplo: Imagine um mapa onde os vértices são cidades e as arestas são as estradas que as conectam. Elementos e Propriedades dos Grafos :

  1. Vértices Adjacentes : Vértices conectados por uma aresta.
  2. Laço : Uma aresta que conecta um vértice a ele mesmo.
  1. Arestas Paralelas : Duas arestas que conectam os mesmos dois vértices.
  2. Grafo Simples : Um grafo sem laços ou arestas paralelas.
  3. Grau do Vértice : Número de arestas que incidem em um vértice.
  4. Grafo Conexo : Um grafo em que qualquer vértice pode ser alcançado a partir de qualquer outro vértice. Operações com Grafos :  Remoção de Vértices/Arestas : A retirada de um vértice remove todas as arestas ligadas a ele; a retirada de uma aresta simplifica o grafo. Tipos Especiais de Grafos :
  5. Grafo Completo : Um grafo onde todos os vértices estão conectados entre si.
  6. Grafo Complementar : Representa as conexões que não existem em um grafo.
  7. Grafo Nulo : Um grafo sem arestas.
  8. Grafo Regular : Todos os vértices têm o mesmo número de arestas conectadas.
  9. Ciclo : Um grafo onde cada vértice é conectado a dois outros, formando um círculo.
  10. Grafos Direcionados : Grafos em que as arestas têm uma direção específica. Esses conceitos fornecem a base para resolver problemas complexos usando grafos, como encontrar o menor caminho ou otimizar rotas, com diversas aplicações práticas na computação. Objetivo dos Algoritmos de Busca: Os algoritmos de busca em grafos são usados para explorar os nós e arestas de um grafo, ou seja, para descobrir caminhos ou conexões entre diferentes pontos em uma rede. Tipos de Busca em Grafos
  11. Busca em Profundidade (Depth-First Search - DFS): o Como Funciona:  Começa em um nó inicial e explora o mais fundo possível ao seguir um caminho. Se chegar a um ponto sem saída, volta (faz "backtracking") para o nó anterior para tentar novos caminhos.  Estratégia: Visitar o nó mais profundo possível em cada ramo antes de retroceder. o Aplicação: Boa para descobrir caminhos completos ou explorar todas as conexões de um grafo. o Complexidade: O tempo de execução depende do número de arestas e nós no grafo, representado por O(a+n)O(a+n)O(a+n), onde aaa é o número de arestas e nnn o número de vértices. o Exemplo:  Suponha que você comece a busca a partir do nó "A". O algoritmo vai explorar o caminho mais profundo, verificando nós conectados a "A", por exemplo, indo até "B", depois "D", até não poder mais continuar, e então retorna para tentar outros caminhos.