# Introdução às Estruturas de Dados ## Sumário - [1. Introdução às Estruturas de Dados](#introduction) - [2. O que é uma Estrutura de Dados, Porque é preciso de Estruturas de Dados, Tipos de Estruturas de Dados, Importância de Estruturas de Dados](#types-of-ds) - [3. Complexidade de Tempo em Estruturas de Dados e Algoritmos](#time-complexity) - [4. Ponteiros em DS e Algoritmos em C](#pointers) - [5. Arrays em DS e Algoritmos](#arrays) - [6. Matrizes em DS e Algoritmos](#2d-array) - [7. Listas Encadeadas em DS e Algoritmos](#linked-list) - [8. Listas Encadeadas Sencilhas em DS e Algoritmos](#singly-linked-list) - [9. Listas Encadeadas Duplamente em DS e Algoritmos](#doubly-linked-list) - [10. Listas Encadeadas Circulares em DS e Algoritmos](#circular-linked-list) - [11. Arrays vs Listas Encadeadas em DS e Algoritmos](#array-vs-linked-list) - [12. Pilhas em DS e Algoritmos](#stacks) - [13. Filas e Filas Prioritárias em Estruturas de Dados e Algoritmos](#queue-and-priority-queue) - [14. Dequeue em Estruturas de Dados e Algoritmos](#dequeue) - [15. Estruturas de Dados em Árvore](#tree) - [16. Percorrimento de Árvore em Estruturas de Dados e Algoritmos](#tree-traversal) - [17. Árvore Binária e Árvore Binária de Pesquisa em Estruturas de Dados e Algoritmos](#binary-tree) - [18. Árvore B+ em Estruturas de Dados e Algoritmos](#b-plus-tree) - [19. Grafos em Estruturas de Dados e Algoritmos](#graphs) - [20. Árvores Gênericas em Estruturas de Dados e Algoritmos](#spanning-tree) ## Transcrições ### Aula - 1 | Introdução às Estruturas de Dados | Simplilearn [Aula - 1 | Introdução às Estruturas de Dados | Simplilearn](https://www.youtube.com/watch?v=izDZhbiOHOc) Bem-vindo ao curso de Estruturas de Dados pela Simplilearn. Como vivimos na Era da Informação, é uma boa ideia familiarizar-se com as melhores maneiras de manter e organizar informações. Mais importante do que isso, para se tornar um engenheiro de software ou um profissional relacionado à ciência de dados, é necessário entender conceitos como as estruturas de dados e os algoritmos. Uma estrutura de dados é como uma maneira especial de organizar e armazenar informação em um computador. Pense nela como um plano ou uma estrutura que mostra como a informação pode ser mantida, usada e alterada eficientemente. Em Python, estas estruturas são realmente importantes para tarefas de computador, ajudando os programaadores a resolver problemas complicados e fazer seus programas mais rápidos. Pense nela como uma caixa de ferramentas com diferentes seções para ferramentas específicas. De forma semelhante, as estruturas de dados criam seções para peças de informação. Cada tipo de estrutura de dados tem sua função. Por exemplo, os arrays fornecem uma linha única de elementos com posições numeradas. Por meio de uma lista encadeada, crie uma cadeia de partes conectadas em que cada parte aponta para o próximo, e os árvores, e outras estruturas de dados de árvore de estruturas hierárquicas úteis para organizar arquivos, são mais estruturas complexas como os grafos mostram relacionamentos complexos entre peças de informação com uma curta introdução às estruturas de dados, começamos com os fundamentos das estruturas de dados. Portanto, sem nenhum adiamento, vamos começar. --- ### Aula - 2 | O Que é uma DS, Porque é Preciso de DS, Tipos de DS, Importância de DS | Simplilearn [Aula - 2 | O Que é uma DS, Porque é Preciso de DS, Tipos de DS, Importância de DS | Simplilearn](https://www.youtube.com/watch?v=aq7t8ztw2T4) O que são estruturas de dados? A informação é dados que foram traduzidos em uma forma eficiente de mover ou processar na computação, como se relaciona com pelapestres informática e egressos. A informação é informação convertida em formato binário digital. O termo dados raws refere-se aos dados na forma mais básica binária em arquivo. Depois de definirmos a informação, vamos olhar no que estrutura de dados envolve: a estrutura de dado organização, gestão e armazenamento em ciência da computação que permitir um acesso e modificação eficiente. Uma estrutura de dado é uma estrutura de algebrados sobre a dado que contém uma coleção de valores de dado, suas relações, e operáculos que podem ser aplicados à dado. Agora vamos entender de forma exata por que precisamos de estruturas de dados, agora que já definimos o que é estrutura de dados. Portanto, vamos olhar em alguns características das estruturas de dados. - Cada estrutura de dados permite dados serem armazenados de maneiras diferentes. - A estrutura de dado permite a pesquisa de dados mais eficiente e extração. - Estruturas específicas de dados são escolhidas para solucionar problemas específicos. - Ele permite a gestão de grandes quantidades de dados tais como bancos de dados e indexadores tales como hash tables. Agora vamos olhar alguns casos reais de estruturas de dados: - Primeiramente, temos o dicionário. Considerando que estamos procurando a palavra "simplilearn" no dicionário, e sabemos que começa com a letra S. Então podemos procurar esta palavra começando com a letra S, e isso é um exemplo da estrutura de dados de matriz. Assim, este dicionário também pode funcionar como uma estrutura de dados de matriz. - Após o dicionário, temos um exemplo de tocador de música. Se você tem uma playlist com três músicas, a segunda música será tocada após a primeira, e a terceira será tocada após a segunda, portanto elas serão tocadas sem interrupção porque todas estas três músicas estão ligadas em uma estrutura de lista de envolvente. Assim, adotando lista encadeada, o próximo exemplo que temos é a pilha de livros. A pilha de livros analógico é um exemplo perfeito de funcionamento real da estrutura de pilha de dados. - Em seguida, considere uma fila de pessoas em uma janela de carnaval. A pessoa que chega primeiro recebe um leque. Neste exemplo de pilots, a janela de carnaval é um exemplo de real funcionamento da estrutura de fila de dados. - Por último, Google Maps. Google Maps é a estrutura de gráfico em que todas as cidades e estados estão conectadas. Se quisermos ir de um lugar para outro, pode haver muitas formas de fazer isso; podemos utilizar alguns algoritmos para encontrar o caminho mais curto. Assim, Google Maps é um exemplo perfeito para a estrutura de gráfico de funcionamento real. A seguir, vamos olhar em alguns tipos diferentes de estruturas de dados em esta aula. Desse tipo, temos a estrutura linear. Os elementos da estrutura linear são dispostos subsequentemente um após o outro, eles são simplesmente fáceis de implementar pois os elementos estão dispostos em ordem específica. Seguindo pela primeira categoria, temos a segunda categoria, que é a não linear estrutura que iremos discutir a seguir. Na estrutura linear, temos quatro diferentes tipos: 1. O primeiro deles é a estrutura de matriz: elementos na estrutura de matriz são dispostos na ordem continua em matrizes e, os elementos da estrutura de matriz são do tipo determinado por um software ou programa e também determina o tipo de elementos que podem ser armazenados na matriz. 2. Seguindo a matriz, temos a lista encadeada: elementos em uma estrutura de lista encadeada são conectados por uma série de nós. Além disso, cada nó contém os itens de dado e também a referência para o próximo nó. 3. O terceiro é a estrutura de pilha: os elementos em uma pilha de estrutura de dado são armazenados de acordo com a regra LIFO (O Último a Ser Inserido Primeiro a Ser saído). Em outras palavras, o último elemento armazenado na pilha é removido primeiro. Nas operações de pilha, apenas podem ser realizadas em um único fim, não podemos fazer alterações ruin aos elementos apontados, apenas podemos adicionar ou remover os elementos do extremo. 4. Por último, a estrutura de fila: diferentemente das pilhas, a estrutura de fila opera conforme a regra FIFO (Primeiro a ser inserido Primeiro a ser encontrado). O regra FIFO diz que o primeiro elemento armazenado na fila é removido primeiro. Na fila, a inserção e a extração são realizadas a partir de um extremo oposto. - Continuando, vamos discutir sobre a segunda categoria de estruturas de dados, que são as estruturas não lineares. As estruturas não lineares não têm elementos em ordem. Em vez disso, eles são dispostos em uma ordem de hierarquia com um elemento conectado a um ou mais outros. Portanto, o primeiro deles nas estruturas não lineares é a estrutura de árvore. A estrutura de árvore é construída por meio de nós e arestas. Cada árvore é construída em elementos chamados vértices ou nós. Cada vértice ou nó é conectado aos demais nós ou vértices por aristas. O próximo nesta lista é a estrutura de gráfico. Uma estrutura de gráfico é semelhante à estrutura de árvore. Também tem Vértices e Aristas. Cada vértice na estrutura de gráfico é conectado por meio de aristas, a única diferença entre nosso árvore e grafo é que no árvore, podemos ter número determinado de vértices em comparação com o nosso grafo na qual não existem limites numéricos para vértices. A seguir, vamos olhar na importância da estrutura de dados. A importância da estrutura de dados: 1. Estruturas de dados são amplamente utilizadas em quase todos os aspectos da computação informática, tanto para computações simples como computações complexas. 2. Estruturas de dados são utilizadas em todos os domínios da computação informática, incluindo Inteligência Artificial, Gráficos, Big Data, sistemas operacionais e muito mais. 3. Estruturas de dados são uma componente essencial de muitos algoritmos computacionais, pois permitir um manuseio eficiente de dados para os programadores. 4. Uma estrutura de dado selecionada apropriadamente pode melhorar a eficiência de um programa de computador ou algoritmo. --- ### Aula - 3 | Complexidade de Tempo em Estruturas de Dados e Algoritmos | Simplilearn [Aula - 3 | Complexidade de Tempo em Estruturas de Dados e Algoritmos | Simplilearn](https://www.youtube.com/watch?v=0fmODm2ICEw) Introdução à complexidade de tempo. A complexidade de tempo de um algoritmo é a quantidade de tempo que ele demora a execução como uma função do comprimento do input. O comprimento do input determina quantos operações o algoritmo irá fazer. Fornecerá informações sobre a variação na execução de tempo aumentando ou diminuindo enquanto o número de operações em um algoritmo aumenta ou diminui. Primeiro, vamos olhar em algumas complexidades de tempo, começando com a complexidade constante de tempo, então vamos discutir complexidade linear, logarítmica e quadrática de tempo, agora vamos olhar em detalhes. 1. Complexidade de Tempo Constante: Quando um algoritmo não depende do tamanho do input, ele é definido para ter complexidade de tempo de ordem θ(1). A execução sempre demora o mesmo tempo, independentemente do input. Como você pode ver neste código, cada linha de código tem um tempo de execução de um e cada linha de código é independente de qualquer tamanho de input, assim o tempo de execução sempre é constante. ``` def constante_tempo(): print("Olá, mundo! ") print(2 * 2) print(3 + 3) ``` 2. Complexidade de Tempo Linear: Quando a execução de tempo de um algoritmo aumenta linearmente com o tamanho do input, ele é definido para ter complexidade de tempo linear. Quando um algoritmo percorre todos os valores em uma carga de dados de entrada, ele é definido para ter complexidade de tempo θ(n). Como no código a seguir, o loop depende de n tamanho, assim a complexidade de tempo é θ(n). ``` def tempo_linear(n): for i em range(n): print(i) ``` 3. Complexidade de Tempo Logarítmico: Quando um algoritmo diminui o tamanho de dados no passo redução, então é definido para ter complexidade de tempo logarítmica. Árvores ou algoritmos de pesquisa binária são alguns algoritmos com complexidade de tempo logarítmica. No código a seguir, por meio de busca de meio na e tubo, a execução tempo se tornará aproximadamente logarítmica no tempo. ``` def pesquisa_binaria(arr, n, x): inicio = 0 fim = n - 1 enquanto início não é maior que fim: meio = inicio + (fim - inicio) / 2 se arr[meio] é igual a x: retornar meio ou seja x > arr[me # Aula - 04 | Ponteiros em C, DS e Algoritmos ## Conteúdo Resumido - Introdução aos Ponteiros - Definição e declaração de ponteiros - Acessando valores de variáveis utilizando ponteiros - Ponteiros Nulos - Tipos de Ponteiros - Ponteiros de Tipo Void - Ponteiros V (Ponteiros Não Inicializados) - Ponteiros Pegando - Casos de Uso de Ponteiros - Árithmética de Ponteiros - Incremento e Decremento - Adição e Subtração de um inteiro a um ponteiro - Ponteiro para Ponteiro - Arrays de Ponteiros - Chamada por Valor e por Referência ## Introdução aos Ponteiros Um ponteiro é uma variável que armazena o endereço de outra variável. É declarado usando o símbolo de estrela (*) e você pode obter o endereço de uma variável usando o símbolo `&` de maneira similar à sua declaração de ponteiro. Vamos escrever um código simples: ```c int A = 5; int *ptr; ptr = &A; // atribuindo o endereço da variável para o ponteiro printf("Valor da variável utilizando o nome da variável: %d\n", A); printf("Valor da variável utilizando o ponteiro: %d\n", *ptr); ``` Executando o código acima, você pode acessar o valor da variável utilizando tanto seu nome de variável quanto o ponteiro. ## Ponteiros Nulos Um ponteiro nulo é um tipo de ponteiro que aponta para memória nula ou para nada. Tenta um programa simples para melhor understoodir: ```c int *ptr = NULL; printf("%d", ptr); // não imprimirá nada, pois este ponteiro aponta para uma memória nula ou para nada ``` ## Ponteiros de Tipo Void Um ponteiro criado com o tipo de dado void é chamado de ponteiro de tipo void. Para imprimir o valor da variável, é necessário casting este ponteiro para evitar erros: ```c int a = 5; void *ptr = &a; // Imprimir o valor de a usando o ponteiro printf("%d\n", *(int*) ptr); ``` ## Ponteiros V (Ponteiros Não Inicializados) Os ponteiros V podem ser bastante perigosos, pois podem causar falhas no programa ou um satsegmentation fault. Eles ocorrem quando declaramos um ponteiro, mas não o inicializamos atribuindo memória a ele. Retornemos à linha de comando e tentemos com um código simples: ```c int *ptr; // este ponteiro é um ponteiro não inicializado printf("%d", ptr); // erro de segmentação ou sem saída, pois não há valor atribuído a ele ``` ## Ponteiros Pegando Um ponteiro pegando aponta para um espaço de memória livre ou memória não existente após você o desalocar utilizando uma função `free()`. Tente melhor entender usando um código simples: ```c int *ptr; int arr[10]; ptr = &arr[0]; // armazenando o endereço do primeiro elemento no ponteiro free(ptr); // desalocando a memória printf("%d", *ptr); // este ponteiro aponta para nada, pois a memória foi desalocada ``` ## Árithmética de Ponteiros Pode-se executar operações aritméticas em ponteiros, como incrementar, decrementar, adicionar e subtrair. No entanto, as operações só fazem sentido quando aplicadas em estruturas de dados sequenciais como arrays ou strings: 1. Incremento: Se aplicado em um ponteiro, ele atribuirá o endereço à sequência sucessora subsequente do ponteiro. 2. Decremento: Se aplicado em um ponteiro, ele atribuirá o endereço à sequência anterior do ponteiro. 3. Adição: Adicionar um inteiro a um ponteiro irá saltá-lo a cabo, depois saltando o número de elementos informado. 4. Subtração: Subtrair um inteiro de um ponteiro irá saltar para trás, depois saltando o número de elementos informado a partir da sua localização atual. ## Ponteiro para Ponteiro Agora implementamos ponteiros para ponteiros em nosso código: ```c int a = 5, **pptr; // Criando dois níveis de ponteiros pptr = &a; // Ponteiro de primeiro nível recebe o endereço da variável `a` int *p = pptr; // Usando o ponteiro do primeiro nível, atribui o endereço da variável `a` ao segundo nível de ponteiro `p` printf("%d", *p); // imprime o valor de `a` printf("%d", **pptr); // também imprime 5 ``` ### Aula - 5 | Arrays em Estruturas de Dados e Algoritmos | [URL do Simplilearn](https://www.youtube.com/watch?v=9nL3mhOtPKU) (Inglês) #### A Importância dos Arrays Para ilustrar a necessidade de array, vamos considerar um conceito simples. Imagine que possuímos dados sobre alunos e queremos armazenar suas notas individuais. Se criassemos uma variável para cada aluno (aluno 1, aluno 2, aluno 3, etc. ), ficaria muito enlaçada, especialmente se tivermos centenas ou milhares de alunos. Para tornar tudo mais gerenciável, temos uma estrutura de dados — arrays — que pode armazenar vários elementos de dados em uma única variável e aplicar operações coletivamente. #### O Que são Arrays? Um array é uma estrutura de dados linear que armazena its elementos sequencialmente, um depois do outro. Quando declaramos um array, os elementos são armazenados em sequence. #### Representação da Memória de Arrays Quando declaramos um array, o compilador atribui a ele um bloco de memória com endereços. Esses endereços são utilizados como valores de índice para que o usuário possa acessar elementos de array de uma forma mais conveniente. Enquanto criar um array, é importante observar que todos os elementos devem ser do mesmo tipo de dados, por exemplo, caractere, inteiro, flutuante, etc. #### Acessando Elementos através do Índice Para acessar elementos de um array, usamos o índice. Em termos práticos, se queremos acessar o elemento 'R', não sabemos o endereço de memória exacto. Em vez disso, utilizamos o índice para informar ao sistema que queremos o elemento no local especificado do array. #### Lower Bounds e Upper Bounds A local inicial de um array é o Lower Bound, sendo o último ou o valor máximo o Upper Bound. #### Tipos de Arrays 1. Arrays unidimensionais: Exige somente um índice para especificar o número de elementos e, que são armazenados sequencialmente. 2. Arrays multidimensionais: - Arrays bidimensionais: Organizados em uma forma de matriz com linhas e colunas, exigindo dois índices para o número de linhas e colunas. - Arrays tridimensionais: É uma coleção de 2D arrays constituindo três índices: Tamanho do bloco, tamanho de linha e tamanho de coluna. #### Declaração de Sintaxe de Arrays - Especifique o tipo de dados do array, como inteiro, flutuante ou caractere. - Selecione um nome para o array. - Disponha do tamanho do array. Aqui está um exemplo de declaração de um array unidimensional em C: ```c int notas[10]; ``` E aqui está um exemplo de declaração de um array bidimensional em C: ```c int alunos[3][3]; ``` Na declaração, `alunos` é o nome do array, e ele tem 3 linhas e 3 colunas, fazendo um 3x3 matriz. Os elementos em cada linha podem ser acessados utilizando o índice da linha e o índice da coluna. --- Para resumir, aprendemos sobre os arrays e a sua importância em manipulações de dados. Ajudam-nos a gerenciar e aplicar operações em elementos de dados em grande escala. A compreensão de arrays é essencial para um trabalho eficaz com dados de estruturas e algoritmos. Uma vantagem de arrays é que oferece um método simples e eficiente para armazenar e manipular uma grande quantidade de dados em uma única variável. No entanto, arrays têm limitações de memória e podem ser complexos de gerenciar se não forem manejados com cuidado, o que pode liderar a problemas potenciais como erros de segmentação e varieduras. Esses desvantagens podem frequentemente ser superadas utilizando arrays dinâmicos ou listas ligadas. Em resumo, um entendimento claro de arrays é essencial para uma sólida fundação em data structures and algorithms. # Aula 6 | Arrays Bidimensionais na ciência de dados e algoritmo | Link de vídeo da Simplilearn: <https://www.youtube.com/watch?v=WguGNN46Uu8> Idioma: pt-PT ## O que são arrays Vamos dar uma rápida definição de arrays. O array é um tipo de dados que armazena elementos do mesmo tipo em ordem sequencial. Arrays bidimensionais são considerados um array de arrays. Arrays bidimensionais representam as informações no formato linhas e colunas, que é muito semelhante ao formato tabular de dados. ## Por que usar arrays bidimensionais A principal vantagem de usar arrays bidimensionais é que os elementos podem ser representados no formato matriz, que é linhas e colunas. Usando arrays bidimensionais, podemos inicializar, acessar e imprimir múltiplos elementos em uma matriz em um único segmento de código. Você pode ver um exemplo a seguir: ```markdown # Exemplo de arrays bidimensionais # Definindo o tipo de dados, o nome da matriz e as dimensões integer tipo dados, nome da matriz notas, linhas 4, colunas 3 # Inicializando os elementos da matriz notas[0][0] = 85 notas[0][1] = 90 notas[0][2] = 95 notas[1][0] = 88 notas[1][1] = 91 notas[1][2] = 80 notas[2][0] = 89 notas[2][1] = 94 notas[2][2] = 100 notas[3][0] = 92 notas[3][1] = 93 notas[3][2] = 87 ``` ## Sintaxe de arrays bidimensionais Antes de tudo, precisamos definir o tipo de dados: o tipo de dados pode ser `integer`, `character`, `float`, `string`, etc. Depois, precisamos dar um nome para a matriz bidimensional. Depois vem dois subscripts, que representam as dimensões: as linhas e as colunas. O primeiro subscript define o número de linhas e o segundo subscript define o número de colunas. Você não pode ter `null` como número de colunas. É necessário terminar a sintaxe com um ponto e vírgula. ## Exemplos de arrays bidimensionais ```markdown # Exemplo 1 array nome alunos, linhas 5, colunas 3 # Exemplo 2 array nome notas, linhas 3, colunas 3 notas[0][0] = 78 notas[0][1] = 90 notas[0][2] = 85 notas[1][0] = 80 notas[1][1] = 87 notas[1][2] = 93 notas[2][0] = 75 notas[2][1] = 88 notas[2][2] = 83 ``` ## Próximos passos Agora é sua vez de praticar mais sobre arrays bidimensionais. Você pode acessar o vídeo completo abaixo: <https://www.youtube.com/watch?v=WguGNN46Uu8> Experimente e pratique mais com arrays bidimensionais. Ainda precisando de animação para suas leções, procura uma aplicação divertida para aprender. Boas estudos! 😊 # Arrays multidimensionais em estruturas de dados e algoritmos Temos um exemplo adicional, do tipo dado em caracteres, e o nome da matriz é `names`. Temos sete linhas e 15 colunas. Em seguida, vamos ver como visualizar arrays multidimensionais. Leremos mais cedo que arrays multidimensionais são uma coleção de linhas e colunas. Vejam a seguir um exemplo de um array multidimensional. Consideraremos uma matriz com seis linhas e quatro colunas. Como você pode ver na minha tela, temos um array do tipo inteiro, e o nome da matriz é `array`. Temos seis linhas e quatro colunas. O produto de linhas e colunas nos dará o número total de elementos presentes num array multidimensional. Neste exemplo, temos seis linhas e quatro colunas, e o produto é 24, portanto, deveremos ter 24 elementos nesta matriz. A seguir, vamos ver como inicializar arrays multidimensionais. Há duas formas de inicializar um array multidimensional: 1. Declarando elementos em um conjunto de chaves `{}`. Esta matriz inclui três linhas e três colunas. Nota-se que o índice sempre começa em zero. Aqui temos três linhas, o que significa que a linha começa em 0 e termina em 2. O mesmo se aplica à coluna. No entanto, este método pode ser um pouco confuso. 2. Um método melhor com mais clareza. Poderemos visualizar todos os elementos com melhor clareza em comparação com o método anterior. Neste método, podemos declarar o número de linhas e colunas separadamente, e é mais fácil de entender. Até agora, aprendemos como visualizar e inicializar um array multidimensional. Utilizamos valores de índice, isto é, o índice da linha e o índice da coluna. Para acessar um elemento num array multidimensional, é preciso passar o nome da matriz seguido do valor de índice da linha e do valor de índice da coluna. Por exemplo, vamos supor que queremos acessar o elemento 9. Para isso, teremos que passar o nome da matriz seguido do valor de índice da linha e do valor de índice da coluna, como mostrado a seguir: ``` array[0][0] ``` Nota-se que o índice começa sempre com 0. Agora vamos começar a imprimir arrays multidimensionais. Para imprimir um array multidimensional, vamos usar dois laços `for` aninhados. Voltaremos aos tipos de código, e trabalharemos em um código. Portanto, em minha tela, você pode ver um exemplo para um array multidimensional. Lá, os dados estão agrupados na matriz `first`, com três linhas e três colunas. No primeiro grupo de elementos, temos 9, 6 e 1, e no segundo grupo de elementos temos 144, 70 e 50, e no terceiro grupo temos 10, 12 e 78. Seguindo a subsequente, teremos dois contadores, os quais são `I` e `J`. Usamos o contador `I` no laço `for` externo, e o contador `J` no laço `for` interno. Dentro do laço `for` interno, temos uma instrução impressão que é utilizada para imprimir os elementos na matriz. Vamos tentar executar este código e ver o resultado. Assim, podemos ver o código foi executado com sucesso, e temos aqui os elementos que se referem a `9 6 144 70 50 10 12`, e `78`. Vamos entender a lógica: 1. Nós temos declarado o contador com o valor inicial como 0. A condição é que `I` deve ser menor que 3, o que é verdadeiro, então o controle entra no segundo laço interno. 2. No laço interno, temos `J` igual a 0, e `J` deve ser menor que 3, o que é verdadeiro, portanto, a condição ou a declaração é válida e vai imprimir o primeiro elemento: 9. 3. Depois de incrementar o valor de `J` de novo, `J` é agora igual a 1, e `J` é menor que 3, então novamente entraremos na condição, e vai imprimir o segundo elemento: 6. 4. Quando incrementamos o valor de `J` de 2 para 3, a condição falha porque 3 não pode ser menor que 3, então o controle entra no laço externo. 5. Neste laço externo, o valor de `I` é incrementado para 1. Portanto, 1 é menor que 3, então o controle entra novamente no laço interno. 6. Agora vamos verificar `J` igual a 0, o que é verdadeiro, e vamos imprimir o segundo grupo de elementos: 144, 70 e 50, no modo in que imprimimos o primeiro grupo. 7. Este processo repetirá até que todos os elementos na matriz estejam impressos. --- ### Aula - 7 | Lista Encadeada em DS e Algoritmos | URL do Simplilearn: [https://www.youtube.com/watch?v=hXG0syXewZI](https://www.youtube.com/watch?v=hXG0syXewZI) Eu traduzirei o texto para o inglês e formatarei segundo as regras fornecidas. --- # Lista Encadeada em Estruturas de Dados e Algoritmos Uma lista encadeada é uma estrutura de dados linear composta de nós. Estes nós consistem em dois elementos: dados, e uma referência a outro ponteiro. Tipicamente, a lista encadeada começa com um nó cabeça, que termina com o último nó, chamado de cauda. ## Por qué precisamos de Listas Encadeadas? - Uma lista encadeada é uma estrutura de dados linear como arrays, mas apenas os arrays podem ter um tamanho fixo. No entanto, uma lista encadeada pode expandir sua capacidade dinamicamente. - Operações como inserção e remoção são muito mais fáceis numa lista encadeada que num array. Agora vamos implementar a Lista Encadeada no editor de código. Vamos começar por criar um nó. Para criar um nó, precisamos de uma classe. Vamos declarar uma classe chamada `Node`, e vamos declarar seus membros como `public`. O primeiro membro é `int data`, e o segundo membro é um `node` ponteiro. Agora iríamos para o bloco principal, e iniciamos o nosso primeiro nó. Para inicializar um nó, usaremos `Node head` o nome `head` é igual a `n`, de forma similar, criaremos mais nós. Agora, alocaremos estes quatro nós na Memória não-contígua, para isso, usaremos o nome de nodes `Node head equals to new Node() new Node braces`. Agora, alocaremos todos os nós de forma semelhante. Agora, atribuiremos dados a nosso primeiro nó. Para acessar membros da classe node, usaremos setas ( -> ). Depois, usaremos `data = dê-lo um valor como dois`. Agora, vamos ligar este primeiro nó com o segundo nó. Para isso, usamos `head` de mesmo jeito usamos setas e `next` é igual ao nó `N1` nó, agora criamos nossa primeira lista completa. Agora vamos criar outros nós. `N1` tem os dados que referem a três, e `N1` tem o `next` do ponteiro é igual a `N2`, `n` tem `data` igual a cinco, e `o` `next` é igual a `código de cabeça`. O `tail` é igual a `T`, o `data` é igual a sete, e o `next` é igual a `null`. Agora vamos chamar uma função para imprimir esta lista. Passaremos `head` como o argumento, pois somente podemos transitório uma lista encadeada da sua cabeça. Definiremos a função print list com `head` como argumento, actuaismente, esta função é uma função de tipo `void`, com argumento `Node head`. Usaremos um laço `while` para percorrer esta lista. Daremos-lhe a condição: se `a` não é igual a `null`, então imprime o valor de `a e o next`. Agora, mudaríamos o cursor (`ch`) para o próximo nó, para isso, usaremos `a` setas e `next`. Agora, vamos tentar executar este código. Como podemos ver, percorremos toda a lista encadeada. Agora voltaríamos para nossas diapositivas. Vamos discutir as diferentes tipos de Listas Encadeadas. A primeira é a Lista Encadeada Única, seguidamenteemos a Lista Encadeada Dupla, em seguida, a Lista Encadeada Circle e finalmente sugerimos a Lista Encadeada Circle Dupla. Vamos discutir cada um deles em detalhes. Primeiro, a Lista Encadeada Única, é uma lista encadeada unidirecional. Enquanto utilizamos a Lista Encadeada Única, podemos transitório apenas para a mesma numa única de cima para baixo. A seguir, a Lista Encadeada Dupla. É uma lista encadeada bidirecional, e podemos alternar entre as duas direções, sendo esta lista composta de um nó com um ponteiro extra que aponta ao nó anterior. A Lista Encadeada Circle é uma lista encadeada normal, exceto pela última lista que aponta para o Cabecera da lista, o programador precisa ser cuidadoso enquanto transitório, caso contrário ele poderá se deparar num laço infinito. Finalmente, a Lista Encadeada CircleDupla é uma combinação da Lista Encadeada Única e a Lista Encadeada Circle. Sua última lista aponta para o Cabecera da lista, e o ponteiro `Previous` do Cabeça lista aponta para a última lista. Também é bidirecional, estando assim nosso controle de acesso aos dois sentidos. Mas é isso, vamos simplificar os pontos principais que já aprendemos: 1. Eu reconheci as listas encadeadas como uma estrutura de dados dinâmica. 2. A Lista Encadeada oferece operações de inserção e remoção mais fáceis do que outras Estrutura de Dados lineares como arrays e arrays em nível superficial. 3. É grande obstáculo quando utilizamos uma lista encadeada estamos sempre começar a partir do Cabeça. 4. Uma lista encadeada é importante implementar outras estruturas de dados como pilhas e filas. Até mais! Obrigado por estar a aprender comigo! # Lista Simplesmente Encadeada em Estruturas de Dados e Algoritmos | Simplilearn Este aula vai apresentar a Você a Lista Simplesmente Encadeada, uma estrutura de dados linear composta por nós. Cada nó consiste em dois componentes: dados e um ponteiro para outro nó, referenciado como um apontador. Você pode percorrer a Lista Simplesmente Encadeada usando ponteiros, começando pelo nó cabeça e indo até o nó cauda. ## Implementando Lista Simplesmente Encadeada no Editor de Código Para criar uma Lista Simplesmente Encadeada em seu editor de código, você precisará primeiro definir uma classe `Node` com os seguintes membros como públicos: - `int data` - `Node* next` Agora, começamos por inicializar nossa primeira nó: ```cpp Node* head = nullptr; ``` Em seguida, alocaremos e inicializaremos quatro nós na pilha de memória usando `new Node*` e atribuiremos seus valores de dados de acordo: ```cpp head = new Node{2}; Node* N1 = new Node{3}; Node* N2 = new Node{5}; Node* tail = new Node{7}; ``` Vincule os nós juntos definindo os ponteiros `next`: ```cpp head->next = N1; N1->next = N2; N2->next = tail; ``` Agora, criaremos uma função `printList` para imprimir o conteúdo da lista: ```cpp void printList(Node* head) { while (head ! = nullptr) { std: : cout << head->data << " "; head = head->next; } } ``` Finalmente, execute o código e chame a função `printList` para exibir a lista encadeada: ```cpp printList(head); ``` ## Operações em Lista Simplesmente Encadeada As operações que podem ser aplicadas a uma Lista Simplesmente Encadeada incluem: 1. Inserção: Você pode inserir um nó em três posições diferentes: no início, no final ou em uma posição específica após outro nó. 2. Exclusão: Similar à inserção, você pode realizar exclusão em três posições diferentes: no início, no final ou em uma posição específica após um nó. Vamos discutir estas operações em detalhes. ### Inserção em Posições Diferentes #### Inserção no Início Para inserir um nó no início, alocaremos um novo nó e definiremos seu ponteiro `next` para o atual cabeça: ```cpp Node* newNode = new Node{newData}; newNode->next = head; ``` Em seguida, atualizaremos o ponteiro cabeça para apontar para o nó recém-inserido: ```cpp head = newNode; ``` #### Inserção no Final Para inserir um nó no final, localizaremos o último nó e definiremos seu ponteiro `next` para o novo nó: ```cpp while (current->next ! = nullptr) { current = current->next; } Node* newNode = new Node{newData}; current->next = newNode; ``` #### Inserção em uma Posição Especifica Para inserir um nó em uma posição específica após outro nó, localizaremos o nó anterior, atualizaremos seu ponteiro `next` para o novo nó, e definiremos o ponteiro `next` do novo nó para o nó existente após a posição de inserção # Aula - 10 | Lista Encifrada Circular na Ciência da Computação e Algoritmos | URL do Simplilearn: <https://www.youtube.com/watch?v=zWKaf_pb2z4> ## Introdução à Lista Encotrada Circular Uma lista encifrada circular é uma estrutura de dados linear composta por nós, semelhante a uma lista encadeada simples, mas com uma diferença importante: o último nó aponta para o nó cabeça. Esses nós consistem de dois componentes - dados e uma referência para um ponteiro de próximo. Esses nós podem ser iterados usando o ponteiro de próximo. Vamos tentar implementá-la no editor de código. Começaremos criando uma classe `Node`: ``` class Node: def __init__(self, dado=None): self. dado = dado self. proximo = None ``` Vamos escrever uma função para inserir um nó no início: ```python def push(cabeca, dado): novo_nó = Node(dado) se cabeca não for nulo: novo_nó. proximo = cabeca novo_nó. proximo. anterior = novo_nó senão: novo_nó. anterior = novo_nó cabeca = novo_nó retorna cabeca ``` Vamos escrever uma função para imprimir esse anexo: ```python def print_list(cabeca): temp = cabeca se cabeca não for nulo: enquanto temp não for nulo: print(temp. dado) temp = temp. proximo ``` Vamos começar com o bloco principal: ```python # Inicialização dessa lista como vazia cabeca = None cabeca = push(cabeca, 5) # 5 cabeca = push(cabeca, 11) # 11 <- 5 cabeca = push(cabeca, 9) # 9 <- 11 cabeca = push(cabeca, 2) # 2 <- 9 ``` Essa lista encadeada circular é: `5 11 9 2` e então volta para `5`. Vamos tentar imprimir essa lista encadeada circular: ```python print_list(cabeca) # 5 11 9 2 5 ``` Vamos discutir algumas das operações que podemos aplicar em uma lista encadeada circular: 1. **Insiração**: podemos realizar a inseração em três situações diferentes - no início, no final e em uma posição específica após um nó. 2. **Exclusão**: podemos realizar a exclusão em três situações diferentes - no início, no final e em uma posição específica após um nó. Vamos discutir essas em detalhes: - **Insiração no início**: para inserir no início, precisamos dar a primeira célula endereço para o ponteiro de próximo do novo nó. - **Insiração no final**: para inserir no final, precisamos dar o endereço do novo nó para a referência de célula do nó mais recente. - **Insiração em uma posição específica**: para inserir em uma posição específica, precisamos alterar o endereço do ponteiro de antecessor para o endereço do novo nó e o ponteiro de próximo do novo nó para o próximo nó. Vamos lhes dar essas operações no code editor: ```python def insert(cabeca, nó_após_qual, dado): novo_nó = Node(dado) se nó_após_qual for nulo: retorna cabeca novo_nó. proximo = nó_após_qual. proximo nó_após_qual. proximo = novo_nó novo_nó. anterior = nó_após_qual def delete(cabeca, nó_para_excluir): se cabeca for nulo: retorna cabeca se cabeca é nó_para_excluir: cabeca = nó_para_excluir. proximo se o proximo de nó_para_excluir não for nulo: o nó_para_excluir. proximo. anterior = nó_para_excluir. anterior o nó_para_excluir. anterior. proximo = nó_para_excluir. proximo ``` # Aula - 11 | Array vs Lista Encadeada em DS e Algoritmos | Simplilearn URL: [https://www.youtube.com/watch?v=l5IY76os2FA](https://www.youtube.com/watch?v=l5IY76os2FA) (Língua: en) ## O que é um Array? Começamos pelo tom, um array é uma estrutura de dados linear que armazena elementos homogêneos em uma forma contínua. Aqui, homogêneo significa que você poderá armazenar apenas um único tipo de dados de elemento em um array. Por exemplo, considere que você declarou um array e queria armazenar algumas informações nesse array. Você pode declarar um tipo de dado primeiro, depois o nome do array. Se você declarar que o array deve armazenar um tipo de dados de inteiro, então poderá somente armazenar elementos inteiros nesse tipo de dado. Não será possível armazenar qualquer outro tipo de dado como float ou character. ## O que é uma Lista Encadeada? Semelhante aos arrays, mesmo uma lista encadeada é uma estrutura de dados linear, mas a diferença é que é uma estrutura de dados dinâmica. Aqui, você pode aumentar ou diminuir o tamanho da lista encadeada em tempo de execução. A diferença é aqui as memórias locais não estão contíguas entre si mas são mais comprida do heap da memória. Assim, existe um grande pedaço de memória fora do qual a lista encadeada irá emprester alguns locais de memória, e uma depois da outra, as nós serão interconectados por meio de ponteiros. ## Tipos de Arrays Basicamente, temos três tipos diferentes de arrays: - **Arrays umidimensionais: ** Estes teve apenas uma subscriptura, portanto eles terão apenas uma única linha. - **Arrays bidimensionais: ** Nesses, teremos subscripts. Será possível ter linhas e colunas. - **Arrays tridimensionais: ** Terão três subscripts, portanto linhas, colunas e blocos de endereço. ## Tipos de Listas Encadeadas Semelhante aos arrays, mesmo listas encadeadas são divididas em três tipos: - **Lista Encadeada Simples: ** Neste tipo de nó existem duas partes, portanto o endereço do nó e seu valor serão armazenados no primeiro segmento, naquela parte do segmento o valor será armazenado e naquela segunda parte do segmento o endereço que aponta para o próximo nó será armazenado. - **Lista Encadeada em Anel: ** A diferença entre a lista encadeada simples e lista encadeada em anel é, no final da lista encadeada simples, você pode ver um ponteiro de valor nulo que indica o fim da lista encadeada, mas na lista encadeada em anel o endereço não será nulo, simplesmente irá apontar para o nó cabeça. Assim, é isso que o faz ser uma lista encadeada de tipo anel. - **Lista Encadeada Duplamente Encadeada: ** Neste caso é possível ver que há dois endereços de localização. O primeiro nó começará com valor nulo em sua primeira localização, depois se armazenará o valor nesse segmento e a segunda localização apontará para o nó seguinte, na segunda parte do nó também você verá o valor armazenado, mas a primeira localização apontará para o nó anterior. A segunda localização apontará para o próximo nó, e no final a segunda localização será apontada como nula, o que indica o fim da lista. ## Diferenças fundamentais entre Arrays e Listas Encadeadas - **Acesso Aleatório: ** Os elementos de um array podem ser acessados aleatoriamente apenas utilizando-se apenas valores de índice, mas o acesso aleatório não é possível em listas encadeadas. Se quiser acessar algum elemento em listas encadeadas, terá necessidade de executar a operação de percorrer sequencialmente. - **Estatica vs dinâmica: ** Arrays são estáticos, o que significa que seu tamanho de memória é fixo e não pode ser alterado em tempo de execução, mas em listas encadeadas o tamanho da memória é dinâmica, de forma que você pode expandir ou diminuir o memória em tempo de execução de acordo com requisitos específicos. - **Independência dos Elementos: ** Cada elemento em um array é independente em natureza, portanto pode acessar algum elemento apenas informando a localização de índice em um array. Em listas encadeadas, os elementos são interdependentes uns dos outros, portanto, para acessar um elemento específico, é necessário percorrer outros elementos ou percorrer os demais elementos. - **Operações: ** Cada operação em arrays demora mais tempo, especialmente operações como inserção e exclusão, mas listas encadeadas tomam menos tempo para realizar operações de inserção e exclusão. - **Tempo de Acesso: ** Acessar qualquer elemento em um array é mais rápido pois o acesso aleatório é possível, mas em listas encadeadas, acessar um elemento é mais lento pois sabemos que é necessário passar por todos os elementos do nó para acessar um elemento específico. ## Aula - 12 | Pilha DS e Algoritmos | Simplilearn URL: [https://www.youtube.com/watch?v=nyuzBU2UUwo](https://www.youtube.com/watch?v=nyuzBU2UUwo) (Língua: en) ## O que é uma Pilha? Basicamente, uma pilha é uma estrutura de dados linear, igual a array e lista encadeada. A diferença é que ela segue uma ordem específica quando uma operação é implementada em ela. A ordem específica que ela segue é de último a ser colocado para ser o primeiro a ser retirado (LIFO) ou primeiro a ser colocado para ser o último a ser retirado (FIFO). Em uma parte posterior, vamos compreender esta ordem de forma muito melhor. Todas as operações podem ser feitas apenas no único extremo acima da pilha. ## Representações de Pilha Podemos representar uma pilha utilizando dois métodos diferentes: - Utilizando um array - Utilizando uma lista encadeada Na representação de pilha utilizando um array, nós utilizaremos uma memória contínua e em uma pilha, nós utilizaremos uma memória não contínua. A pilha funcionará como estrutura de dados dinâmica. Na representação de pilha de lista encadeada, uma estrutura de dados dinâmica que é uma lista encadeada é utilizada para representar a pilha. Esta pilha funcionará como uma pilha circular. Na pilha circular, teremos utilizando o ponteiro de lista de cabeça para apontar para o último nó, e ao tentar inserir um elemento, ele irá apontar para o nó anterior. Assim, habilitaremos uma pilha circular. Ao deletar um elemento, o último nó alterará seu ponteiro do próximo para o nó anterior, e o ponteiro de lista de cabeça apontará para o nó anterior. Assim, o memória da pilha circular será gerenciada de forma dinâmica. A pilha circular mencionada acima é conhecida como Pilha Duplamente Encadeada em Anel. Ela oferece seguintes vantagens em relação à Pilha Linear: - É possível utilizar-la para efficientemente implementar as Filas de Entrada em Primero e Filas de Entrada em Último (FIFO). - Permite acessar ambos os extremos da pilha de forma eficiiente com operações variáveis. Aqui está um exemplo de código para a implementação de uma pilha duplamente encadeada em anel em C++: ```cpp #include<iostream> using namespace std; class Stack { private: int top, capacity; pair <int, pair<Node*, Node*> > circList[20]; // node pair consegue conter o endereço do dado e o próximo nó public: Stack(int size) { top = -1; capacity = size; } bool isFull() { return (top == capacity - 1); } bool isEmpty() { return (top == -1); } void push(int data) { if (! isFull()) { Node *node = new Node(data); if(isEmpty()) { circList[0] = make_pair(data, node); circList[1] = make_pair(NULL, &circList[0]); top++; } else { Node* last_node = circList[top]. second; last_node->next = node; node->next = circList[0]. second; top++; } } else { cout << "Stack Overflow" << endl; } } void pop() { if (! isEmpty()) { Node* top_node = circList[top]. second; if(top > 1) { top_node->next = circList[0]. second; top--; } else { circList[1]. second = circList[0]. second; top = 0; } } else { cout << "Stack Underflow" << endl; } } int top_element() { if (! isEmpty()) { return circList[top]. first; } return -1; } }; ``` Aqui, estamos utilizando uma par da parte de nó, que contém o endereço do dado e o próximo nó para implementar a lista duplamente encadeada em anel. Estamos então utilizando dois ponteiros para fazer referência ao primeiro e último nó da pilha. # Pilha Dinâmica Uma pilha dinâmica não precisa definir o número máximo de elementos presentes na pilha. Lembra-se que nos arrays, precisávamos definir o valor máximo. Neste caso, não é necessário atribuir o valor máximo pois a pilha é dinâmica. Os ponteiros são usados para armazenar a endereço das notas a seguir, e a variável utilizada em este método é `top`. A `top` está a apontar para o endereço do elemento mais alto na pilha representada por uma lista ligada. ## Operações Básicas na Pilha ### Operação Push A operação `push` é o mecanismo de inserção de um novo elemento na pilha. Siga estes passos: 1. Verifique se a pilha está cheia ou não. 2. Se a pilha estiver cheia, gere um erro e termine. 3. Se a pilha estiver cheia, não é necessário executar nenhuma operação. 4. Se a pilha não estiver cheia, incremente o `top` por um. 5. Adicione os elementos de dados na pilha onde o `top` aponta. 6. Sucesso – a operação `push` foi bem-sucedida. ### Operação Pop A operação `pop` é o mecanismo de eliminação ou deleção ou remoção de um elemento de dados da pilha existente. 1. Verifique se a pilha está vazia ou não. Se ela estiver vazia, termine o controle e encerre a pilha. Se a pilha não estiver vazia, poderá continuar com a operação de pop. 2. Se a pilha não estiver vazia, abraçar o elemento de dados no topo e peça-lhe para sair. 3. Decremente o topo por um. ### Operação Peak A​`peak` operação envolve a devolução do elemento de dados no topo da pilha sem mover-o. Você passa o valor do elemento do topo. ## Exemplo Prático Agora vamos realizar um exemplo prático. Aqui está o exemplo de código para a pilha: ```cpp // Verifique se a pilha está vazia if (isEmpty(stack)) { return; } // Verifique se a pilha está cheia if (isFull(stack)) { exit(); } // Operação Push Exemplo push(stack, data); // Operação Pop Exemplo pop(stack); // Operação Peak Exemplo peak(stack); ``` Neste exemplo, temos três funções para push, pop e peak. Tentaremos inserir elementos e também eliminar e tentar invocar algumas funções sobre ele ou executar algumas operações na pilha. # Operação de Pico em Lista Encadeada Realizando a operação de pico em uma lista encadeada significa recuperar o elemento que aponta como o topo. Não envolve a eliminação ou a adição de um novo elemento no topo. Em vez disso, copia o elemento ou recupera o elemento que aponta como o topo. Essa operação é chamada de operação de pico. Em uma lista encadeada, é possível realizar a operação de pico usando a teoria discutida. Vamos executar um programa prático baseado em uma implementação de pilha usando uma lista encadeada. Pode ver um exemplo de código aqui, com base no qual vamos Implementar uma pilha usando uma lista encadeada. Aqui estamos com as operações Pop, Push e Top seguidas pela função principal. Não se preocupe com o código. Este snippet de código específico será anexado na caixa de descrição abaixo, ou você pode pedir-nos, e será enviado para seu endereço de email. Você pode entender o código e tenter executá-lo em seu sistema local. Sem mais demora, vamos executar o código diretamente. Então, aqui está o que aconteceu: ``` O código foi compilado com sucesso. Tentamos adicionar alguns elementos na pilha: A, B, C. C é o elemento mais alto da pilha, e este elemento foi removido. Após isso, o elemento no topo (após C ser removido como B) também foi removido novamente. O único elemento que estava na pilha foi A, que foi removido após a realização da operação de remoção. Agora, atualmente a pilha está vazia. Você pode ver que tentamos empilhar os elementos A, B, C, e também tentamos desempilhar os elementos A, B, C. ``` ## Aula 13 | Fila e Fila de Prioridade em Estruturas de Dados e Algoritmos | Simplilearn Vamos começar com a introdução às filas. Você sabe que todos nós dependemos de aplicativos de mensagem como WhatsApp, Facebook Messenger, Instagram chats para se comunicar com nossos amigos e familiares. Quando o usa, provavelmente tenha observado que a pessoa com quem está tentando se comunicar recebe mensagens na mesma ordem em que você as enviou. Agora, a questão que se dá é como isso está acontecendo? Como estas aplicações mantêm a ordem dessas mensagens? A solução para essas perguntas nos traz para as filas. Basicamente, em esses aplicativos, uma fila é mantida para cada usuário contendo as mensagens a serem entregues ao usuário. Quando o usuário conecta à rede, todas as mensagens na fila são entregues, e após entregue, as filas vazias são excluídas. Claro que isso ilustra a importância das estruturas de dados. Vamos abordar mais a fundo a estrutura da fila. Antes disso, vamos olhar um exemplo real da fila para entendê-la mais claramente. O exemplo mais comum e relevante da fila é ação de contagem de entradas de filme em um lanhouse de filme. No filme de contagem, provavelmente tenha observado que ambos os extremos permanecem abertos. Além disso, a pessoa que entra primeiro recebe um bilhete primeiro, e certamente a pessoa que entra por último vai receber mais tarde. Filas e estruturas de dados ressemelham todos esses Propriedades da fila de contagem de ingressos, o que torna eles melhores na criação de sistemas virtuais primeiro-ao-principio. Estruturalmente, eles são definidos como uma coleção linear de diferentes tipos de dados que permitem a inserção em um único ponto e a deletição em outro. Diferentemente de qualquer outra estrutura de dados, os extremos de uma fila permanecem abertos, permitindo que ela tenha diferentes funcionalidades em ambos os extremos. O extremo pela qual a inserção ocorre é chamado de rear, e o extremo pela qual a deletção ocorre é conhecido como front. Além disso, existem dois Links de abordagem para considerar a estrutura da fila, e todos isso depende da abordagem do programador. Isso significa que, como um programador, caso considere o final esquerdo como o reverso, então o nó de trás será o nó de trás da fila, enquanto que caso considere o final esquerdo como o front, o extremo direito será o nó de fronteira da fila. Dinamicamente, os elementos na fila não podem ser operados em suas posições respeitivas. Eles podem ser operados apenas do extremo frontal ou posterior. Avançando, vamos discutir as operações em estruturas de dados de fila um a um. A primeira operação é **Enqueue (NQ)**. Ela é usada para armazenar os elementos no fila. Seguirá o **Dequeue (DQ)**. O uso desta operação é para retirá-los. Também temos as operações **Full** e **IsEmpty**. A função **Full** analisa se a fila está cheia ou não, e a operação **IsEmpty** avalia se a fila está vazia. Também temos a operação **Front**, que retorna o elemento no topo (front) da fila, além de **Size (**tamanho**)**, que retorna o número de elementos na fila. # Filas em Desenvolvimento de Software: Análise Detalhada A fila, uma estrutura de dados comum, é frequentemente utilizada em várias aplicações, como aplication web ou apps móveis. Por exemplo, a Domino's Swiggy utiliza uma fila para gerenciamento de status de pedidos de comida. Quando você faz um pedido por um portal online, seu ID de pedido entra na fila, e, uma vez que os pedidos antes do ID de pedido sejam atendidos, seu pedido também será atendido. A partir desses exemplos, podemos claramente dizer que as filas são usadas sempre que existe uma necessidade de uma estrategy FCFS em desenvolvimento de software. Quando trabalhando em projetos que exigem uma abordagem FCFS, lembre-se de que você precisa implementar uma estrutura de dados de fila para completá-los com sucesso. Vejamos alguns pontos chave que discutimos nessa sessão: ## Estrutura da Fila 1. A fila é uma estrutura linear. 2. A fila permite a inserção em um único fim e a exclusão em outro. 3. As extremidades da fila permanecem abertas, permitindo que ela tenha diferentes funcionalidades em ambos os extremos. 4. A fila funciona sob restrições: a inserção deve ser realizada no nó rear e exclusão no nó front. 5. As operações de inserção e exclusão são conhecidas como NQ (Nova Entrada) e DQ (Exclusão), respectivamente. ## Operações e Tipos de Filas 1. A NQ e a DQ são responsáveis pela manipulação de dados em uma fila. 2. Discutirmos diferentes tipos de filas, tais como: 1. Fila Circular 2. Fila Encadeada 3. Fila Baseada em Pilha 3. Descobriríamos aplicativos de filas e como elas atendem à necessidade de sistemas FCFS. 4. As filas podem lidar com vários tipos de dados também. ## Implementando uma Fila Até agora, já sabemos que as filas são uma espécie de lista com algumas restrições na inserção e exclusão, e existem duas abordagens para tratar da implementação da fila: 1. Implementação baseada em Array 2. Implementação baseada em lista encadeada Nesta sessão, vamos focar em uma implementação baseada em array. ## Implementação de Filas Baseadas em Arrays (C++) Para implementar uma fila usando uma matriz de um-dimensional em C++, precisamos incluir as seguintes bibliotecas: ``` #include <iostream> #include <stdio. h> using namespace std; ``` Precisamos inicializar nossa matriz e os ponteiros front e rear, onde 'size' é o tamanho da matriz declarado antes da compilação. ``` int q[100]; int front = -1; int rear = -1; ``` ### Funções da Fila - `isFull()`: Valida se a fila está cheia. Se o ponteiro rear é igual ao tamanho máximo da fila, a fila estará completamente cheia. - `isEmpty()`: Valida se a fila está vazia. Se o ponteiro rear e o ponteiro front apontam para -1, a fila estará vazia. - `Peek()`: Extrait o elemento onde o ponteiro front aponta sem remover da fila. - `NQ()`: Realiza a inserção na fila. Trata de·o fluxo do array se a fila está cheia e trata de fluxo do array se a fila está vazia, - `DQ()`: Realiza a exclusão de um elemento da fila. Trata de fluxo do array se a fila está vazia. Com todas estas operações implementadas, podemos verificar a funcionalidade da fila usando a função de exibição a seguir. Esta função imprime os elementos da fila na tela e os elementos serão exibidos apenas se a fila não estiver vazia. ## Programa de exemplo ```cpp #include <iostream> #include <stdio. h> using namespace std; void isFull(); void isEmpty(); int Peek(); void NQ(int value); void DQ(); void display(); int main() { int choice; cout << "Informe as operações (1-Inserção 2-Exclusão 3-Exibir 0-Encerrar) "; cin >> choice; switch (choice) { case 1: { int value; cout << "Informe o valor: "; cin >> value; if (! isFull()) { NQ(value); } else { cout << "Tamanho maisico atingido! Informe um tamanho maior para a matriz! "; } break; } case 2: { if (! isEmpty()) { DQ(); } else { cout << "Tamanho below zero! Informe um tamanho maior para a matriz! "; } break; } case 3: { display(); break; } case 0: { return 0; } } main(); return 0; } void isFull() { if (rear + 1 == size) { cout << "A fila está Cheia! \n"; return; } } void isEmpty() { if (rear == -1 && front == -1) { cout << "A fila está Vazia! \n"; return; } } int Peek() { isEmpty(); return q[front]; } void NQ(int value) { if (isFull()) { cout << "Tamanho maisico atingido! Informe um tamanho maior para a matriz! "; return; } if (rear == -1) { rear = 0; front = 0; } else { rear = (rear + 1) % size; } q[rear] = value; } void DQ() { if (isEmpty()) { cout << "Tamanho below zero! Informe um tamanho maior para a matriz! "; return; } front = (front + 1) % size; } void display() { int i = rear; if (! isEmpty()) { cout << "Fila: \n"; if (front <= rear) { for (int i = front; i <= rear; i++) { cout << q[i] << " "; } } else { for (int i = front; i < size; i++) { cout << q[i] << " "; } for (int i = 0; i <= rear; i++) { cout << q[i] << " "; } } } } ``` # Fila Prioritária em Markdown ## Introdução A fila prioritária é uma estrutura de dados que mantém uma coleção de elementos, cada um com uma prioridade associada, e garante que o elemento de maior prioridade seja sempre o desempilhado primeiro. Neste tutorial, discutiremos a representação de uma fila prioritária utilizando uma pilha encadeada e compararemos com outras abordagens. ## Representação por Pilha Encadeada ### Vantagens - **Inserção**: A complexidade da inserção é O(1) - **Remoção**: A complexidade da remoção permanece constante, já que é O(1) - **Operação de Visualização**: A complexidade da operação de visualização é O(1) ### Desvantagens - **Inserção**: A implementação da pilha encadeada de prioridade pode ser vulnerável devido à pesquisa linear necessária para a inserção de um novo elemento em O(n) de tempo - Gerenciamento de Memória: A implementação em pilha encadeada consome mais memória em comparação com outras estruturas de dados como heap binário e árvore binária devido ao overheader de memória dos pontadores ### Exemplo ```markdown Elementos atuais na Fila Prioritária: (3, 43, 45, 17, 45, 43) Insere овidenおotivo com dados 2 Dado que o dado é menor que o nó de cabeça (3), o novo nó deverá ser inserido antes dele. Insere овidenおotativo com dados 45 O nôvo nodo é comparado com o elemento 2, em seguida, com 3. Como ele é maior que ambos, a variável temporal se move para o próximo nó para comparação. Após duas comparações, a variável temporal atingiu o nó de cola (17). Comparando com 17, o nó novo é inserido após o nó que contém dados 17. Remoção do elemento de maior prioridade (3) A remoção é tempo constante O(1), pois o elemento de maior prioridade está sempre na fila de prioridade. ``` ## Comparação com Outras Abordagens Além da abordagem de pilha encadeada, existem duas outras abordagens para a implementação de uma fila prioritária: heap binário e árvore binária. O heap binário e a árvore binária fornecem complexidades quase idênticas: O(log n) para inserção e remoção, e O(1) para a operação de visualização. Contudo, a escolha da abordagem ideal depende das considerações relacionadas à gestão de memória. - Heap Binário: Implementado por arrays, utiliza arrays para otimização ao benefício da memória local e desempenho na memória cache mais rápido. - árvore binária: Utiliza pontadores para implementar o nó front e traseiro, o que consuma mais espaço em memória. A construção de uma árvore binária auto-balanceada custa O(n log n). Sobre gerenciamento de memória, o heap binário é a estrutura de dados ótima para a implementação de uma fila prioritária porque custa apenas O(n) em ambas a inserção e a remoção, enquanto a árvore binária (árvore AVL) custa O(n log n) para a construção de uma árvore binária auto-balanceada. ## Estrutura de Dado Heap O heap é uma estrutura de dados baseada em árvores que satisfaz a invariante de heap, que declara que se a for um nó pai de B, então a é ordenado com relação a B. Os elementos do heap podem ser organizados de duas maneiras: heap máximo e heap mínimo. - Heap Máximo: O valor do nó pai é maior do que ou igual ao valor de seu nó filho. Em um heap máximo, o nó raiz tem o maior valor, enquanto todos os outros nós filhos têm valores mais pequenos. - Heap Mínimo: O valor do nó pai é menor do que ou igual ao valor de seu nó filho. Em um heap mínimo, o nó raiz tem o menor valor, enquanto todos os outros nós filhos têm valores mais grandes. ### Exemplo ```markdown Elementos atuais no Heap Máximo: 43 / \ 40 45 \ / \ 45 17 6 ``` ### Implementação da Fila Prioritária utilizando Heap Mínimo A exemplificação do heap mínimo ainda não adicionada. ## Conclusão A fila prioritária é essencial em algoritmos que exigem processamento em tempo real de eventos ou tarefas. Neste tutorial, discutimos a representação de filas prioritárias mediante uma pilha encadeada e comparamos com outras abordagens, como heap binário e árvore binária. O heap binário é a melhor opção para implementar uma fila prioritária em termos de gerenciamento de memória. Também exploramos a estrutura de dados heap e seus tipos: heap máximo e heap mínimo. Em sessões futuras aprenderemos a implementar uma fila prioritária usando um heap mínimo. Acompanhe! Abaixo segue o texto com a correção da formatação Markdown: ```markdown Implementação de Heap =============== Esta estrutura que criamos previamente é adequada nesta função. Inicialize as variáveis dentro desta estrutura Heap: # Inicialização de Heap Inicialize as variáveis dentro desta estrutura Heap: ```C # Declaração das Variáveis H_Op operator H_Op count = 0 H_Op size_sides = INITIAL_SIZE_SIDES H array = (int*) malloc(H_Op size_sides * sizeof(int)); #Implantação da Inicialização de Heap if(NULL == H_Op_allocate(H_Op count, H_Op size_sides, H)) { print("Error: F while allocating memory to Heap array"); exit(0); } ``` Para mover os nós, implemente a função `maxcore_hipy`: ```C #Função bidirecional maxcore_hipy void maxcore_hipy(int* data, int largest, int* count) { int left, right, largest_location; // Se a localização esquerda for maior ou igual à localização atual ou se o valor de data_left for maior que o valor de localização_atual if(left >= C && data_left > data[largest_location]) { largest_location = left; } // Verifique o valor à direita if(largest_location > right && data[right] > data[largest_location]) { largest_location = write; } // Se o valor de data for maior que o valor de data_largest e data for maior que o valor apontado pela localização_atual if (write >= *count && data > data[largest_location]) { largest_location = write; } // Altere a localização atual se for menor do que a posição recentemente definida if(largest_location ! = largest_location) { int temp; temp = data[largest_location]; data[largest_location] = data[largest_location_old]; data[largest_location_old] = temp; largest_location_old = largest_location; } } ``` Implemente a função de inserção para o operador push: ```C #Função Heap_push void Heap_push(struct Heap* H, int value) { int index, parent; // Verifique se a contagem de elementos atual é igual ao tamanho atual do array if(H->count == H->size) { H->size += 1; H->HeapArray = (int*) realloc(H->HeapArray, (sizeof(int)*H->size)); } // Verifique se a memória foi alocada if(NULL == (H->HeapArray)){ exit(EXIT_FAILURE); } // Atribua o valor passado no argumento ao array de qualquer espaço inativo H->HeapArray[H->count++] = value; // Inicialize as posições índice e nó de pai, depois itere e verifique se os nós pais são menores ou igual aos nós filhos. index = H->count; parent = index/2; while(index >= 1 && H->HeapArray[parent] >= H->HeapArray[index]) { H->HeapArray[parent] = H->HeapArray[index]; H->HeapArray[index] = H->HeapArray[parent]; // Atualize a posição atual com a posição atual de pai index = parent; parent = index/2; } } ``` Implemente a função **Heap_delete** que usará o operador pop: ```C #Função de Ex # Documentação Técnica: Fila Prioritária e Desemзомbrar na Estruturas e Algoritmos de Dados Com um exemplo, vamos dizer que o nosso arquivo de texto contém estes caracteres aleatórios. O algoritmo de codificação Huffman cria códigos para cada caractere com base em sua ocorrência. Por exemplo, a frequência do caractere 'a' é mais predominante do que 'b', e 'c' tem menos frequência. Então, o comprimento do código 'a' será menor que o código 'b', e o comprimento do código 'b' será menor que o código 'c'. Agora, de acordo com a quantidade de código gerado, a fila prioritária `Q` determinará a prioridade dos caracteres e os armazenará. Esses códigos utilizam menos espaço em memória do que os caracteres reais, e por causa do mapeamento das ocorrências, os dados não serão perdidos. Espero que agora vá entender por que a fila prioritária `Q` é tratada como uma estrutura de dados abstrata e porque é a versão melhor da estrutura de dados `Q`. --- ## Aula - 14 | Desemzoombrar em Estruturas e Algoritmos de Dados e Algoritmos | Simplilearn [URL](https://www.youtube.com/watch?v=4gaGxVQRx-Q) ### Português O que é DQ? DQ é a abreviatura de "duas extremidades q", e como a abreviatura "duas extremidades Q" sugere, é uma versão da `Q` com algumas características avançadas em ambas as extremidades. No entanto, previamente tivemos aprendido que a estrutura de dados `Q` básica deve realizar a inserção da uma de suas extremidades e a exclusão da outra ou extremidade oposta. Portanto, qual é a limitação ou nova característica que esta versão particular de `Q` três? Bem, o DQ estende a ideia de uma fila `Q` lineares Implementando a inserção e exclusão em ambas as extremidades. Por isso, a estrutura DQ é familiarizada com a capacidade de inserir e excluir novos elementos em qualquer extremidade, por isso, DQ é considerado uma versão mais geralizada da estrutura de dados `Q`. Movendo-se adiante, entendamos as propriedades essenciais de DQ. A primeira propriedade de DQ afirma que a fila DQ pode utilizar o Principio de Push e Pop (Last In First Out - LIFO) para a inserção ou remoção de elementos. Este princípio é utilizado ao implementar a estrutura de dados pilha, e segundo este princípio, os elementos devem ser inseridos a partir de um extremidade e depois devem ser excluídos a partir da mesma extremidade. Por exemplo, estamos armazenando alguns elementos nesta estrutura `CU`, que é performando a exclusão e a inserção a partir de uma extremidade, no entanto, quando a exclusão começa, o elemento inserido no fim será removido primeiro, enquanto o elemento armazenado no início será removido no fim. Isso claro que a fila DQ pode herdar todas as propriedades da estrutura de dados pilha. A próxima propriedade de DQ afirma que a fila DQ pode utilizar o Principio de FIFO (First In First Out - FIFO) para realizar tanto a inserção como a exclusão de operações. Isso significa que o elemento que entrou nesta fila no início também sairá da fila no fim, enquanto o elemento que entrou no fim pode ser removido no fim. Ambas as propriedades exibem a unicidade dessa determinada versão de `Q` de dados. Agora, nas próximas slipes, vamos entender os diferentes tipos de fila DQ. O primeiro tipo de fila DQ que teremos é chamado de fila `DQ` de contento de entrada restrito. Este termo proprietário consumers que nesta versão particular de fila DQ, a inserção apenas pode ser realizada em uma das extremidades, enquanto a exclusão pode ser realizada em ambas as extremidades. A ilustração de fila DQ abaixo mostra como as operações nestas filas DQ se realizarão. O próximo tipo de fila DQ é a fila DQ de conteúdo de exclusão restrito. Este termo faz claro que esta versão de `Q` possuirá algumas restrições em relação a exclusão em questa específica de fila. A exclusão de elementos pode ser realizada apenas em uma das extremidades, enquanto a inserção pode ser realizada em ambas as extremidades. Esta estrutura de fila DQ mostra como as diferentes operações se realizarão nestas filas DQ. Agora, está a questão interessante que queremos que todos respondam baseadas na propriedade DQ que já discoramos até agora. Qual das estruturas de Dados seria mais ideal para a implementação de DQ em dados estruturas? Quanto nós entendemos por ideal, é a complexidade ao tempo e à espaço para a estratêgia de implementação que você escolhe deve ser mínima. As opções disponíveis são arquito circular, lista encadeada simples e lista encadeada dupla. Será interessante ver quantos de vocês ultrapassarão este teste, portanto, por favor, deixe suas respostas nesta questão no segmento de comentários. Abraços, [Nome de Usuário] Amigo dados. . . escndores! Nos próximas slide a bateria. > Disponível em: <https://www.youtube.com/watch?v=4gaGxVQRx-Q> ### Inglês What is DQ? DQ is an abbreviation for double-ended queue (deque) and as the term double-ended queue suggests, it is a type of queue that has some advanced features at both of its ends. However, previously, we learned that the basic queue data structure must perform insertion from one of its ends and deletion from the opposite end or vice versa. So what new limitation or feature does this particular type of queue bring? Well, the DQ extends the concept of a linear queue by implementing insertion and deletion at both of its nodes. By that, I mean the DQ data structure is acquainted with the possibility to insert and delete new elements at any node, which is why DQ is considered as a more generalized version of queue data structure. Moving ahead, we shall understand the key properties of DQ. The first property of DQ states that the DQ can use the LIFO (Last In First Out) principle for inserting or removing elements. This principle is used while implementing stack data structure, and according to this principle, the elements must be inserted from one end and should also be removed from the same end. For instance, we are storing some elements inside this CU structure, which is performing both insertion and deletion from one end. However, when deletion begins, the element inserted at last will be removed first, whereas the element stored at first will get removed at the end. This property explains that the DQ can inherit all the properties of the stack data structure. The next property of DQ states that the DQ can use the FIFO (First In First Out) principle for performing both insertion and deletion operations. That means the element which enters inside the queue at the beginning will also leave the queue at the beginning, and the element which entered at last can leave the queue at last. Both of these properties display the uniqueness of this particular type of queue data structure. Now in the upcoming slides, we will understand different types of the DQ. The first type of DQ we have is termed as restriction input DQ. This terminology itself suggests that this particular type of DQ will have some restrictions while performing enqueue or insertion operation in this type of DQ, the enqueue operation can only be performed from one end, whereas the dequeue operation can be performed at both of its ends. The diagram below illustrates how the operations in this type of DQ will take place. The next type of DQ is the restriction output DQ. This term makes it clear that this type of queue will have some restrictions while performing dequeue operations in this specific type of queue. The removal of elements can only be performed from one end, whereas the enqueue operation can be performed at both of its ends. This particular structure of a queue demonstrates how the different operations will occur in this type of DQ. Now, here is the interesting question that we want you all to answer based on the properties of DQ that we have discussed till now. Which of the following data structure will be more ideal for implementing DQ in data structures? What I mean by ideal is the time and space complexity for implementation strategy that you decide should be minimum. The options that we have are circular array, singly linked list, and doubly linked list. It will be interesting to see how many of you guys will get this right, so guys do leave your answers to this question in the comment section. In a week's time, we will announce the right answer and you all can check it out on that note. Let's get on with it and understand the representation of DQ using circular Q and, yes, circular Q is one of the answers for the previous questions. We'll understand why it is in the upcoming slides, just check the complexity analysis of other two strategies to decide your answer for the previous question as multiple answers can be accurate for that question. Now, let's understand what a circular Q is. . . Well, a circular Q is nothing but the extended notion of linear Q as it follows the FIFO (First In First Out) principle with the exception of the last position of this particular Q being connected to its first position, making a circular linked list. This circular list is responsible for naming the Q as circular Q or ring buffer. Let's understand how a circular Q operates with the help of simulation. This is the illustration of a circular Q with the size n as the position of the rear pointer keeps increasing, a new element will be inserted in this Q until the rear pointer reaches the end of Q but what if we perform dequeue operation? If we perform the dequeue then the empty space will get created at the front of Q. However, this space can further be utilized in this particular type of Q by making circular incrementation of the rear pointer or by using a circular linked list. So, if we increment the rear pointer from its index 5 to index 0, we can utilize empty space by making new insertions using this feature of circular Q. We can implement both enqueue and dequeue at both ends of Q using this feature of circular Q. Let's understand how we can do that in upcoming slides. . . The first type of enqueue is a pretty ordinary one and this enqueue will increment the position of the rear pointer to insert a new element. The arrow shown in blue is the rear pointer and the arrow shown in orange is the front pointer, so as you can see, once we increment the rear pointer, new elements will get inserted on the right of the front pointer. This is how the enqueue using rear node happens. . . Now what if we want to perform enqueue using the front node? To perform the enqueue using the front node we'll have to make the front pointer reach to the end of Q. The only way to achieve that is to set the front pointer equal to the maximum size minus one manually, which will allow the front pointer to reach the end of Q for performing insertion and for further enqueue we'll just have to decrement the front pointer location for inserting new elements. This is all about enqueue. Now that we have implemented enqueue from both ends, let's move on to the dequeue operation. . . First, we'll look at the dequeue operation using the front node. To dequeue elements using the front node, we'll have to increment the front pointer until it reaches the end of Q and once it reaches the max size, we'll use circular incrementation to bring the front pointer to the beginning of Q while performing these incrementations, meanwhile, the elements will be removed from the queue. Next up is dequeue using rear end to perform dequeue using a rear pointer we'll have to decrement its location once we decrement the rear pointer location, the element will get removed from the queue. In upcoming slides, we'll look at the different operations that can be performed on a double-ended Q. Basically, there are four primary operations in the DQ which we have already understood in the representation of DQ using circular Q. These operations are enqueue and dequeue at front and enqueue and dequeue at the rear. Additionally, the circular Q implementation of DQ costs us the most optimal complexity that we can hope for. Having said that, let's dive into the implementation of these operations using a circular Q. We'll implement a double-ended queue using circular Q in order to do that, we'll use the C programming language and one-dimensional arrays. So without wasting any more time, let's move on to the code editor now. . In order to get started with the implementation of DQ, we'll have to create an array and pointer variables. So, let's do that. We'll name this as DQ and provide 100 as the initial size. After this initialization process, we'll start working with the enqueue at the rear node. We'll declare the function named as : `void rear_enqueue`, so let's do that. `void rear_enqueue` will pass `data` as an argument to enqueue an element. Now while performing enqueue, we should always check if the Q is full or not because if it is full, then the enqueue won't be possible. For that, we'll write the condition `if (front == 0 && rear == size - 1) // Queue is Full, enqueue is not possible`. Moving on to the positive cases, if the rear pointer is pointing to null then we'll have to initialize the front and rear pointers to zero to make the first insertion into the Q and we will insert the element at the index 0 where the rear pointer is pointing. So, for that, what we'll do is we'll write another condition `if (front == -1 && rear == -1) { front = 0; rear = 0; DQ[rear] = data; }`. If the rear pointer is already at the end of Q and there is still space available, then what we'll do is we'll increment our rear pointer to make an insertion. So, for that, what we'll do is we'll create the `else` block and inside it, we will write `rear++` and we will set `DQ[rear] = data`. If all these mentioned conditions fail, then the rear pointer has reached its maximum size and we are left with the last possible last insertion location. For that, we'll write another condition `else if (rear == size - 1) { front = 0; rear = 0; DQ[rear] = data; }`. With this, we have successfully implemented the `void rear_queue` function. The dequeue function will be quite similar to the enqueue function that we discussed previously. For implementing the `void front_dequeue` function, we'll have to pass no arguments to the function as we are only removing elements. So there is no need to check the size of the Q in this function as by passing the function call we are dequeuing an element. The next question that comes to our mind is how will the front pointer be handled after removing an element from the front? To handle the front pointer after removing an element, we'll need to update both the front and rear pointers if there is only one element in the array. So, for that, let's write another condition `if (rear == front) { front = -1; rear = -1; }`. If there are multiple elements in the array and we dequeue an element then we will have to increment the front pointer and also decrement the rear pointer if needed. Considering the condition `if (rear > front) { front += 1; rear -= 1; }`. With this, we have successfully implemented the `void front_queue` function. Similarly, we will implement other operations like isEmpty and size which will help us test our DQ implementation. This ends the implementation of Deque using Circular Q in C. Here is the complete code for Deque using Circular Q in C: ``` #include<stdio. h> #include<assert. h> #define CQ_MIN_SIZE 3 #define CQ_MAX_SIZE 100 #define MAX(*(rear>front)? : rear) void InitCQ(int CQ[], int *front, int *rear) { front = rear = NULL; } int isEmptyCQ(int CQ[], int front, int rear) { // An empty queue is a queue without any elements if(front == -1 && rear == -1) { return 1; } else { return 0; } } int sizeCQ(int CQ[], int front, int rear) { // Calculate the size of the circular queue int size = MAX(rear, front) - MIN(rear, front) + 1; // Check if the circular queue wraps around if (size > CQ_MAX_SIZE) size = size % CQ_MAX_SIZE; // Anything greater than the size means the queue is full if (size > 0) return size; return 0; } void rear_enqueue(int CQ[], int *front, int *rear, int data) { int size = isCentralized(CQ, front, rear); // Queue is full, enqueue is not possible if (size == CQ_MAX_SIZE) { printf("DQ is Full\n"); return; } // Queue is empty or the rear pointer is pointing to null index if (front == -1 && rear == -1) { front = 0; rear = 0; CQ[rear] = data; } else { rear++; // Handle the circular rear == CQ_MIN_SIZE { rear %= CQ_MIN_SIZE; } CQ[rear] = data; } } void front_dequeue(int CQ[], int *front, int *rear) { // If the front pointer is pointing to null index if (front == -1 && rear == -1) printf("DQ is empty\n"); else { // The front pointer index should be in a legal range assert(front < CQ_MAX_SIZE); int data = CQ[front]; CQ[front] = -1; // If DQ is full, ensure that it isn't empty if (sizeCQ(CQ, front, rear) == 0) { // Update the rear pointer to -1 rear = -1; } else { front++; // Handle the circular front == CQ_MIN_SIZE { front %= CQ_MIN_SIZE; } } printf("Elements dequeued: %d\n", data); } } ``` Hope you enjoyed learning Deque using Circular Queue in C through this tutorial. Happy learning! [Nome de Usuário] # Implementação de Fila Duplamente Encadeada (Deque) em C Este texto fornece uma explicação e implementação de uma Fila Duplamente Encadeada (Deque) na linguagem de programação C. A Fila Duplamente Encadeada segue a ordem FIFO (primeiro em, primeiro saída) e LIFO (último em, primeiro saída), tornando-a uma estrutura de dados útil para diversas aplicações. ## Função Front Deque A função `void DQ_front()` é criada para inserir elementos no topo (front) da Fila Duplamente Encadeada. Esta função não recebe nenhum argumento e verifica se a Fila Duplamente Encadeada está vazia antes de executar a operação. ```markdown void DQ_front(void) { // Verifica se a Fila Duplamente Encadeada está vazia if (front == -1 && rear == -1) { printf("F\n"); return; } // Implementação da adição de um elemento no topo da Fila Duplamente Encadeada // . . . } ``` ## Função Rear Deque A função `void DQ_rear()` é criada para inserir elementos no fim (rear) da Fila Duplamente Encadeada. Esta função também não recebe nenhum argumento e verifica se a Fila Duplamente Encadeada está vazia antes de executar a operação. ```markdown void DQ_rear(void) { // Verifica se a Fila Duplamente Encadeada está vazia if (front == -1 && rear == -1) { printf("F\n"); return; } // Implementação da adição de um elemento no fim da Fila Duplamente Encadeada // . . . } ``` ## Função Front Deque Delete A função `void DQ_front_delete()` é criada para remover um elemento do topo (front) da Fila Duplamente Encadeada. Esta função não recebe nenhum argumento e verifica se a Fila Duplamente Encadeada está vazia antes de executar a operação. ```markdown void DQ_front_delete(void) { // Verifica se a Fila Duplamente Encadeada está vazia if (front == -1 && rear == -1) { printf("F\n"); return; } // Implementação da remoção de um elemento do topo da Fila Duplamente Encadeada // . . . } ``` ## Função Rear Deque Delete A função `void DQ_rear_delete()` é criada para remover um elemento do fim (rear) da Fila Duplamente Encadeada. Esta função não recebe nenhum argumento e verifica se a Fila Duplamente Encadeada está vazia antes de executar a operação. ```markdown void DQ_rear_delete(void) { // Verifica se a Fila Duplamente Encadeada está vazia if (front == -1 && rear == -1) { printf("F\n"); return; } // Implementação da remoção de um elemento do fim da Fila Duplamente Encadeada // . . . } ``` ## Função Display A função `void display()` é criada para exibir o estado atual da Fila Duplamente Encadeada. Esta função não recebe nenhum argumento e iterará através da Fila Duplamente Encadeada para imprimir seus elementos. ```markdown void display(void) { int i; // Cria uma variável iteradora (i) e inicializa-a no ponteiro front i = front; // Verifica se a Fila Duplamente Encadeada está vazia if (front == -1) { printf("F\n"); return; } // itera pela Fila Duplamente Encadeada e exibe seus elementos // . . . } ``` ## Função Main A função main demonstra como utilizar as funções da Fila Duplamente Encadeada para diversas operações como a inserção de elementos, remoção de elementos e exibição do estado da Fila Duplamente Encadeada. ```markdown int main() { // Implementação da inicialização da Fila Duplamente Encadeada // . . . // Inserção de elementos usando os ponteiros front e rear // . . . // Exibição do estado da Fila Duplamente Encadeada display(); // Remoção de elementos do topo e do fim da Fila Duplamente Encadeada // . . . // Exibição do estado da Fila Duplamente Encadeada após a remoção display(); // Retorna um código de sucesso (0) return 0; } ``` # Operação Deque O *Deque* é uma operação que deleta um item da fila e, se a fila estiver vazia, há uma condição de **Underflow** como exceção. Agora, vamos entender alguns exemplos básicos de deque. Pode-se considerar os pacientes do Registro do Hospital e também um exemplo de consumidores que utilizam de ATM. Agora, vamos entender os **tipos de deque**, existem basicamente três tipos de fila de dupla: **Cíclica**, **Duas Direções** e **Prioridade**. Agora, vamos entender a **fila cíclica** em uma fila cíclica, o último elemento aponta para o primeiro elemento para criar uma ligação cíclica. A fila cíclica é também conhecida como buffer de buffer. A inserção é feita na traseira e a remoção é feita na frente, como discutido anteriormente. A seguir, vamos examinar a **duas Direções Fila**. Na duas Direções Fila, tanto a inserção quanto a remoção podem ser feitas tanto na traseira quanto na frente da fila. Por fim, na **fila de prioridade**, as notas possuem prioridade pré-definidas, com a inserção acontecendo conforme a chegada das notas e o nó de menor prioridade será o primeiro nó removido da fila. Agora, vamos examinar um exemplo prático de fila, depois vamos examinar suas aplicações. Pode-se ver um exemplo no meu ecrã, não se preocupe com o código pois este será anexado na caixa de descrição abaixo e você sempre tem acesso a ele e pode executá-lo em seu próprio sistema. Agora, sem nenhum ado a mais, iniciemos a execução do código para que você possa verificar que o código foi executado com sucesso e o terminal está solicitando que você dê uma operação. As primeiras operações possíveis são: - Inserir um elemento na fila - Remover um elemento - Exibir os elementos na fila - Sair Agora, vamos inserir um elemento na fila. Por exemplo, "10". Agora, vamos mostrar os elementos na fila para isso, precisaremos selecionar a opção número 3 e então você vai ver que a fila contém o elemento número 10. Agora, vamos remover este elemento. Com isso, você terá que selecionar a opção número 2 ou de busca. Depois, você vai ver que o elemento foi removido da fila e o elemento removido é 10. A próxima opção é. . . Agora, se você quer sair, você precisará selecionar a opção número 4 pelo próximo tempo que vimos como inserir, visualizar e remover elementos na fila. Não dejemos fazer mais espera e entremos na próxima parte de nosso tópico - aplicações de filas. As aplicações de filas incluem: 1. Busca em largura (BFS) 2. Traversal em árvore 3. Agenda 4. Calendário para agenda de Agendamento de Pacientes 5. Desfazendo operações em programação ## Aplicação de Busca em Largura (BFS) A busca em Largura ou BFS busca em uma árvore binária em uma direção específica no explorador e teste a profundidade a partir de um nó em todas as sub-árvores até atingir a linha limite exigida ou o nó nulo. Ao contrário da busca em profundidade, no BFS, você usa uma fila ou uma união find set (conjunto de conjunto) para ajudar na operação de busca. Vamos fazer um programa na próxima aula. # Aula - 16 | Percurso de Árvore em Estruturas de Dados e Algoritmos | Simplilearn (<https://www.youtube.com/watch?v=JpCt9fvDyJE>) Nesta aula, vamos aprender sobre o percurso de árvore em Estruturas de Dados e Algoritmos. ## Percurso de Árvore O percurso de árvore é um processo de visitar cada e qual node de uma estrutura de dados árvore. Vamos ver um exemplo de árvore simples: ``` A / \ B C / \ D E ``` Neste caso, vamos visitar todos os nodes um após o outro usando os métodos de percurso de árvore. ### Estrutura de Dados Usada para Percurso de Árvore Há principalmente duas estruturas de dados utilizadas para percurso de árvore: 1. Estrutura de Dados Fila 2. Estrutura de Dados Pilha #### Estrutura de Dados Pilha Uma pilha é uma estrutura de dados linear que opera no princípio Last-In-First-Out (LIFO). Existem apenas um apontador na pilha, o apontador do topo, que aponta para o elemento superior da pilha. Só o topo da pilha é utilizado para inserção e exclusão. #### Estrutura de Dados Fila Em contraste com as pilhas, uma fila é uma estrutura de dados que é aberta em ambos os lados. Uma das extremidades é sempre aberta e usada apenas para entrada, enquanto a outra é usada apenas para exclusão ou eliminação. O princípio seguido na fila é o princípio First-In-First-Out (FIFO). ### Tipos de Percurso de Árvore O percurso de árvore pode ser feito da seguinte forma: 1. **Busca em Largura** 2. **Busca em Profundidade** #### Busca em Largura (BFS) A Busca em Largura (BFS) percorre os nodes por nível em vez de sub-árvores. Ela visita primeiro o nó raiz, depois os nodes no próximo nível e assim por diante. Continua até chegar nos folhas dos árvores inteiros. #### Busca em Profundidade (DFS) Na Busca em Profundidade (DFS), nós percorremos a árvore da seguinte forma: 1. **Pré-percurso**: Percorre o nó raiz primeiro, depois o sub-árvore à esquerda e, por último, o sub-árvore à direita. 2. **Em-ordem**: Percorre primeiro o sub-árvore à esquerda, depois o nó raiz e, por último, o sub-árvore à direita. 3. **Pós-percurso**: Percorre primeiro o sub-árvore à esquerda, depois o sub-árvore à direita e, por último, o nó raiz. ### Aplicações do Percurso de Árvore 1. Os árvores de pesquisa binária são utilizadas para verificar rapidamente se um elemento está presente em um conjunto ou não. 2. A variante mais popular da utilização de árvores B-tree em bancos de dados é o árvore B-tree, que é uma variante da estrutura de dados árvore. 3. A versão modificada das árvores chamada de árvores B+ é utilizada em bancos de dados para recuperação de dados eficiente. 4. O compilador utiliza uma árvore sintática para validar a sintaxe de cada programa. --- # Árvores Binárias: Propriedades, Tipos e Operações ## Propriedades de Árvores Binárias - O número máximo de nós em um nível `L` é `2^L`. - O número máximo de nós em uma árvore binária de altura `H` é `2^H - 1`. - A altura mínima em uma árvore binária com `2` nós é `log₂ L + 1`. - A altura mínima em uma árvore binária com `n` nós é `log₂ L +1`. - Uma árvore binária com `L` folhas tem pelo menos `log L base 2 + 1` níveis. ## Tipos de Árvores Binárias - Árvore Binária Completa: Uma árvore única onde um nó pode ter apenas dois filhos ou nenhum filho. - Árvore Binária Totalmente Preenchida: Outra árvore binária específica onde cada nó em todos os níveis, exceto o último, deve ter dois filhos, e, nível mais baixo, todas as folhas podem residir na parte esquerda. - Árvore Binária Perfeita: Uma árvore binária é perfeita se cada nó deve ter dois filhos e todas as folhas estiverem no mesmo nível. - Árvore Binária Balanceada: Uma árvore binária é balanceada se o tamanho dos subárvore das duas subárvores filhas de cada nó não deve variar mais que uma unidade. - Árvore Binária Degenerada: Uma árvore binária é considerada degenerada se cada nó interno tem apenas um filho. ## Operações em Árvores Binárias - Percursação: Os três tipos de operações de percursação possíveis em árvore binária são: - Percorrimento pré-ordenado - Percorrimento em ordem - Percorrimento pós-ordenado ### Exemplo: Percorrimento Em Ordem, Pre-Ordem e Pós-Ordem ```markdown # Percorrimento em ordem 1 2 6 5 3 # Percorrimento pré-ordenado 2 1 3 6 5 # Percorrimento pós-ordenado 1 6 5 3 2 ``` - Inserção: Podemos inserir um nó de maneira a obedecer as regras da árvore binária, isto é, o novo nó deve ser menor que o nó pai se estiver na subárvore à esquerda ou maior se estiver na subárvore à direita. - Exclusão: Excluímos um nó de conformidade com as regras da árvore binária, isto é, o valor da subárvore à esquerda deve ser menor que o valor do nó pai e o valor da subárvore à direita deve ser maior que o valor do nó pai. ## Conclusões Finais - As árvores binárias são mais rápidas nas operações de busca do que outros tipos de árvores. - É mais fácil encontrar o máximo e o mínimo elementos em uma árvore binária. - A árvore binária não permite valores duplicados. - Ela é usada para a realização de DFS em graphs. - É usada para convertê-la a expressões fixas de prefixo do postfix. # Aula - 19 | Grafos em Estruturas de Dados e Algoritmos | Simplilearn URL: [https://www.youtube.com/watch?v=vkdCfbsn3lo](https://www.youtube.com/watch?v=vkdCfbsn3lo) (Idioma: en) ## Introdução breve aos Grafos * Um grapfo é uma estrutura de dados não linear que consiste em conjuntos finitos de vértices e muitos ligações entre eles. * Os vértices são nós presentes em um grapfo. * As ligações são as conexões entre nós. ## Termos de Grafos * Vértices Adjacentes: Se existir uma ligação entre dois vértices, eles são chamados de vértices adjacentes. * Edges Adjacentes: Se existir um vértice em comum entre dois ligações, elas são chamadas de ligações adjacentes. * Grau (em um grapho direcionado): O número de vértices adjacentes a um vértice é chamado de seu grau. Aqui está um exemplo resumido de um grapfo com 5 vértices e as ligações entre eles: ```markdown A --B-- D | C | E ``` * Neste exemplo, A, B, C, D, e E são vértices. * A e B, B e D, D e C são vértices adjacentes desde que estão diretamente conectados. * AB, BD, e DC são ligações adjacentes pois compartilham um vértice (B). * O grau do vértice A, B, C, D, e E é 3, 4, 2, 3, e 1, respectivamente. # Teoria Básica da Teoria de Grafos (Português de Portugal) Uma caminhada é considerada uma sequência de vértices distintos de forma que os vértices consecutivos são adjacentes um ao outro. Em seguida, temos o ciclo. Uma caminhada que possui apenas um único vértice repetido são chamados de vértices inicial e final. Seguindo esta ideia, temos o percurso. O termo percurso é autodecorativo. Uma caminhada é a sequência de vértices e arestas no grafo que é utilizada para percorrer de um vértice para outro. Agora, entramos no próximo segmento desta adjunto tutorial onde vamos discutir os diferentes tipos de autografos disponíveis. ## Grafo Finito Um grafo é considerado finito quando o número de vértices e o número de arestas são de número finito ou contável. ## Grafo Infinito O grafo infinito é o oposto típico do grafo finito. Este grafo não terá um número contável de arestas e de vértices. A imagem abaixo é um exemplo de um grafo típico infinito. ! [Grafo Infinito](URL_TO_THE_IMAGE) ## Grafo Trivial Um grafo é dito trivial se há apenas um único único vértice sem nenhuma aresta. Este tipo de grafo terá apenas um vértice, como um único vértice, e não haverá aresta se você tiver apenas um único vértice, logo não haverá possibilidade de haver uma aresta, a menos se tiver um único laço de sabão tipo de aresta que se conecta a si mesmo, mas neste grafo trivial não temos de se preocupar com isso. Nós simplesmente temos um único vértice. ## Grafo Simples Um grafo é dito simples se há apenas uma única aresta entre cada vértice. Este exemplo pode ser considerado como um grafo simples. Aqui cada conjunto de vértices tem apenas uma única aresta entre eles. ## Grafo Multicamino Se existem várias arestas entre os pares de vértices, então este tipo de grafo é chamado de grafo multicamino. Aqui, você pode ver que temos dois vértices A e B e temos duas arestas conectando-os, não uma mas duas. No entanto, em um grafo simples, deveríamos ter apenas uma aresta conectando os vértices. Isso é a diferença entre um grafo simples e um grafo multicamino. ## Grafo Nulo Um grafo é dito nulo se há apenas vértices e não há arestas entre eles. Lembre-se do grafo trivial? Somos apenas um único vértice, mas não há arestas conectadas. Mas neste caso, temos múltiplos vértices, mas nenhuma isso é chamado de grafo nulo. ## Grafo Completo Um grafo é chamado de grafo completo onde cada vértice deverá ser conectado com todos os outros vértices usando arestas. O significado deste tipo de grafo é que todas as arestas serão conectadas uns aos outros usando pelo menos uma única aresta. Aqui, você pode ver que a tem `a` é conectado a `b`, `d` e `c` todos juntos com pelo menos uma aresta e de forma similar todos os outros vértices serão conectados uns aos outros com pelo menos uma única aresta. ## Grafo Pseudo Um pseudo grafo é um grafo em que no menos um vértice terá uma aresta autoligada tipo. Lembre-se da aresta autoligada ou aresta autoconexada que temos discutido na caminhada grafo tipo então este tipo de âncora que se conecta a si mesmo é chamada de aresta autoligada. Qualquer grafo deverá ter este tipo de âncora, chamado de grafo pseudo. ## Grafo Direcionado Um grafo é chamado de grafo direcionado onde cada aresta tem uma direção associada. Até agora, discutimos são que são conexos, mas não há direção. Mas aqui, você pode ver que esta ligação de aresta tem uma direção que é de B para `a`, não de `A` para `B`. Esta ligação que direciona a traversal do grafo são conhecidas como as ligações direcionadas. ## Grafo Regular Um grafo é um grafo regular onde cada vértice do grafo tem o mesmo grau. Aqui, você pode ver que todos os vértices tem o mesmo grau que é a conexão com todos os outros vértices. O grau de `a` é três, portanto o grau de `B` também é três e o grau de `D` é o mesmo que `C`, e de forma semelhante o grau de `C` é três. Portanto, o que eu quero dizer é que todos estes vértices têm o mesmo grau que três. ## Grafo Pesado Um grafo é Grafo Pesado onde cada aresta tem um peso que indica o custo de três travesuras através das arestas. Aqui você pode ver que todas as arestas têm um peso diferente. A aresta de `A` para `B` tem o peso cinco, a aresta de `a` para `c` tem o peso 'n ', a aresta de `C` para `B` tem dois, a aresta de `a` para `d` é 8 e finalmente `C` para `d` é 7. Portanto, todos esses valores são os pesos ou óptimas para percorrer de um vértice para outro. ## Grafo Conectado Um grafo é conectado onde cada par do grafo é conectado. ## Grafo Desconectado É o oposto típico do grafo conectado. Um grafo é conectado onde cada par do grafo não é conectado. ## Grafo Cíclico Um grafo é dito cíclico se ele contém pelo menos uma ciclo em um grafo composto por uma conexão completa. Aqui você pode perceber que você pode percorrer `A` para `c`, `c` para `d`, `d` para `B` e `B` para `a` novamente, assim é chamado de um grafo cíclico. ## Grafo Acylico Gráfico ao estilo contrário é o cíclico. Neste caso, você não é capaz de retornar de `A` Novamente ao percorrer de `a` em `c`, `c` para `d`, e não há ligação entre `d` para `B`, portanto a rota completa não está disponível. Este tipo de grafo são chamados de grafo acíclico. ## Representação de Grafo Geralmente, existem dois diferentes maneiras de representar a estrutura de dados de grafo: usando uma matriz de adjacência e uma lista de adjacência. ### Matriz de Adjacência A matriz de adjacência é uma representação sequencial usada para representar quais nós estão conectados a quais nós. Se houver uma ligação entre dois vértices, então o valor do elemento correspondente da matriz é um, caso contrário, zero. Se houver um grafo pesado, além de zeros e uns, podemos armazenar o peso da ligação como o número correspondente. ### Lista de Adjacência A lista de adjacência é uma representação ligada. Nesta representação, mantemos a lista de seus vizinhos para cada nó no grafo. Cada nó é o grafo possui a lista de adjacência de seus vizinhos, array de vértices que são indexados por cada número de vértice de cada vértice, a matriz elemento aponta para a lista de vizinhos do vértice. ## Traversal de Grafo O traverse de grafo refere-se à examinação todos os laços e nós em um grafo. O traverse de grafo pode ser realizado de duas maneiras: a primeira é o percorrimento de largura ou o algoritmo BFS, e a segunda é o algoritmo DFS de profundidade. ### Caminhada de Largura (BFS) BFS inicia a traversal do grafo a partir do nó inicial e revela todos os nossos vizinhos. Em seguida, ele seleciona o nó mais perto e explora todos os novos nós. A estrutura de dados do Q pode ser usada no algoritmo BFS. A imagem abaixo mostra o algoritmo BFS funcionando. Ele escolhe o nó mais próximo primeiro, depois percorre-se todos os outros nós. ! [Algoritmo BFS Funcionando](URL_TO_THE_IMAGE) ### Caminhada de Profundidade (DFS) DFS começa a traversal do grafo a partir do nó inicial do grafo e então vai mais profundo e mais profundo até encontrar o nó que não tem filhos, então retorna da morta em direção aos nós recentemente explorados. As estruturas de dados do P können ser usadas para o algoritmo DFS. Destes são algumas das ideias básicas da teoria de grafos. Se você desejar aprender mais sobre o DFS e o algoritmo BFS, consulte as sugestões de vídeos de instrução adicionados na caixa de descrição abaixo. # Troubleshooting rede utilizando o Spanning Tree em Estruturas de Dados e Algoritmos Uma Rede Local (LAN) apresentou problemas devido a um laço de rede formado num sistema de rotas. Para resolver este problema, a equipe utilizou uma Estrutura de Árvore Espelho (Spanning Tree). ## Aulas - 20 | Spanning Tree em Estruturas de Dados e Algoritmos | Simplilearn ([URL](https://www.youtube.com/watch?v=Atxn1xEUQw8)) (Língua: en) ### Entendendo Grafos Conectados e Árvores Espelho 1. **Grafo Não Direcionado**: Em este tipo de grafo, todos os ligações não apontam a uma direção específica. Pode se mover de qualquer nó para qualquer outro nó e voltar atrás também. 2. **Grafo Direcionado**: Em este tipo de grafo, as ligações apontam em uma direção específica. Somente pode se movimentar na direção. 3. **Grafo Conectado**: Um grafo com um caminho a partir de qualquer vértice para qualquer outro vértice no grafo. Pode se chegar a qualquer vértice a partir de qualquer outro vértice em um grafo. ### Básicos e propriedades da Árvore Espelho #### O que é uma Árvore Espelho? Uma Árvore Espelho para qualquer grafo **G** com vértices **V** e ligações **E** pode ser representada usando notação superscript **g-** com vértices **v-** e ligações **e⁰** (as ligações em **g-** devem ser **V-1**). #### Propriedades da Árvore Espelho 1. Um grafo conectado pode ter mais de uma árvore espelho. 2. O número de vértices e árvores devem ser iguais ao número de vértices no grafo fornecido. 3. O número de ligações é igual a **V - 1** mod. 4. A árvore deve não conter nenhuma ciclo. 5. Se o grafo está completo, você pode calcular o número de árvores possíveis usando a fórmula **V ^(V-2)**, onde **V** é o número de vértices fornecido para o grafo. 6. Uma árvore de conexão nunca pode estar desconectada. 7. Se o grafo contém mais de uma iguais pesos, há uma chance de ter mais de uma árvore Mínima Espelho. ### Aplicações da Árvore Espelho 1. **Construção de Rede de Telecomunicações**: Para construir uma rede de telecomunicações para toda a cidade, onde é necessário lidar com um custo enorme, pode-se utilizar a Estrutura de Árvore Mínima (MST). 2. **Técnicas de Aclimação**: A árvore mínima é utilizada nas técnicas de aclimação em conjunto com algoritmos de agrupamento que grupam elementos semelhantes nos dados não classificados e não estruturados. 3. **Segmentação de Imagens**: Para dividir uma imagem em blocos de pixels semelhantes, uma MST é utilizada. 4. **Fatores Financeiros**: A árvore mínima pode ser construída para visualizar as relações entre as ações nos Matrizes de Correlação. ## A solução da equipe para o problema de rede Nesta história, Patrick e sua equipe mudaram a roteadora tradicional para uma roteadora de Árvore Mínima, garantindo custo-efetivo e evitando atividades de laçamento. O sistema de rede atual se beneficia com um menor caminho de rotação, resultando em uma rede sem laços. 