Implementar estruturas de dados e algoritmos em Java: classificação, pesquisa, gráficos

Se você acha que o conteúdo deste blog é útil ou inspirador para você, siga meu blog para obter os artigos técnicos e tutoriais mais recentes o mais rápido possível. Ao mesmo tempo, você também pode deixar uma mensagem na área de comentários para compartilhar suas idéias e sugestões. Obrigado pelo seu apoio!

1. Introdução

Estruturas de dados e algoritmos são dois pilares da ciência da computação e são ferramentas essenciais para resolver vários problemas complexos e otimizar programas de computador. A importância das estruturas de dados e algoritmos reflete-se nos seguintes aspectos:

  1. Melhorar a eficiência do programa : Boas estruturas de dados e algoritmos podem fazer com que os programas sejam executados mais rapidamente e usem os recursos computacionais de forma mais eficiente. Por exemplo, usar o algoritmo quicksort pode reduzir bastante o tempo de classificação do que usar o algoritmo bubble sort.
  2. Resolver problemas complexos : Estruturas de dados e algoritmos podem ajudar os desenvolvedores a resolver vários problemas complexos, como processamento gráfico, inteligência artificial, processamento de big data, etc. Por exemplo, o uso de estruturas de dados e algoritmos apropriados pode permitir que os mecanismos de pesquisa lidem melhor com as consultas dos usuários e retornem os resultados mais relevantes.
  3. Melhore a confiabilidade do programa : Use estruturas de dados e algoritmos corretos para evitar vários erros e exceções quando o programa estiver em execução. Por exemplo, o uso de estruturas de dados ou algoritmos inadequados ao manipular dados pode resultar em corrupção de dados ou falhas no programa.
  4. Organizar e gerenciar melhor os dados : Os dados podem ser melhor organizados e gerenciados com as estruturas de dados corretas. Por exemplo, o uso de uma tabela hash permite que os programas encontrem dados mais rapidamente, enquanto o uso de uma estrutura em árvore permite uma melhor organização dos dados hierárquicos.

Estruturas de dados e algoritmos são ferramentas muito importantes na ciência da computação para melhorar a eficiência do programa, resolver problemas complexos, melhorar a confiabilidade do programa e organizar e gerenciar melhor os dados. O domínio de estruturas de dados e algoritmos é essencial para o desenvolvimento de programas de computador eficientes e de alta qualidade.


2. Algoritmo de classificação

1. Classificação por bolha

A classificação por bolha é um algoritmo básico de classificação. Sua ideia é comparar dois elementos adjacentes. Se o elemento anterior for maior que o último elemento, troque as posições dos dois elementos. Após uma rodada de comparação, o maior elemento será trocado. até o final da matriz. Repita esse processo até que todo o array esteja classificado.

A seguir está o código para implementar a classificação por bolha em Java:

import java.util.Arrays;

public class BubbleSort {
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        // 外层循环控制比较轮数
        for (int i = 0; i < n - 1; i++) {
            // 内层循环控制每轮比较次数
            for (int j = 0; j < n - i - 1; j++) {
                // 如果前一个元素大于后一个元素,则交换位置
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = { 4, 2, 8, 3, 1, 9, 6, 5, 7 };
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

No código acima, definimos um método estático ​bubbleSort​para implementar a classificação por bolhas, que aceita uma matriz inteira como parâmetro e usa dois loops para implementar o processo de classificação por bolhas. No loop externo, controle o número de rodadas de comparação, o número de loops é o comprimento do array menos 1; no loop interno, controle o número de comparações em cada rodada, o número de loops é o comprimento do array menos o número atual de rodadas menos 1. Se o elemento anterior for maior que o último, troque as posições dos dois elementos.

Finalmente ​main​, chame ​bubbleSort​o método no método para classificar uma matriz de inteiros e use ​Arrays.toString​o método para enviar os resultados classificados para o console.

2. Classificação de seleção

A ideia da classificação por seleção é selecionar o menor elemento do array e colocá-lo na frente do array e, em seguida, selecionar o menor elemento dos elementos restantes e colocá-lo no final da parte classificada. Repita esse processo até que todo o array esteja classificado.

A seguir está o código para implementar a classificação por seleção em Java:

import java.util.Arrays;

public class SelectionSort {
    public static void selectionSort(int[] arr) {
        int n = arr.length;
        // 外层循环控制已排序部分的长度
        for (int i = 0; i < n - 1; i++) {
            int minIndex = i;
            // 内层循环从未排序部分中选出最小元素的下标
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            // 将最小元素与已排序部分的末尾元素交换位置
            int temp = arr[minIndex];
            arr[minIndex] = arr[i];
            arr[i] = temp;
        }
    }

    public static void main(String[] args) {
        int[] arr = { 4, 2, 8, 3, 1, 9, 6, 5, 7 };
        selectionSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

No código acima, definimos um método estático ​selectionSort​para implementar a classificação por seleção, que aceita uma matriz inteira como parâmetro e usa dois loops para implementar o processo de classificação por seleção. No loop externo, o comprimento da parte classificada é controlado e o número de loops é o comprimento da matriz menos 1; no loop interno, o subscrito do menor elemento é selecionado da parte não classificada. Se um elemento menor for encontrado, atualize seu subscrito para o subscrito do menor elemento.

Finalmente ​main​, chame ​selectionSort​o método no método para classificar uma matriz de inteiros e use ​Arrays.toString​o método para enviar os resultados classificados para o console.

3. Classificação de inserção

A ideia da classificação por inserção é dividir a matriz em partes classificadas e não classificadas e inserir os elementos não classificados na posição apropriada da parte classificada a cada vez. A operação específica é pegar o primeiro elemento da parte não classificada, compará-lo a partir do último elemento da parte classificada, até que um elemento menor que ele seja encontrado e, em seguida, inserir o elemento na posição apropriada.

A seguir está o código para implementar a classificação por inserção em Java:

import java.util.Arrays;

public class InsertionSort {
    public static void insertionSort(int[] arr) {
        int n = arr.length;
        // 外层循环控制已排序部分的长度
        for (int i = 1; i < n; i++) {
            int cur = arr[i];
            int j = i - 1;
            // 内层循环将待排序元素插入到已排序部分的适当位置
            while (j >= 0 && arr[j] > cur) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = cur;
        }
    }

    public static void main(String[] args) {
        int[] arr = { 4, 2, 8, 3, 1, 9, 6, 5, 7 };
        insertionSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

No código acima, definimos um método estático ​insertionSort​para implementar a classificação por inserção, que aceita uma matriz inteira como parâmetro e usa dois loops para implementar o processo de classificação por inserção. No loop externo, o comprimento da parte classificada é controlado e o número de loops é o comprimento da matriz menos 1; no loop interno, os elementos a serem classificados são inseridos nas posições apropriadas da parte classificada. Compare os elementos a serem classificados com os elementos na parte classificada de trás para frente. Se o elemento na parte classificada for maior que o elemento a ser classificado, mova o elemento para trás uma posição até a posição correta do elemento a ser classificado. classificado é encontrado.

Finalmente ​main​, chame ​insertionSort​o método no método para classificar uma matriz de inteiros e use ​Arrays.toString​o método para enviar os resultados classificados para o console.

4. Classificação rápida

A classificação rápida é um algoritmo de classificação de divisão e conquista. Sua ideia central é dividir o array em duas partes, uma parte menor que a outra, e então classificar rapidamente as duas partes separadamente e, finalmente, mesclá-las. A operação específica é selecionar um elemento de referência, dividir a matriz em duas partes, os elementos à esquerda são menores que o elemento de referência e os elementos à direita são maiores que o elemento de referência e, em seguida, executar recursivamente a classificação rápida no partes esquerda e direita.

A classificação rápida é um algoritmo de classificação eficiente. Sua ideia básica é selecionar um elemento de referência e dividir a parte a ser classificada em subsequências esquerda e direita, de modo que os elementos na subsequência esquerda sejam todos menores que o elemento de referência, e os elementos em a subsequência direita é menor que o elemento de referência.Maior ou igual ao elemento base. Em seguida, as subsequências esquerda e direita são rapidamente classificadas recursivamente até que toda a sequência seja classificada.

A seguir está o código para implementar o quicksort em Java:

import java.util.Arrays;

public class QuickSort {
    public static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            // 找到基准元素的位置
            int pivotIndex = partition(arr, left, right);
            // 对左右子序列递归地进行快速排序
            quickSort(arr, left, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, right);
        }
    }

    private static int partition(int[] arr, int left, int right) {
        // 将第一个元素作为基准元素
        int pivot = arr[left];
        int i = left + 1;
        int j = right;
        while (true) {
            // 从左往右找到第一个大于等于基准元素的位置
            while (i <= j && arr[i] < pivot) {
                i++;
            }
            // 从右往左找到第一个小于基准元素的位置
            while (i <= j && arr[j] >= pivot) {
                j--;
            }
            if (i >= j) {
                // 如果i >= j,则说明已经将数组分成了左右两个子序列
                // 左子序列中的元素都小于基准元素,右子序列中的元素都大于等于基准元素
                break;
            }
            // 交换arr[i]和arr[j]的位置,将小于等于基准元素的元素放到左子序列中
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
            // 继续在左子序列中找大于等于基准元素的元素
            i++;
            // 在右子序列中找小于基准元素的元素
            j--;
        }
        // 将基准元素放到正确的位置,即i-1的位置
        arr[left] = arr[i - 1];
        arr[i - 1] = pivot;
        // 返回基准元素的位置
        return i - 1;
    }

    public static void main(String[] args) {
        int[] arr = { 4, 2, 8, 3, 1, 9, 6, 5, 7 };
        quickSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }
}

No código acima, definimos um método estático ​quickSort​para implementar a classificação rápida, que aceita uma matriz inteira, limite esquerdo e limite direito como parâmetros e implementa o processo de classificação rápida recursivamente. Em cada rodada de classificação rápida, primeiro selecionamos um elemento de referência e, em seguida, usamos dois ponteiros i e j para apontar para as extremidades esquerda e direita, respectivamente. Em seguida, percorremos o array da esquerda para a direita para encontrar a posição i do primeiro elemento maior ou igual ao elemento de referência e, em seguida, percorremos o array da direita para a esquerda para encontrar a posição j do primeiro elemento menor que a referência elemento. Se i e j não se encontrarem, troque as posições de arr[i] e arr[j] e coloque os elementos menores ou iguais ao elemento de referência na subsequência esquerda. Continue a encontrar elementos maiores ou iguais ao elemento de referência na subsequência esquerda até i >= j, indicando que a matriz foi dividida em subsequências esquerda e direita. Em seguida, coloque o elemento de referência na posição correta, ou seja, a posição de i-1, os elementos da subsequência esquerda são todos menores que o elemento de referência e os elementos da subsequência direita são maiores ou iguais à referência elemento.

A complexidade de tempo do quicksort é O(nlogn), e a complexidade de tempo no pior caso é O(n^2), mas esta situação é relativamente rara. Sua complexidade de espaço é O(logn) e é um algoritmo de classificação local que não requer espaço adicional.

Aqui está um exemplo de saída de classificação rápida de uma matriz inteira usando o código acima:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

5. Mesclar classificação

Merge sort é um algoritmo de classificação baseado na ideia de dividir e conquistar. Sua ideia central é decompor um grande problema em vários pequenos problemas para resolver e, em seguida, combinar as soluções dos pequenos problemas para obter a solução do problema original . A ideia básica da classificação por mesclagem é dividir a sequência a ser classificada em várias subsequências, cada uma delas em ordem, e então mesclar as subsequências para obter uma sequência completamente ordenada.

A classificação por mesclagem é adequada para classificar dados em grande escala e sua complexidade de tempo é estável em O (nlogn). Sua principal vantagem é que possui boa estabilidade e pode lidar com dados em grande escala, mas necessita de espaço adicional para armazenar os resultados intermediários gerados na divisão e conquista.

A seguir está um exemplo de código para implementar a classificação por mesclagem em Java:

import java.util.Arrays;

public class MergeSort {

    public static void mergeSort(int[] arr) {
        int[] temp = new int[arr.length];
        sort(arr, 0, arr.length - 1, temp);
    }

    private static void sort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;
            sort(arr, left, mid, temp);
            sort(arr, mid + 1, right, temp);
            merge(arr, left, mid, right, temp);
        }
    }

    private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;
        int j = mid + 1;
        int k = 0;
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }
        while (i <= mid) {
            temp[k++] = arr[i++];
        }
        while (j <= right) {
            temp[k++] = arr[j++];
        }
        k = 0;
        while (left <= right) {
            arr[left++] = temp[k++];
        }
    }

    public static void main(String[] args) {
        int[] arr = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
        mergeSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

Na classificação por mesclagem, a etapa mais importante é mesclar duas subsequências classificadas. Quando mesclamos duas subsequências ordenadas, podemos usar a travessia de ponteiro duplo para colocar os elementos nas duas subsequências em uma matriz temporária em ordem de tamanho até que todos os elementos de uma das subsequências sejam colocados no temporário da matriz. Neste ponto, os elementos da outra subsequência devem ser maiores que os elementos do array temporário (porque estão ordenados), para que possamos colocar esses elementos diretamente no final do array temporário.

Finalmente, precisamos apenas copiar os elementos do array temporário de volta para o array original, completando assim todo o processo de classificação por mesclagem.

Em aplicações práticas, a classificação por mesclagem é frequentemente usada como um algoritmo de classificação interna. Por exemplo, o método Arrays.sort em Java usa classificação por mesclagem.

6. Classificação de pilha

A classificação de heap é um algoritmo de classificação baseado na estrutura de dados de heap. Sua ideia é primeiro construir a matriz a ser classificada em um heap binário e, em seguida, retirar os elementos superiores do heap (ou seja, o maior ou o menor elemento) e coloque-os na parte classificada e reajuste a estrutura do heap dos elementos restantes para atender às propriedades do heap. Repita este processo até que todos os elementos estejam classificados.

A classificação de heap pode ser dividida em duas etapas:

  1. Construir um heap: Construa o array a ser classificado em um heap binário. A operação específica é começar a partir do último nó não-folha da matriz e compará-lo com seus nós filhos.Se a natureza do heap não for satisfeita, troque as posições dos dois nós, depois avance e continue a comparar até o nó raiz.
  2. Classificação: Retire os elementos superiores do heap um por um, coloque-os no final da parte classificada e, em seguida, reajuste a estrutura do heap dos elementos restantes para atender às propriedades do heap. A operação específica é trocar a posição do elemento superior do heap pelo último elemento do heap, reduzir o tamanho do heap em 1 e, em seguida, executar uma operação de filtro no elemento superior do heap para movê-lo para uma posição adequada para que atenda às propriedades da pilha.

A complexidade de tempo da classificação de heap é O(nlogn) e a complexidade de espaço é O(1).É um algoritmo de classificação local que não requer espaço adicional para armazenar dados. A classificação de heap é adequada para cenários onde uma grande quantidade de dados precisa ser classificada e a complexidade do espaço é limitada.No entanto, devido à grande constante de classificação de heap, em comparação com algoritmos como classificação rápida, a eficiência de dados de pequena escala pode não ser tão bom quanto outros algoritmos.

Aqui está um exemplo de classificação de heap implementada em Java:

import java.util.Arrays;

public class HeapSort {
    public static void sort(int[] arr) {
        int n = arr.length;
        
        // 构建堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 堆排序
        for (int i = n - 1; i >= 0; i--) {
            // 将堆顶元素与末尾元素交换
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;

            // 对剩余元素重新构建堆
            heapify(arr, i, 0);
        }
    }

    // 堆化操作
    private static void heapify(int[] arr, int n, int i) {
        int largest = i; // 初始化最大值为根节点
        int l = 2 * i + 1; // 左孩子节点
        int r = 2 * i + 2; // 右孩子节点

        // 找出三个节点中的最大值
        if (l < n && arr[l] > arr[largest]) {
            largest = l;
        }
        if (r < n && arr[r] > arr[largest]) {
            largest = r;
        }

        // 如果最大值不是根节点,则交换根节点和最大值,并继续向下堆化
        if (largest != i) {
            int temp = arr[i];
            arr[i] = arr[largest];
            arr[largest] = temp;
            heapify(arr, n, largest);
        }
    }
    
    public static void main(String[] args) {
        int[] arr = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

Neste exemplo, primeiro usamos ​heapify​o método para construir o array em um heap máximo e, em seguida, trocamos o elemento superior e o elemento final do array em cada rodada de classificação e reconstruímos o heap. Finalmente, podemos obter um array ordenado. Como a complexidade de tempo da classificação de heap é O (nlogn), é um algoritmo de classificação muito eficiente e frequentemente usado para classificar dados em grande escala.

7. Resumo

A classificação por bolha e a classificação por seleção são os algoritmos de classificação mais simples, embora tenham alta complexidade de tempo, são fáceis de entender e implementar. A complexidade de tempo da classificação por inserção é ligeiramente menor do que a classificação por bolha e classificação por seleção, mas devido à sua estabilidade e melhor desempenho, é mais comum em aplicações práticas.

A classificação rápida e a classificação por mesclagem são algoritmos de classificação mais eficientes, sua complexidade de tempo é O (nlogn), portanto, eles têm uma vantagem ao lidar com dados em grande escala. A classificação rápida geralmente é mais rápida que a classificação por mesclagem, mas é menos estável. Por outro lado, a classificação por mesclagem tem melhor estabilidade e é adequada para mais cenários.

A classificação de heap é um algoritmo de classificação com complexidade de tempo de O (nlogn), seu desempenho é melhor do que a classificação por bolha, classificação por seleção e classificação por inserção, mas é frequentemente substituído por classificação por mesclagem e classificação rápida em aplicações práticas.

Em aplicações práticas, precisamos escolher diferentes algoritmos de classificação de acordo com problemas específicos e características dos dados. Para dados de pequena escala, podemos usar algoritmos de classificação simples, como classificação por bolha, classificação por seleção e classificação por inserção; para dados de grande escala, podemos usar algoritmos de classificação mais eficientes, como classificação rápida, classificação por mesclagem e classificação por heap.


3. Algoritmo de pesquisa

1. Pesquisa linear

A pesquisa linear, também conhecida como pesquisa sequencial, é um algoritmo de pesquisa básico. Sua ideia é pesquisar para trás, um por um, a partir do primeiro elemento do conjunto de dados até que o elemento alvo seja encontrado ou todo o conjunto de dados seja percorrido. Por ser comparado um por um, é aplicável a vários tipos de dados, incluindo conjuntos de dados não ordenados e ordenados.

A complexidade de tempo da pesquisa linear é O(n), onde n representa o tamanho do conjunto de dados e, na pior das hipóteses, todo o conjunto de dados precisa ser pesquisado. No entanto, se o conjunto de dados for ordenado, o número de comparações pode ser reduzido otimizando o salto para fora do loop, e a complexidade do tempo pode atingir O(1).

A seguir está um exemplo de código para implementar pesquisa linear em Java:

public class LinearSearch {
    public static int linearSearch(int[] arr, int target) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == target) {
                return i;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        int target = 3;
        int index = linearSearch(arr, target);
        if (index != -1) {
            System.out.println("目标元素 " + target + " 在数组中的位置为:" + index);
        } else {
            System.out.println("目标元素 " + target + " 不在数组中");
        }
    }
}

O código acima implementa uma função de pesquisa linear ​linearSearch​simples e ​main​demonstra como usar essa função para encontrar elementos no array da função.

2. Pesquisa binária

A pesquisa binária, também conhecida como pesquisa binária, é um algoritmo de pesquisa comumente usado. Sua ideia é reduzir pela metade o intervalo a ser pesquisado em cada processo de busca por um conjunto de dados ordenado, até que o elemento alvo seja encontrado ou o intervalo a ser pesquisado esteja vazio.

A complexidade de tempo da pesquisa binária é O(log n), onde n é o tamanho do conjunto de dados. Como cada pesquisa reduzirá pela metade o intervalo a ser pesquisado, sua eficiência é muito maior do que a pesquisa linear, especialmente para grandes conjuntos de dados ordenados. Mas as suas limitações também são óbvias, aplicáveis ​​apenas a conjuntos de dados ordenados.

A seguir está um exemplo de código para implementar a pesquisa binária em Java:

public class BinarySearch {
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        int target = 3;
        int index = binarySearch(arr, target);
        if (index != -1) {
            System.out.println("目标元素 " + target + " 在数组中的位置为:" + index);
        } else {
            System.out.println("目标元素 " + target + " 不在数组中");
        }
    }
}

O código acima implementa uma função de pesquisa binária ​binarySearch​simples e ​main​demonstra como usar essa função para encontrar elementos no array na função.

3. Pesquisa de interpolação

A pesquisa de interpolação é uma variante da pesquisa binária que também funciona em conjuntos de dados ordenados. Diferente da pesquisa binária, a pesquisa por interpolação seleciona o intervalo de pesquisa de acordo com a posição estimada do valor alvo, em vez de selecionar diretamente a posição intermediária.

Especificamente, a ideia básica do algoritmo de busca por interpolação é comparar o valor alvo com os dois pontos finais do intervalo de busca e, em seguida, calcular uma posição estimada com base na posição relativa do valor alvo em todo o conjunto de dados. Então, de acordo com a posição estimada, o intervalo de busca é reduzido até que o valor alvo seja encontrado.

A complexidade de tempo do algoritmo de busca por interpolação é O(logn), mas para conjuntos de dados com distribuição de dados relativamente uniforme, a eficiência da busca por interpolação pode ser maior do que a da busca binária. No entanto, para distribuições de dados extremas, o desempenho das pesquisas de interpolação pode ser prejudicado.

A seguir está um exemplo de implementação de código Java para pesquisa de interpolação:

public class InterpolationSearch {
    public static int interpolationSearch(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        while (left <= right && target >= arr[left] && target <= arr[right]) {
            int pos = left + ((target - arr[left]) * (right - left)) / (arr[right] - arr[left]);
            if (arr[pos] == target) {
                return pos;
            }
            if (arr[pos] < target) {
                left = pos + 1;
            } else {
                right = pos - 1;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        int target = 5;
        int index = interpolationSearch(arr, target);
        if (index != -1) {
            System.out.println("目标元素 " + target + " 在数组中的位置为:" + index);
        } else {
            System.out.println("目标元素 " + target + " 不在数组中");
        }
    }
}

Neste exemplo, primeiro calculamos a posição estimada pos e, em seguida, restringimos o intervalo de pesquisa de acordo com a relação de tamanho entre pos e alvo. Especificamente, se arr[pos] < alvo, à esquerda é atribuído pos + 1; se arr[pos] > alvo, à direita é atribuído pos - 1. Se arr[pos] = alvo, retorne pos.

4. Pesquisa Fibonacci

A pesquisa de Fibonacci é um algoritmo de pesquisa baseado no ponto da seção áurea, que pode ser usado para pesquisar conjuntos de dados ordenados. Semelhante à pesquisa binária, a pesquisa Fibonacci também compara o valor alvo com o ponto médio do intervalo de pesquisa e, em seguida, restringe o intervalo de pesquisa de acordo com o resultado da comparação.

A diferença é que a busca de Fibonacci utiliza o conceito de ponto da seção áurea, ou seja, o intervalo de busca é dividido em dois subintervalos de acordo com uma determinada proporção. Especificamente, assumindo que o comprimento do intervalo de pesquisa é n, primeiro encontramos um número f que é maior que n e mais próximo de n na sequência de Fibonacci e, em seguida, dividimos o intervalo de pesquisa em dois subintervalos com comprimentos f e f-1. Em seguida, compare o valor alvo com o ponto médio dos dois subintervalos e, em seguida, restrinja ainda mais o intervalo de pesquisa de acordo com o resultado da comparação.

A complexidade de tempo do algoritmo de busca de Fibonacci é O(logn), mas comparado à busca binária, requer espaço adicional para armazenar a sequência de Fibonacci.

A pesquisa de Fibonacci é adequada para conjuntos de dados ordenados e o comprimento do conjunto de dados é incerto ou a distribuição do conjunto de dados é relativamente uniforme. Como a taxa de crescimento da sequência de Fibonacci é mais rápida que a da busca binária, a eficiência da busca de Fibonacci pode ser maior quando o comprimento do intervalo de busca é relativamente grande.

A seguir está um exemplo de implementação de código Java da pesquisa Fibonacci:

public class FibonacciSearch {
    public static int fibonacciSearch(int[] arr, int target) {
        int n = arr.length;
        int fibMinus2 = 0, fibMinus1 = 1, fib = fibMinus2 + fibMinus1;
        while (fib < n) {
            fibMinus2 = fibMinus1;
            fibMinus1 = fib;
            fib = fibMinus2 + fibMinus1;
        }
        int offset = -1;
        while (fib > 1) {
            int i = Math.min(offset + fibMinus2, n - 1);
            if (arr[i] < target) {
                fib = fibMinus1;
                fibMinus1 = fibMinus2;
                fibMinus2 = fib - fibMinus1;
                offset = i;
            } else if (arr[i] > target) {
                fib = fibMinus2;
                fibMinus1 -= fibMinus2;
                fibMinus2 = fib - fibMinus1;
            } else {
                return i;
            }
        }
        if (fibMinus1 == 1 && arr[offset + 1] == target) {
            return offset + 1;
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 11, 13, 15};
        int target = 7;
        int index = fibonacciSearch(arr, target);
        if (index != -1) {
            System.out.println("目标元素" + target + "的下标为" + index);
        } else {
            System.out.println("未找到目标元素" + target);
        }
    }
}

Neste exemplo, primeiro usamos a sequência de Fibonacci para calcular a duração do intervalo de pesquisa e, em seguida, encontramos o valor alvo no intervalo de pesquisa. Especificamente, primeiro encontramos o número de Fibonacci f que é maior que o valor alvo e mais próximo do comprimento do intervalo de pesquisa e, em seguida, dividimos o intervalo de pesquisa em dois subintervalos com comprimentos f e f-1. Em seguida, use o loop while para pesquisar iterativamente até que o valor alvo seja encontrado ou o comprimento do intervalo de pesquisa seja 1 e a pesquisa seja interrompida.

Em cada iteração, calculamos o índice i do ponto intermediário e, em seguida, comparamos o valor alvo com arr[i]. Se o valor alvo for menor que arr[i], restrinja o intervalo de pesquisa ao subintervalo esquerdo, caso contrário, restrinja o intervalo de pesquisa ao subintervalo direito. Finalmente, quando o comprimento do intervalo de pesquisa for 1, se arr[offset+1] for igual ao valor alvo, offset+1 será retornado, caso contrário -1 será retornado para indicar que a pesquisa falhou.

Aqui está um exemplo de uso do algoritmo de pesquisa Fibonacci para encontrar elementos em uma matriz classificada:

int[] arr = {1, 3, 5, 7, 9, 11, 13, 15};
int target = 7;
int index = fibonacciSearch(arr, target);
if (index != -1) {
    System.out.println("目标元素" + target + "的下标为" + index);
} else {
    System.out.println("未找到目标元素" + target);
}

A saída é:

Pode-se observar que o uso do algoritmo de busca Fibonacci pode encontrar rapidamente o subscrito do elemento alvo na matriz ordenada.

5. Pesquisa de hash

A pesquisa de hash, também conhecida como pesquisa de tabela hash, é um algoritmo de pesquisa baseado em tabelas hash. Na tabela hash, mapeamos a chave para um local fixo, chamado endereço hash, para que o elemento alvo possa ser encontrado rapidamente dentro da complexidade de tempo de O(1).

A pesquisa de hash possui uma ampla variedade de cenários de aplicação, como índices em bancos de dados, tabelas de símbolos em compiladores e sistemas de cache. Como a tabela hash tem características de pesquisa, inserção e exclusão rápidas, em cenários que exigem operações frequentes de dados, o uso da pesquisa hash pode melhorar muito o desempenho do programa.

A complexidade de tempo da pesquisa de hash é O(1), mas a eficiência da pesquisa pode ser afetada ao lidar com colisões de hash. Os métodos comuns para resolver conflitos de hash incluem método de endereço em cadeia, método de endereço aberto, etc.

Aqui está um exemplo de uso do algoritmo de pesquisa de hash para encontrar um elemento em uma matriz:

import java.util.HashMap;
import java.util.Map;

public class HashSearch {
    public static int hashSearch(int[] arr, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < arr.length; i++) {
            map.put(arr[i], i);
        }
        if (map.containsKey(target)) {
            return map.get(target);
        } else {
            return -1;
        }
    }
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        int target = 5;
        int index = hashSearch(arr, target);
        if (index != -1) {
            System.out.println("目标元素 " + target + " 在数组中的位置为:" + index);
        } else {
            System.out.println("目标元素 " + target + " 不在数组中");
        }
    }
}

No código acima, usamos HashMap em Java para implementar uma tabela hash e usamos os elementos do array como chaves e os subscritos dos elementos como valores. Então, só precisamos procurar o elemento de destino na tabela hash e retornar seu subscrito, se existir; caso contrário, retornar -1 para indicar que a pesquisa falhou.

Deve-se notar que ao usar uma tabela hash para implementar a pesquisa de hash, precisamos escolher uma função hash apropriada para garantir que os elementos sejam distribuídos uniformemente na tabela hash, de modo a evitar a ocorrência de colisões de hash.

6. Pesquisa em árvore

As pesquisas de árvore comuns incluem árvores de pesquisa binária, árvores binárias balanceadas, árvores B, árvores B+ e árvores rubro-negras. Todas essas estruturas em árvore têm bom desempenho de pesquisa e são adequadas para diferentes cenários de aplicação.

  • Árvore de pesquisa binária (BST): É uma árvore binária especial, que satisfaz que a subárvore esquerda de qualquer nó seja menor que o nó e a subárvore direita seja maior que o nó. Em uma árvore de pesquisa binária, a complexidade de tempo de pesquisa, inserção, exclusão e outras operações é O(log n).
  • Árvore binária balanceada: como árvore AVL, árvore vermelha e preta, etc., eles podem garantir que a diferença de altura entre os lados esquerdo e direito da árvore não exceda 1, garantindo assim que a complexidade de tempo de operações como pesquisa, inserção e exclusão é O (log n).
  • Árvore B: É uma árvore de pesquisa balanceada multidirecional, cada nó possui vários nós filhos e pode armazenar vários itens de dados. As árvores B são geralmente usadas em armazenamento externo, como discos, porque seus nós podem armazenar vários itens de dados, o que pode efetivamente reduzir o número de operações de E/S do disco e melhorar a velocidade de leitura de dados.
  • Árvore B+: É uma melhoria baseada na árvore B. Comparado com a árvore B, os nós internos da árvore B+ não armazenam dados, apenas armazenam informações de índice, e todos os dados são armazenados nos nós folha. As árvores B+ também são comumente usadas em armazenamento externo, que pode usar o espaço em disco com mais eficiência.
  • Árvore vermelho-preto: É uma árvore de pesquisa binária com auto-equilíbrio, garantindo que a altura da árvore seja aproximadamente equilibrada colorindo os nós, garantindo assim que a complexidade de tempo de operações como pesquisa, inserção e exclusão seja O( registro n). Árvores vermelho-pretas são frequentemente usadas em estruturas de dados integradas em linguagens de programação, como set e map em C++ STL.

árvore de pesquisa binária

Uma estrutura de dados comumente usada é uma árvore de pesquisa binária, que é uma árvore binária na qual cada nó contém uma chave e um valor correspondente. Para cada nó, os valores-chave de todos os nós em sua subárvore esquerda são menores que o valor-chave do nó, e os valores-chave de todos os nós na subárvore direita são maiores que o valor-chave do nó. Desta forma, podemos usar uma árvore de pesquisa binária para localizar, inserir e excluir elementos rapidamente.

A complexidade de tempo da árvore de pesquisa binária está relacionada à altura da árvore. Na pior das hipóteses, ela pode degenerar em uma lista vinculada e a complexidade de tempo se torna O(n). Portanto, para evitar esta situação, podemos usar uma árvore binária balanceada ou outras estruturas de dados mais avançadas em vez de uma árvore binária de busca.

Aqui está um exemplo de árvore de pesquisa binária implementada em Java:

class Node {
    int key;
    int value;
    Node left;
    Node right;

    public Node(int key, int value) {
        this.key = key;
        this.value = value;
        this.left = null;
        this.right = null;
    }
}

class BST {
    private Node root;

    public BST() {
        root = null;
    }

    public int get(int key) {
        Node node = get(root, key);
        return node == null ? -1 : node.value;
    }

    private Node get(Node node, int key) {
        if (node == null) {
            return null;
        }
        if (node.key == key) {
            return node;
        } else if (node.key > key) {
            return get(node.left, key);
        } else {
            return get(node.right, key);
        }
    }

    public void put(int key, int value) {
        root = put(root, key, value);
    }

    private Node put(Node node, int key, int value) {
        if (node == null) {
            return new Node(key, value);
        }
        if (node.key == key) {
            node.value = value;
        } else if (node.key > key) {
            node.left = put(node.left, key, value);
        } else {
            node.right = put(node.right, key, value);
        }
        return node;
    }
}

Em aplicações práticas, precisamos escolher diferentes algoritmos de busca de acordo com as características dos dados e necessidades específicas. Para conjuntos de dados ordenados, a pesquisa binária e a pesquisa em árvore são escolhas mais comuns; para conjuntos de dados não ordenados, a pesquisa linear e a pesquisa de hash são escolhas mais comuns. Além disso, pesquisas de hash e pesquisas de árvore geralmente têm melhor desempenho ao lidar com dados em grande escala.


4. Representação e percurso de gráficos

1. Conceitos básicos e definições de gráficos

Um gráfico é uma estrutura de dados composta por nós (vértice) e arestas (aresta) e geralmente é usado para descrever o relacionamento entre as coisas. Um gráfico pode ser representado por G=(V,E), onde V representa o conjunto de nós e E representa o conjunto de arestas. Cada aresta conecta dois nós, que podem ser o mesmo nó (auto-loop) ou nós diferentes (não-auto-loop).

Existem dois tipos de gráficos: gráficos direcionados e gráficos não direcionados. Uma aresta em um grafo direcionado tem uma direção, indicando que aponta de um nó para outro, enquanto uma aresta em um grafo não direcionado não tem direção, indicando que o relacionamento entre dois nós é mútuo.

Os nós do gráfico podem ter pesos, indicando que o relacionamento entre os nós é forte ou fraco.Por exemplo, se os usuários de uma rede social têm um relacionamento de amizade, o número de amigos é o peso.

Existem alguns conceitos básicos na figura, incluindo grau, caminho, conectividade, árvore geradora, etc.

  • Grau: O grau de um nó refere-se ao número de arestas conectadas ao nó.
  • Caminho: Um caminho é uma sequência de nós conectados por arestas, e o comprimento de um caminho se refere ao número de arestas no caminho.
  • Conectividade: Dois nós em um grafo não direcionado são conectados se houver um caminho conectando-os. Dois nós em um grafo direcionado são conectados se puderem alcançar outro nó.
  • Árvore geradora: A árvore geradora de um grafo não direcionado refere-se a um subgrafo conectado do grafo, que contém todos os nós do grafo, mas não contém nenhum ciclo (ciclo) e possui o menor número de arestas. Para gráficos direcionados, a definição de uma árvore geradora requer uma distinção entre grau de entrada e grau de saída.

Os gráficos têm uma ampla gama de aplicações em ciência da computação, incluindo topologia de rede, processamento de imagens, processamento de linguagem natural e muito mais.

2. O método de representação do gráfico

Os gráficos podem ser representados de várias maneiras, e os três métodos de representação comuns são os seguintes:

  1. Matriz de Adjacência: Use uma matriz bidimensional para representar os nós e arestas no gráfico, onde as linhas e colunas da matriz representam nós. Se houver uma aresta entre dois nós, ela será marcada como 1 ou 1 na linha correspondente e pesos de borda da coluna. Se não houver aresta entre dois nós, ela será marcada como 0 na posição correspondente.
  2. Lista de Adjacência (Lista de Adjacência): Use uma matriz para representar todos os nós, cada nó corresponde a uma lista vinculada, e a lista vinculada armazena outros nós conectados ao nó e o peso da aresta correspondente.
  3. Matriz de Incidência: Uma matriz bidimensional é usada para representar os nós e arestas no gráfico, onde as linhas da matriz representam nós e as colunas representam arestas. Se houver associação entre o nó e a aresta, a posição correspondente é marcada como 1 ou o peso da aresta, caso contrário é marcado como 0.

Em aplicações práticas, a escolha de diferentes métodos de representação afetará a complexidade de tempo e espaço do algoritmo. Por exemplo, a matriz de adjacência é adequada para representar grafos com arestas densas, mas a complexidade do espaço é alta; a lista de adjacência é adequada para representar grafos com arestas esparsas, mas a complexidade de tempo de acesso aos nós é alta.

Matriz de adjacência

A matriz de adjacência é um método comum de representação de um gráfico, que usa uma matriz bidimensional para representar o relacionamento de conexão entre todos os nós. A linha i e a coluna j da matriz de adjacência são 1 se houver uma aresta entre o nó i e o nó j; caso contrário, é 0.

Matrizes de adjacência são adequadas para grafos densos, ou seja, grafos onde o número de arestas é próximo ao número de nós. Como cada aresta precisa ocupar um elemento de matriz, para um grafo esparso com um pequeno número de arestas, usar uma matriz de adjacência causará muito desperdício de espaço. Além disso, a matriz de adjacência não pode suportar bem a adição e exclusão dinâmica de arestas no gráfico, porque o tamanho da matriz precisa ser reajustado ao adicionar e excluir arestas, resultando em alta complexidade de tempo.

As vantagens de uma matriz de adjacência são:

  • Pode determinar rapidamente se existe uma conexão de borda entre dois nós;
  • Nós de acesso aleatório que suportam tempo constante.

Aqui está um exemplo de código Java para um gráfico direcionado representado por uma matriz de adjacência:

public class DirectedGraph {
    private int V; // 图中节点的数量
    private int[][] adjMatrix; // 邻接矩阵

    public DirectedGraph(int V) {
        this.V = V;
        this.adjMatrix = new int[V][V];
    }

    // 添加一条边
    public void addEdge(int source, int destination) {
        adjMatrix[source][destination] = 1;
    }

    // 打印邻接矩阵
    public void printAdjMatrix() {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                System.out.print(adjMatrix[i][j] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        DirectedGraph graph = new DirectedGraph(4);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(1, 2);
        graph.addEdge(2, 0);
        graph.addEdge(2, 3);
        graph.addEdge(3, 3);

        graph.printAdjMatrix();
    }
}

A execução do código acima produzirá a seguinte matriz de adjacência:

0 1 1 0 
0 0 1 0 
1 0 0 1 
0 0 0 1

O gráfico direcionado representado pela matriz de adjacência contém quatro nós, a saber, 0, 1, 2 e 3. Entre eles, o nó 0 possui duas arestas de saída, apontando para o nó 1 e o nó 2, respectivamente; o nó 1 possui uma aresta de saída apontando para o nó 2; o nó 2 possui duas arestas de saída, apontando para o nó 0 e o nó 3, respectivamente; o nó 3 não possui nenhuma aresta de saída. borda de saída.

Lista de Adjacências

A lista de adjacência é outro método de representação comum de gráfico, que usa uma lista vinculada para representar os nós vizinhos de cada nó no gráfico. Cada nó possui uma lista vinculada, que contém todos os nós vizinhos do nó.

A seguir está a implementação do código Java da lista de adjacências:

Primeiro defina uma classe de nó de lista de adjacências ​AdjListNode​, que contém um número de nó ​v​e um ponteiro para o próximo nó vizinho ​next​.

class AdjListNode {
    int v;
    AdjListNode next;

    public AdjListNode(int v) {
        this.v = v;
        next = null;
    }
}

Em seguida, defina uma classe de lista de adjacências ​AdjList​, que contém um array ​adjLists​, cada elemento do array é um nó da lista de adjacências, representando um nó no grafo e seus nós vizinhos.

class AdjList {
    AdjListNode[] adjLists;

    public AdjList(int numVertices) {
        adjLists = new AdjListNode[numVertices];

        for (int i = 0; i < numVertices; i++) {
            adjLists[i] = new AdjListNode(i);
        }
    }

    // 添加一条边,连接节点u和节点v
    public void addEdge(int u, int v) {
        AdjListNode newNode = new AdjListNode(v);
        newNode.next = adjLists[u].next;
        adjLists[u].next = newNode;
    }
}

Um exemplo de representação de um gráfico não direcionado usando uma lista de adjacências:

0
     / 
    1 - 2
   / \   \
  3   4   5

Código Java correspondente:

// 创建一个有6个节点的邻接表
AdjList adjList = new AdjList(6);

// 添加边
adjList.addEdge(0, 1);
adjList.addEdge(1, 0);
adjList.addEdge(1, 2);
adjList.addEdge(2, 1);
adjList.addEdge(1, 3);
adjList.addEdge(3, 1);
adjList.addEdge(1, 4);
adjList.addEdge(4, 1);
adjList.addEdge(2, 5);
adjList.addEdge(5, 2);

A vantagem da lista de adjacência é que ela pode representar um grafo esparso com menos espaço, mas sua desvantagem é que ela precisa percorrer a lista vinculada ao procurar todos os nós vizinhos de um nó, e a complexidade do tempo é $O(k )$, onde $k$ é o grau médio dos nós. Portanto, a matriz de adjacência é mais eficiente quando é necessário encontrar frequentemente os nós vizinhos do nó.

Matriz de Incidência

Uma matriz de incidência é uma representação de um gráfico em que as linhas representam vértices, as colunas representam arestas e cada elemento indica se o vértice está conectado a essa aresta. Se um vértice estiver conectado a uma aresta, exiba 1 nesse elemento, caso contrário, exiba 0.

Aqui está um exemplo de uma matriz de incidência simples com 4 vértices e 5 arestas:

a b c d e
a   1 0 0 1 1
b   0 1 1 1 0
c   0 0 1 0 1
d   1 1 0 0 0

Em Java, podemos implementar uma matriz de incidência usando um array bidimensional. Aqui está um exemplo de código para uma matriz de incidência simples implementada em Java:

public class AdjacencyMatrix {
    private int[][] matrix; // 二维数组来存储邻接矩阵
    private int numVertices; // 图中的顶点数
    private int numEdges; // 图中的边数

    // 构造方法,初始化邻接矩阵
    public AdjacencyMatrix(int numVertices) {
        this.numVertices = numVertices;
        this.numEdges = 0;
        matrix = new int[numVertices][numVertices];
        for (int i = 0; i < numVertices; i++) {
            for (int j = 0; j < numVertices; j++) {
                matrix[i][j] = 0; // 初始时将所有元素置为0
            }
        }
    }

    // 添加边的方法
    public void addEdge(int i, int j) {
        if (i >= 0 && i < numVertices && j >= 0 && j < numVertices) {
            matrix[i][j] = 1; // 将矩阵对应位置上的元素置为1
            matrix[j][i] = 1; // 因为是无向图,所以需要将两个位置都置为1
            numEdges++; // 边的数量加1
        }
    }

    // 获取顶点数
    public int getNumVertices() {
        return numVertices;
    }

    // 获取边数
    public int getNumEdges() {
        return numEdges;
    }

    // 获取某个位置的元素
    public int get(int i, int j) {
        if (i >= 0 && i < numVertices && j >= 0 && j < numVertices) {
            return matrix[i][j];
        } else {
            return -1;
        }
    }

    // 打印邻接矩阵
    public void printMatrix() {
        for (int i = 0; i < numVertices; i++) {
            for (int j = 0; j < numVertices; j++) {
                System.out.print(matrix[i][j] + " ");
            }
            System.out.println();
        }
    }
}

3. Algoritmo de pesquisa em profundidade

Depth First Search (DFS) é um algoritmo para percorrer ou pesquisar uma árvore ou gráfico. O algoritmo começa a partir de um nó raiz e explora todos os seus ramos até que não haja mais nós inexplorados, depois volta ao nó anterior e então explora outros ramos. O DFS é frequentemente implementado de forma recursiva.

A seguir está uma implementação Java do algoritmo de pesquisa em profundidade para gráficos baseados em listas de adjacências:

import java.util.*;

public class Graph {
    private int V; // 图的顶点数
    private LinkedList<Integer> adj[]; // 邻接表表示图

    // 构造函数,初始化邻接表
    public Graph(int v) {
        V = v;
        adj = new LinkedList[V];
        for (int i = 0; i < V; i++) {
            adj[i] = new LinkedList();
        }
    }

    // 添加一条边,连接节点 v 和 w
    void addEdge(int v, int w) {
        adj[v].add(w);
    }

    // 深度优先搜索
    void DFSUtil(int v, boolean visited[]) {
        visited[v] = true;
        System.out.print(v + " ");

        Iterator<Integer> i = adj[v].listIterator();
        while (i.hasNext()) {
            int n = i.next();
            if (!visited[n])
                DFSUtil(n, visited);
        }
    }

    // 从指定的节点开始进行深度优先搜索
    void DFS(int v) {
        boolean visited[] = new boolean[V];
        DFSUtil(v, visited);
    }

    public static void main(String args[]) {
        Graph g = new Graph(4);

        g.addEdge(0, 1);
        g.addEdge(0, 2);
        g.addEdge(1, 2);
        g.addEdge(2, 0);
        g.addEdge(2, 3);
        g.addEdge(3, 3);

        System.out.println("从节点 2 开始的深度优先搜索结果为:");
        g.DFS(2);
    }
}

saída:

从节点 2 开始的深度优先搜索结果为:
2 0 1 3

No código acima, primeiro definimos uma classe Graph, que contém o número de vértices do grafo​V​​​ e uma lista de adjacências ​adj​, usada para representar a estrutura do grafo. Em seguida, definimos ​addEdge​o método para adicionar uma aresta ao gráfico. Em seguida, implementamos ​DFSUtil​o método de pesquisa em profundidade. Entre eles, usamos um ​visited​array para registrar se cada nó foi visitado e usamos ​Iterator​um iterador para percorrer os nós adjacentes ao nó atual. Por fim, implementamos ​DFS​o método , que chamará ​DFSUtil​o método para iniciar uma pesquisa em profundidade a partir do nó especificado. Finalmente, ​main​no método , criamos um gráfico com 4 nós, adicionamos 6 arestas a ele e fazemos uma pesquisa em profundidade começando no nó 2, e a saída é ​2 0 1 3​.

4. Algoritmo de pesquisa em largura

A pesquisa em largura (BFS) é um algoritmo para travessia de gráfico. Ele atravessa um determinado vértice do gráfico, visita seus nós vizinhos por sua vez e, em seguida, visita nós vizinhos de nós vizinhos e assim por diante, até que todos os nós disponíveis sejam percorridos atingiu o nó.

O BFS geralmente é implementado com a ajuda de filas. Começando no nó de origem, primeiro adicione o nó à fila, depois pegue o primeiro nó da fila, percorra todos os seus nós vizinhos e adicione esses nós vizinhos à fila até a fila está vazio.

A complexidade de tempo do BFS é O(V+E), onde V é o número de nós e E é o número de arestas.

Aqui está um exemplo de código BFS implementado em Java:

import java.util.*;

public class BFS {
    public static void bfs(int[][] graph, int start) {
        int n = graph.length; // 节点数
        boolean[] visited = new boolean[n]; // 标记是否已经访问
        Queue<Integer> queue = new LinkedList<>(); // 队列用于存放待访问的节点
        visited[start] = true; // 标记起点已经访问
        queue.offer(start); // 将起点加入队列中

        while (!queue.isEmpty()) {
            int node = queue.poll(); // 取出队列中的第一个节点
            System.out.print(node + " "); // 访问该节点
            for (int i = 0; i < n; i++) {
                if (graph[node][i] == 1 && !visited[i]) {
                    // 如果该节点有邻居节点且邻居节点未被访问过,则将邻居节点加入队列中
                    visited[i] = true;
                    queue.offer(i);
                }
            }
        }
    }

    public static void main(String[] args) {
        int[][] graph = {
                {0, 1, 1, 0, 0, 0},
                {1, 0, 0, 1, 1, 0},
                {1, 0, 0, 0, 1, 0},
                {0, 1, 0, 0, 1, 1},
                {0, 1, 1, 1, 0, 1},
                {0, 0, 0, 1, 1, 0}
        };
        bfs(graph, 0); // 从节点0开始遍历
    }
}

No código acima, usamos um array booleano visitado para marcar se o nó foi visitado e usamos uma fila para armazenar os nós a serem visitados. Usamos o gráfico da matriz de adjacência para representar o gráfico ao percorrer os nós vizinhos de cada nó. Caso o nó vizinho deste nó não tenha sido visitado, ele será adicionado à fila e marcado como visitado. Repita as etapas acima até que a fila esteja vazia.

V. Resumo

Em termos de algoritmos de classificação, introduzimos classificação por bolha, classificação por seleção, classificação por inserção, classificação rápida, classificação por mesclagem e classificação por heap, e explicamos sua complexidade de tempo e cenários de aplicação. Em termos de algoritmos de busca, introduzimos busca linear, busca binária, busca de interpolação, busca de Fibonacci e busca de hash, e explicamos sua complexidade de tempo e cenários de aplicação.

Em termos de gráficos, apresentamos os conceitos básicos e métodos de representação de gráficos, incluindo matriz de adjacência, lista de adjacência e matriz de associação, e introduzimos dois algoritmos comuns de pesquisa de gráficos, pesquisa em profundidade e pesquisa em largura. Também usamos a linguagem Java para implementar códigos de amostra de vários algoritmos, o que é conveniente para leitura, aprendizagem e compreensão.

Em suma, o domínio de estruturas de dados e algoritmos é um conhecimento básico muito importante em ciência da computação.Este artigo tem como objetivo fornecer aos leitores alguns conceitos básicos e idéias de implementação de algoritmos e códigos de exemplo, na esperança de ser útil para os leitores.

Se você acha que o conteúdo deste blog é útil ou inspirador para você, siga meu blog para obter os artigos técnicos e tutoriais mais recentes o mais rápido possível. Ao mesmo tempo, você também pode deixar uma mensagem na área de comentários para compartilhar suas idéias e sugestões. Obrigado pelo seu apoio!

Acho que você gosta

Origin blog.csdn.net/bairo007/article/details/132544949
Recomendado
Clasificación