Implementar estructuras de datos y algoritmos en Java: clasificación, búsqueda, gráficos.

Si cree que el contenido de este blog es útil o inspirador para usted, siga mi blog para obtener los últimos artículos técnicos y tutoriales lo antes posible. Al mismo tiempo, también puedes dejar un mensaje en el área de comentarios para compartir tus pensamientos y sugerencias. ¡Gracias por tu apoyo!

1. Introducción

Las estructuras de datos y los algoritmos son dos piedras angulares de la informática y son herramientas clave para resolver diversos problemas complejos y optimizar programas informáticos. La importancia de las estructuras de datos y los algoritmos se refleja en los siguientes aspectos:

  1. Mejorar la eficiencia del programa : las buenas estructuras de datos y algoritmos pueden hacer que los programas se ejecuten más rápido y utilicen los recursos informáticos de manera más eficiente. Por ejemplo, utilizar el algoritmo de clasificación rápida puede reducir en gran medida el tiempo de clasificación que utilizar el algoritmo de clasificación de burbujas.
  2. Resolver problemas complejos : las estructuras de datos y los algoritmos pueden ayudar a los desarrolladores a resolver diversos problemas complejos, como procesamiento de gráficos, inteligencia artificial, procesamiento de big data, etc. Por ejemplo, el uso de estructuras de datos y algoritmos adecuados puede permitir que los motores de búsqueda manejen mejor las consultas de los usuarios y devuelvan los resultados más relevantes.
  3. Mejore la confiabilidad del programa : utilice estructuras de datos y algoritmos correctos para evitar diversos errores y excepciones cuando el programa se esté ejecutando. Por ejemplo, el uso de estructuras de datos o algoritmos inapropiados al manipular datos puede provocar daños en los datos o fallas del programa.
  4. Organice y gestione mejor los datos : los datos se pueden organizar y gestionar mejor con las estructuras de datos adecuadas. Por ejemplo, el uso de una tabla hash permite que los programas encuentren datos más rápido, mientras que el uso de una estructura de árbol permite una mejor organización de los datos jerárquicos.

Las estructuras de datos y los algoritmos son herramientas muy importantes en informática para mejorar la eficiencia de los programas, resolver problemas complejos, mejorar la confiabilidad del programa y organizar y administrar mejor los datos. El dominio de las estructuras de datos y los algoritmos es esencial para desarrollar programas informáticos eficientes y de alta calidad.


2. Algoritmo de clasificación

1. Clasificación de burbujas

La clasificación de burbujas es un algoritmo de clasificación básico. Su idea es comparar dos elementos adyacentes. Si el elemento anterior es mayor que el último, entonces se intercambian las posiciones de los dos elementos. Después de una ronda de comparación, se intercambiará el elemento más grande. hasta el final de la matriz. Repita este proceso hasta que se ordene toda la matriz.

El siguiente es el código para implementar la clasificación de burbujas en 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));
    }
}

En el código anterior, definimos un método estático ​bubbleSort​para implementar la clasificación de burbujas, que acepta una matriz de números enteros como parámetro y utiliza dos bucles para implementar el proceso de clasificación de burbujas. En el bucle exterior, controle el número de rondas de comparación, el número de bucles es la longitud de la matriz menos 1; en el bucle interior, controle el número de comparaciones en cada ronda, el número de bucles es la longitud de la matriz menos el número actual de rondas menos 1. Si el elemento anterior es mayor que el último, intercambie las posiciones de los dos elementos.

Finalmente ​main​, llame ​bubbleSort​al método en el método para ordenar una matriz de enteros y use ​Arrays.toString​el método para enviar los resultados ordenados a la consola.

2. Orden de selección

La idea de la clasificación por selección es seleccionar el elemento más pequeño de la matriz y colocarlo al frente de la matriz, y luego seleccionar el elemento más pequeño de los elementos restantes y colocarlo al final de la parte ordenada. Repita este proceso hasta que se ordene toda la matriz.

El siguiente es el código para implementar la clasificación por selección en 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));
    }
}

En el código anterior, definimos un método estático ​selectionSort​para implementar la clasificación por selección, que acepta una matriz de números enteros como parámetro y utiliza dos bucles para implementar el proceso de clasificación por selección. En el bucle exterior, se controla la longitud de la parte ordenada y el número de bucles es la longitud de la matriz menos 1; en el bucle interior, el subíndice del elemento más pequeño se selecciona de la parte no ordenada. Si se encuentra un elemento más pequeño, actualice su subíndice al subíndice del elemento más pequeño.

Finalmente ​main​, llame ​selectionSort​al método en el método para ordenar una matriz de enteros y use ​Arrays.toString​el método para enviar los resultados ordenados a la consola.

3. Ordenación por inserción

La idea de la ordenación por inserción es dividir la matriz en partes ordenadas y sin clasificar, e insertar los elementos sin clasificar en la posición apropiada de la parte ordenada cada vez. La operación específica es tomar el primer elemento de la parte sin clasificar, compararlo desde el último elemento de la parte ordenada, hasta encontrar un elemento más pequeño que él, y luego insertar el elemento en la posición apropiada.

El siguiente es el código para implementar la ordenación por inserción en 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));
    }
}

En el código anterior, definimos un método estático ​insertionSort​para implementar la clasificación por inserción, que acepta una matriz de números enteros como parámetro y utiliza dos bucles para implementar el proceso de clasificación por inserción. En el bucle exterior, se controla la longitud de la parte ordenada y el número de bucles es la longitud de la matriz menos 1; en el bucle interior, los elementos a ordenar se insertan en las posiciones apropiadas de la parte ordenada. Compare los elementos a ordenar con los elementos en la parte ordenada de atrás hacia adelante. Si el elemento en la parte ordenada es más grande que el elemento a ordenar, mueva el elemento hacia atrás una posición hasta la posición correcta del elemento a ordenar. Se encuentra ordenado.

Finalmente ​main​, llame ​insertionSort​al método en el método para ordenar una matriz de enteros y use ​Arrays.toString​el método para enviar los resultados ordenados a la consola.

4. Clasificación rápida

La clasificación rápida es un algoritmo de clasificación de divide y vencerás. Su idea central es dividir la matriz en dos partes, una parte es más pequeña que la otra, y luego ordenar rápidamente las dos partes por separado y finalmente fusionarlas. La operación específica es seleccionar un elemento de referencia, dividir la matriz en dos partes, los elementos de la izquierda son más pequeños que el elemento de referencia y los elementos de la derecha son más grandes que el elemento de referencia, y luego realizar de forma recursiva una clasificación rápida en el partes izquierda y derecha.

La clasificación rápida es un algoritmo de clasificación eficiente. Su idea básica es seleccionar un elemento de referencia y dividir la parte que se va a ordenar en subsecuencias izquierda y derecha, de modo que los elementos de la subsecuencia izquierda sean todos más pequeños que el elemento de referencia, y los elementos de la subsecuencia izquierda sean todos más pequeños que el elemento de referencia. la subsecuencia derecha son todos más pequeños que el elemento de referencia Mayor o igual que el elemento base. Luego, las subsecuencias izquierda y derecha se ordenan rápidamente de forma recursiva hasta que se ordena toda la secuencia.

El siguiente es el código para implementar Quicksort en 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));
    }
}

En el código anterior, definimos un método estático ​quickSort​para implementar la clasificación rápida, que acepta una matriz de números enteros, un límite izquierdo y un límite derecho como parámetros e implementa el proceso de clasificación rápida de forma recursiva. En cada ronda de clasificación rápida, primero seleccionamos un elemento de referencia y luego usamos dos punteros i y j para señalar los extremos izquierdo y derecho respectivamente. Luego, recorremos la matriz de izquierda a derecha para encontrar la posición i del primer elemento mayor o igual que el elemento de referencia, y luego recorremos la matriz de derecha a izquierda para encontrar la posición j del primer elemento menor que la referencia. elemento. Si i y j no se encuentran, intercambie las posiciones de arr [i] y arr [j] y coloque los elementos menores o iguales que el elemento de referencia en la subsecuencia izquierda. Continúe buscando elementos mayores o iguales que el elemento de referencia en la subsecuencia izquierda hasta que i> = j, lo que indica que la matriz se ha dividido en subsecuencias izquierda y derecha. Luego, coloque el elemento de referencia en la posición correcta, es decir, la posición de i-1, los elementos de la subsecuencia izquierda son todos más pequeños que el elemento de referencia y los elementos de la subsecuencia derecha son mayores o iguales que la referencia. elemento.

La complejidad temporal de la clasificación rápida es O (nlogn), y la complejidad temporal en el peor de los casos es O (n ^ 2), pero esta situación es relativamente rara. Su complejidad espacial es O (logn) y es un algoritmo de clasificación in situ que no requiere espacio adicional.

Aquí hay un ejemplo de resultado de clasificación rápida de una matriz de enteros usando el código anterior:

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

5. Combinar orden

Merge sort es un algoritmo de clasificación basado en la idea de dividir y conquistar. Su idea central es descomponer un problema grande en varios problemas pequeños para resolverlo y luego combinar las soluciones de los problemas pequeños para obtener la solución del problema original. . La idea básica de la clasificación por combinación es dividir la secuencia que se va a clasificar en varias subsecuencias, cada una de las cuales está en orden, y luego fusionar las subsecuencias para obtener una secuencia completamente ordenada.

La ordenación por combinación es adecuada para ordenar datos a gran escala y su complejidad temporal es estable en O (nlogn). Su principal ventaja es que tiene buena estabilidad y puede manejar datos a gran escala, pero necesita espacio adicional para almacenar los resultados intermedios generados al dividir y conquistar.

El siguiente es un código de muestra para implementar la ordenación por combinación en 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));
    }
}

En la ordenación por combinación, el paso más importante es fusionar dos subsecuencias ordenadas. Cuando fusionamos dos subsecuencias ordenadas, podemos usar el recorrido de doble puntero para colocar los elementos de las dos subsecuencias en una matriz temporal en orden de tamaño hasta que todos los elementos de una de las subsecuencias se coloquen en el temporal de la matriz. En este punto, los elementos de la otra subsecuencia deben ser más grandes que los elementos de la matriz temporal (porque están ordenados), por lo que podemos colocar estos elementos directamente al final de la matriz temporal.

Finalmente, solo necesitamos copiar los elementos de la matriz temporal nuevamente a la matriz original, completando así todo el proceso de clasificación por combinación.

En aplicaciones prácticas, la ordenación por combinación se usa a menudo como un algoritmo de clasificación interna, por ejemplo, el método Arrays.sort en Java usa la ordenación por combinación.

6. Ordenación del montón

La clasificación del montón es un algoritmo de clasificación basado en la estructura de datos del montón. Su idea es construir primero la matriz que se ordenará en un montón binario y luego sacar los elementos superiores del montón (es decir, el elemento más grande o más pequeño) y colóquelos en la parte ordenada y luego reajuste la estructura del montón de los elementos restantes para cumplir con las propiedades del montón. Repita este proceso hasta que todos los elementos estén ordenados.

La clasificación del montón se puede dividir en dos pasos:

  1. Construya un montón: cree la matriz que se ordenará en un montón binario. La operación específica es comenzar desde el último nodo no hoja de la matriz y compararlo con sus nodos secundarios a su vez. Si la naturaleza del montón no se cumple, intercambie las posiciones de los dos nodos, luego avance y Continúe comparando hasta el nodo raíz.
  2. Clasificación: saque los elementos superiores del montón uno por uno, colóquelos al final de la parte ordenada y luego reajuste la estructura del montón de los elementos restantes para cumplir con las propiedades del montón. La operación específica es intercambiar la posición del elemento superior del montón con el último elemento del montón, luego reducir el tamaño del montón en 1 y luego realizar una operación de filtrado en el elemento superior del montón para moverlo a una posición adecuada para que cumpla con las propiedades del montón.

La complejidad temporal de la clasificación del montón es O (nlogn) y la complejidad espacial es O (1), es un algoritmo de clasificación in situ que no requiere espacio adicional para almacenar datos. La clasificación de montón es adecuada para escenarios donde es necesario ordenar una gran cantidad de datos y la complejidad del espacio es limitada. Sin embargo, debido a la gran constante de la clasificación de montón, en comparación con algoritmos como la clasificación rápida, la eficiencia de los datos a pequeña escala puede No será tan bueno como otros algoritmos.

A continuación se muestra un ejemplo de clasificación de montón implementada en 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));
    }
}

En este ejemplo, primero usamos ​heapify​el método para construir la matriz en un montón máximo y luego intercambiamos el elemento superior y el elemento final de la matriz en cada ronda de clasificación y reconstruimos el montón. Finalmente, podemos obtener una matriz ordenada. Dado que la complejidad temporal de la clasificación del montón es O (nlogn), es un algoritmo de clasificación muy eficiente y, a menudo, se utiliza para clasificar datos a gran escala.

7. Resumen

La clasificación por burbujas y la clasificación por selección son los algoritmos de clasificación más simples, aunque tienen una gran complejidad temporal, son fáciles de entender e implementar. La complejidad temporal de la clasificación por inserción es ligeramente menor que la de la clasificación por burbuja y la clasificación por selección, pero debido a su estabilidad y mejor rendimiento, es más común en aplicaciones prácticas.

La clasificación rápida y la clasificación por combinación son algoritmos de clasificación más eficientes y su complejidad temporal es O (nlogn), por lo que tienen una ventaja cuando se trata de datos a gran escala. La clasificación rápida suele ser más rápida que la clasificación por combinación, pero es menos estable. Por el contrario, la ordenación por combinación tiene mejor estabilidad y es adecuada para más escenarios.

La clasificación de montón es un algoritmo de clasificación con una complejidad temporal de O (nlogn) y su rendimiento es mejor que la clasificación de burbujas, la clasificación de selección y la clasificación de inserción, pero a menudo se reemplaza por la clasificación por fusión y la clasificación rápida en aplicaciones prácticas.

En aplicaciones prácticas, debemos elegir diferentes algoritmos de clasificación según problemas específicos y características de los datos. Para datos a pequeña escala, podemos usar algoritmos de clasificación simples, como clasificación por burbujas, clasificación por selección y clasificación por inserción; para datos a gran escala, podemos usar algoritmos de clasificación más eficientes, como clasificación rápida, clasificación por fusión y clasificación en montón.


3. Algoritmo de búsqueda

1. Búsqueda lineal

La búsqueda lineal, también conocida como búsqueda secuencial, es un algoritmo de búsqueda básico. Su idea es buscar hacia atrás uno por uno desde el primer elemento del conjunto de datos hasta encontrar el elemento objetivo o recorrer todo el conjunto de datos. Debido a que se compara uno por uno, es aplicable a varios tipos de datos, incluidos conjuntos de datos ordenados y desordenados.

La complejidad temporal de la búsqueda lineal es O (n), donde n representa el tamaño del conjunto de datos y, en el peor de los casos, es necesario buscar en todo el conjunto de datos. Sin embargo, si el conjunto de datos está ordenado, el número de comparaciones se puede reducir optimizando el salto fuera del bucle y la complejidad del tiempo puede alcanzar O (1).

El siguiente es un código de muestra para implementar la búsqueda lineal en 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 + " 不在数组中");
        }
    }
}

El código anterior implementa una función de búsqueda lineal ​linearSearch​simple y ​main​demuestra cómo usar esta función para buscar elementos en la matriz de la función.

2. Búsqueda binaria

La búsqueda binaria, también conocida como búsqueda binaria, es un algoritmo de búsqueda de uso común. Su idea es reducir a la mitad el rango a buscar en cada proceso de búsqueda de un conjunto de datos ordenados, hasta que se encuentre el elemento objetivo o el rango a buscar esté vacío.

La complejidad temporal de la búsqueda binaria es O (log n), donde n es el tamaño del conjunto de datos. Dado que cada búsqueda reducirá a la mitad el rango a buscar, su eficiencia es mucho mayor que la búsqueda lineal, especialmente para grandes conjuntos de datos ordenados. Pero sus limitaciones también son obvias y sólo se aplican a conjuntos de datos ordenados.

El siguiente es un código de muestra para implementar la búsqueda binaria en 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 + " 不在数组中");
        }
    }
}

El código anterior implementa una función de búsqueda binaria ​binarySearch​simple y ​main​demuestra cómo usar esta función para buscar elementos en la matriz de la función.

3. Búsqueda por interpolación

La búsqueda por interpolación es una variante de la búsqueda binaria que también funciona en conjuntos de datos ordenados. A diferencia de la búsqueda binaria, la búsqueda por interpolación selecciona el intervalo de búsqueda de acuerdo con la posición estimada del valor objetivo, en lugar de seleccionar directamente la posición media.

Específicamente, la idea básica del algoritmo de búsqueda por interpolación es comparar el valor objetivo con los dos puntos finales del intervalo de búsqueda y luego calcular una posición estimada en función de la posición relativa del valor objetivo en todo el conjunto de datos. Luego, según la posición estimada, el intervalo de búsqueda se reduce hasta encontrar el valor objetivo.

La complejidad temporal del algoritmo de búsqueda por interpolación es O (logn), pero para conjuntos de datos con una distribución de datos relativamente uniforme, la eficiencia de la búsqueda por interpolación puede ser mayor que la de la búsqueda binaria. Sin embargo, para distribuciones de datos extremas, el rendimiento de las búsquedas de interpolación puede degradarse.

El siguiente es un ejemplo de implementación de código Java para la búsqueda de interpolación:

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 + " 不在数组中");
        }
    }
}

En este ejemplo, primero calculamos la posición estimada pos y luego limitamos el rango de búsqueda de acuerdo con la relación de tamaño entre pos y objetivo. Específicamente, si arr[pos] < objetivo, a la izquierda se le asigna pos + 1; si arr[pos] > objetivo, a la derecha se le asigna pos - 1. Si arr[pos] = objetivo, devuelve pos.

4. Búsqueda de Fibonacci

La búsqueda de Fibonacci es un algoritmo de búsqueda basado en el punto de la sección áurea, que se puede utilizar para buscar conjuntos de datos ordenados. De manera similar a la búsqueda binaria, la búsqueda de Fibonacci también compara el valor objetivo con el punto medio del intervalo de búsqueda y luego reduce el rango de búsqueda de acuerdo con el resultado de la comparación.

La diferencia es que la búsqueda de Fibonacci utiliza el concepto de punto de sección áurea, es decir, el intervalo de búsqueda se divide en dos subintervalos según una determinada proporción. Específicamente, suponiendo que la longitud del intervalo de búsqueda es n, primero encontramos un número f que sea mayor que n y más cercano a n en la secuencia de Fibonacci, y luego dividimos el intervalo de búsqueda en dos subintervalos con longitudes f y f-1. A continuación, compare el valor objetivo con el punto medio de los dos subintervalos y luego reduzca aún más el rango de búsqueda de acuerdo con el resultado de la comparación.

La complejidad temporal del algoritmo de búsqueda de Fibonacci es O (logn), pero en comparación con la búsqueda binaria, requiere espacio adicional para almacenar la secuencia de Fibonacci.

La búsqueda de Fibonacci es adecuada para conjuntos de datos ordenados y la longitud del conjunto de datos es incierta o la distribución del conjunto de datos es relativamente uniforme. Dado que la tasa de crecimiento de la secuencia de Fibonacci es más rápida que la de la búsqueda binaria, la eficiencia de la búsqueda de Fibonacci puede ser mayor cuando la longitud del intervalo de búsqueda es relativamente grande.

El siguiente es un ejemplo de implementación de código Java de búsqueda de 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);
        }
    }
}

En este ejemplo, primero usamos la secuencia de Fibonacci para calcular la longitud del intervalo de búsqueda y luego encontramos el valor objetivo en el intervalo de búsqueda. Específicamente, primero encontramos el número de Fibonacci f que es mayor que el valor objetivo y más cercano a la longitud del intervalo de búsqueda, y luego dividimos el intervalo de búsqueda en dos subintervalos con longitudes f y f-1. Luego, use el bucle while para buscar iterativamente hasta que se encuentre el valor objetivo o la longitud del intervalo de búsqueda sea 1 y se detenga la búsqueda.

En cada iteración, calculamos el índice i del punto intermedio y luego comparamos el valor objetivo con arr [i]. Si el valor objetivo es menor que arr[i], reduzca el intervalo de búsqueda al subintervalo izquierdo; de lo contrario, reduzca el intervalo de búsqueda al subintervalo derecho. Finalmente, cuando la longitud del intervalo de búsqueda es 1, si arr[offset+1] es igual al valor objetivo, se devuelve offset+1; de lo contrario, se devuelve -1 para indicar que la búsqueda falló.

A continuación se muestra un ejemplo del uso del algoritmo de búsqueda de Fibonacci para encontrar elementos en una matriz ordenada:

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);
}

La salida es:

Se puede ver que el uso del algoritmo de búsqueda de Fibonacci puede encontrar rápidamente el subíndice del elemento objetivo en la matriz ordenada.

5. Búsqueda de hash

La búsqueda hash, también conocida como búsqueda de tabla hash, es un algoritmo de búsqueda basado en tablas hash. En la tabla hash, asignamos la clave a una ubicación fija, llamada dirección hash, de modo que el elemento objetivo se pueda encontrar rápidamente dentro de la complejidad temporal de O (1).

La búsqueda hash tiene una amplia gama de escenarios de aplicación, como índices en bases de datos, tablas de símbolos en compiladores y sistemas de caché. Dado que la tabla hash tiene las características de búsqueda, inserción y eliminación rápidas, en escenarios que requieren operaciones de datos frecuentes, el uso de la búsqueda hash puede mejorar en gran medida el rendimiento del programa.

La complejidad temporal de la búsqueda de hash es O (1), pero la eficiencia de la búsqueda puede verse afectada cuando se trata de colisiones de hash. Los métodos comunes para resolver conflictos de hash incluyen el método de dirección en cadena, el método de dirección abierta, etc.

A continuación se muestra un ejemplo del uso del algoritmo de búsqueda hash para encontrar un elemento en una 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 + " 不在数组中");
        }
    }
}

En el código anterior, usamos HashMap en Java para implementar una tabla hash y usamos los elementos de la matriz como claves y los subíndices de los elementos como valores. Luego, solo necesitamos buscar el elemento de destino en la tabla hash y devolver su subíndice si existe; de ​​lo contrario, devolver -1 para indicar que la búsqueda falló.

Cabe señalar que cuando utilizamos una tabla hash para implementar la búsqueda hash, debemos elegir una función hash adecuada para garantizar que los elementos se distribuyan uniformemente en la tabla hash, a fin de evitar la aparición de colisiones hash.

6. Búsqueda de árboles

Las búsquedas de árboles comunes incluyen árboles de búsqueda binarios, árboles binarios equilibrados, árboles B, árboles B+ y árboles rojo-negro. Todas estas estructuras de árbol tienen un buen rendimiento de búsqueda y son adecuadas para diferentes escenarios de aplicación.

  • Árbol de búsqueda binaria (BST): es un árbol binario especial, que satisface que el subárbol izquierdo de cualquier nodo sea más pequeño que el nodo y el subárbol derecho sea más grande que el nodo. En un árbol de búsqueda binario, la complejidad temporal de buscar, insertar, eliminar y otras operaciones es O (log n).
  • Árbol binario equilibrado: como el árbol AVL, el árbol rojo-negro, etc., pueden garantizar que la diferencia de altura entre los lados izquierdo y derecho del árbol no exceda 1, asegurando así que la complejidad temporal de operaciones como la búsqueda, inserción y eliminación es O (log n).
  • Árbol B: es un árbol de búsqueda equilibrado de múltiples vías, cada nodo tiene múltiples nodos secundarios y puede almacenar múltiples elementos de datos. Los árboles B generalmente se usan en almacenamiento externo, como discos, porque sus nodos pueden almacenar múltiples elementos de datos, lo que puede reducir efectivamente la cantidad de operaciones de E/S del disco y mejorar la velocidad de lectura de datos.
  • Árbol B +: es una mejora basada en el árbol B. En comparación con el árbol B, los nodos internos del árbol B + no almacenan datos, solo almacenan información de índice y todos los datos se almacenan en los nodos hoja. Los árboles B+ también se utilizan habitualmente en el almacenamiento externo, que puede utilizar el espacio en disco de forma más eficiente.
  • Árbol rojo-negro: es un árbol de búsqueda binario autoequilibrado que garantiza que la altura del árbol esté aproximadamente equilibrada coloreando los nodos, asegurando así que la complejidad temporal de operaciones como búsqueda, inserción y eliminación sea O ( iniciar sesión norte). Los árboles rojo-negro se utilizan a menudo en estructuras de datos integradas en lenguajes de programación, como set y map en C++ STL.

árbol de búsqueda binaria

Un árbol de búsqueda binario es una estructura de datos de uso común, que es un árbol binario en el que cada nodo contiene una clave y un valor correspondiente. Para cada nodo, los valores clave de todos los nodos en su subárbol izquierdo son menores que el valor clave del nodo, y los valores clave de todos los nodos en el subárbol derecho son mayores que el valor clave del nodo. De esta manera, podemos utilizar un árbol de búsqueda binario para buscar, insertar y eliminar elementos rápidamente.

La complejidad temporal del árbol de búsqueda binaria está relacionada con la altura del árbol y, en el peor de los casos, puede degenerar en una lista vinculada y la complejidad temporal se convierte en O (n). Por tanto, para evitar esta situación, podemos utilizar un árbol binario equilibrado u otras estructuras de datos más avanzadas en lugar de un árbol de búsqueda binario.

A continuación se muestra un ejemplo de un árbol de búsqueda binario implementado en 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;
    }
}

En aplicaciones prácticas, debemos elegir diferentes algoritmos de búsqueda según las características de los datos y las necesidades específicas. Para conjuntos de datos ordenados, la búsqueda binaria y la búsqueda de árbol son opciones más comunes; para conjuntos de datos desordenados, la búsqueda lineal y la búsqueda hash son opciones más comunes. Además, las búsquedas de hash y de árbol generalmente tienen un mejor rendimiento cuando se trata de datos a gran escala.


4. Representación y recorrido de gráficos.

1. Conceptos básicos y definiciones de gráficos.

Un gráfico es una estructura de datos compuesta por nodos (vértice) y aristas (arista), y generalmente se usa para describir la relación entre cosas. Un gráfico se puede representar por G=(V,E), donde V representa el conjunto de nodos y E representa el conjunto de aristas. Cada borde conecta dos nodos, que pueden ser el mismo nodo (autobucle) o nodos diferentes (no autobucle).

Hay dos tipos de gráficos: gráficos dirigidos y gráficos no dirigidos. Un borde en un gráfico dirigido tiene una dirección, lo que indica que apunta de un nodo a otro, mientras que un borde en un gráfico no dirigido no tiene dirección, lo que indica que la relación entre dos nodos es mutua.

Los nodos en el gráfico pueden tener pesos, lo que indica que la relación entre los nodos es fuerte o débil. Por ejemplo, si los usuarios de una red social tienen una relación de amigos, el número de amigos es el peso.

Hay algunos conceptos básicos en la figura, incluidos grado, ruta, conectividad, árbol de expansión, etc.

  • Grado: el grado de un nodo se refiere al número de aristas conectadas al nodo.
  • Ruta: una ruta es una secuencia de nodos conectados por bordes, y la longitud de una ruta se refiere al número de aristas en la ruta.
  • Conectividad: dos nodos en un gráfico no dirigido están conectados si hay una ruta que los conecta. Dos nodos en un grafo dirigido están conectados si pueden llegar a otro nodo.
  • Árbol de expansión: el árbol de expansión de un gráfico no dirigido se refiere a un subgrafo conectado del gráfico, que contiene todos los nodos del gráfico, pero no contiene ningún ciclo (ciclo) y tiene el menor número de aristas. Para gráficos dirigidos, la definición de árbol de expansión requiere una distinción entre grado interno y externo.

Los gráficos tienen una amplia gama de aplicaciones en informática, incluida la topología de red, el procesamiento de imágenes, el procesamiento del lenguaje natural y más.

2. El método de representación del gráfico.

Los gráficos se pueden representar de varias formas y los tres métodos de representación comunes son los siguientes:

  1. Matriz de adyacencia: utilice una matriz bidimensional para representar los nodos y bordes en el gráfico, donde las filas y columnas de la matriz representan nodos. Si hay un borde entre dos nodos, se marca como 1 o 1 en la fila correspondiente y pesos de borde de columna. Si no hay ningún borde entre dos nodos, se marca como 0 en la posición correspondiente.
  2. Lista de adyacencia (Lista de adyacencia): utilice una matriz para representar todos los nodos, cada nodo corresponde a una lista vinculada y la lista vinculada almacena otros nodos conectados al nodo y el peso del borde correspondiente.
  3. Matriz de incidencia: se utiliza una matriz bidimensional para representar los nodos y los bordes en el gráfico, donde las filas de la matriz representan nodos y las columnas representan bordes. Si hay una asociación entre el nodo y el borde, la posición correspondiente se marca como 1 o el peso del borde; de ​​lo contrario, se marca como 0.

En aplicaciones prácticas, la elección de diferentes métodos de representación afectará la complejidad temporal y espacial del algoritmo. Por ejemplo, la matriz de adyacencia es adecuada para representar gráficos con bordes densos, pero la complejidad espacial es alta; la lista de adyacencia es adecuada para representar gráficos con bordes dispersos, pero la complejidad temporal para acceder a los nodos es alta.

Matriz de adyacencia

La matriz de adyacencia es un método de representación común de un gráfico, que utiliza una matriz bidimensional para representar la relación de conexión entre todos los nodos. La fila i y la columna j de la matriz de adyacencia es 1 si hay un borde entre el nodo i y el nodo j; en caso contrario, es 0.

Las matrices de adyacencia son adecuadas para gráficos densos, es decir, gráficos donde el número de aristas es cercano al número de nodos. Dado que cada arista debe ocupar un elemento de matriz, para un gráfico disperso con una pequeña cantidad de aristas, el uso de una matriz de adyacencia provocará una gran pérdida de espacio. Además, la matriz de adyacencia no puede admitir bien la adición y eliminación dinámica de aristas en el gráfico, porque el tamaño de la matriz debe reajustarse al agregar y eliminar aristas, lo que genera una alta complejidad temporal.

Las ventajas de una matriz de adyacencia son:

  • Puede determinar rápidamente si existe una conexión de borde entre dos nodos cualesquiera;
  • Nodos de acceso aleatorio que soportan tiempo constante.

A continuación se muestra un ejemplo de código Java para un gráfico dirigido representado por una matriz de adyacencia:

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();
    }
}

Al ejecutar el código anterior se generará la siguiente matriz de adyacencia:

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

El gráfico dirigido representado por la matriz de adyacencia contiene cuatro nodos, a saber, 0, 1, 2 y 3. Entre ellos, el nodo 0 tiene dos bordes salientes, que apuntan al nodo 1 y al nodo 2 respectivamente; el nodo 1 tiene un borde saliente que apunta al nodo 2; el nodo 2 tiene dos bordes salientes, que apuntan al nodo 0 y al nodo 3 respectivamente; el nodo 3 no tiene borde de salida.

Lista de adyacencia

La lista de adyacencia es otro método común de representación de gráficos, que utiliza una lista vinculada para representar los nodos vecinos de cada nodo en el gráfico. Cada nodo tiene una lista vinculada que contiene todos los nodos vecinos del nodo.

La siguiente es la implementación del código Java de la lista de adyacencia:

Primero defina una clase de nodo de lista de adyacencia ​AdjListNode​, que contenga un número de nodo ​v​y un puntero al siguiente nodo vecino ​next​.

class AdjListNode {
    int v;
    AdjListNode next;

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

Luego defina una clase de lista de adyacencia ​AdjList​, que contiene una matriz ​adjLists​, cada elemento de la matriz es un nodo de la lista de adyacencia, que representa un nodo en el gráfico y sus nodos vecinos.

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;
    }
}

Un ejemplo de representación de un gráfico no dirigido utilizando una lista de adyacencia:

0
     / 
    1 - 2
   / \   \
  3   4   5

Código Java correspondiente:

// 创建一个有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);

La ventaja de la lista de adyacencia es que puede representar un gráfico disperso con menos espacio, pero su desventaja es que necesita atravesar la lista vinculada cuando busca todos los nodos vecinos de un nodo, y la complejidad del tiempo es $ O (k )$, donde $k$ es el grado promedio de los nodos. Por lo tanto, la matriz de adyacencia es más eficiente cuando es necesario encontrar con frecuencia los nodos vecinos del nodo.

Matriz de incidencia

Una matriz de incidencia es una representación de un gráfico en el que las filas representan vértices, las columnas representan aristas y cada elemento indica si el vértice está conectado a esa arista. Si un vértice está conectado a un borde, muestra 1 en ese elemento; de lo contrario, muestra 0.

A continuación se muestra un ejemplo de una matriz de incidencia simple con 4 vértices y 5 aristas:

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

En Java, podemos implementar una matriz de incidencia usando una matriz bidimensional. Aquí hay un código de muestra para una matriz de incidencia simple implementada en 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 búsqueda en profundidad

Depth First Search (DFS) es un algoritmo para atravesar o buscar un árbol o gráfico. El algoritmo comienza desde un nodo raíz y explora todas sus ramas hasta que no quedan nodos inexplorados, luego retrocede hasta el nodo anterior y luego explora otras ramas. DFS suele implementarse de forma recursiva.

La siguiente es una implementación Java del algoritmo de búsqueda en profundidad para gráficos basados ​​en listas de adyacencia:

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);
    }
}

producción:

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

En el código anterior, primero definimos una clase Graph, que contiene el número de vértices del gráfico ​V​y una lista de adyacencia ​adj​, que se utiliza para representar la estructura del gráfico. Luego definimos ​addEdge​el método para agregar una arista al gráfico. Luego implementamos ​DFSUtil​el método de búsqueda en profundidad. Entre ellos, usamos una ​visited​matriz para registrar si cada nodo ha sido visitado y usamos ​Iterator​un iterador para atravesar los nodos adyacentes al nodo actual. Finalmente, implementamos ​DFS​el método , que llamará ​DFSUtil​al método para iniciar una búsqueda profunda desde el nodo especificado. Finalmente, en ​main​el método , creamos un gráfico con 4 nodos, le agregamos 6 aristas y hacemos una búsqueda en profundidad comenzando desde el nodo 2, y el resultado es ​2 0 1 3​.

4. Algoritmo de búsqueda en amplitud

La búsqueda en amplitud (BFS) es un algoritmo para el recorrido de gráficos. Atraviesa desde un determinado vértice del gráfico, visita sus nodos vecinos por turno y luego visita los nodos vecinos de los nodos vecinos, y así sucesivamente, hasta que se atraviesan todos los nodos disponibles. .alcanzó el nodo.

BFS generalmente se implementa con la ayuda de colas: comenzando desde el nodo de origen, primero agregue el nodo a la cola, luego tome el primer nodo de la cola, atraviese todos sus nodos vecinos y agregue estos nodos vecinos a la cola hasta que la cola está vacío.

La complejidad temporal de BFS es O (V + E), donde V es el número de nodos y E es el número de aristas.

A continuación se muestra un ejemplo de código BFS implementado en 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开始遍历
    }
}

En el código anterior, usamos una matriz booleana visitada para marcar si el nodo ha sido visitado y usamos una cola para almacenar los nodos que se visitarán. Usamos el gráfico de matriz de adyacencia para representar el gráfico al atravesar los nodos vecinos de cada nodo. Si el nodo vecino de este nodo no ha sido visitado, se agregará a la cola y se marcará como visitado. Repita los pasos anteriores hasta que la cola esté vacía.

V. Resumen

En términos de algoritmos de clasificación, introdujimos la clasificación por burbujas, la clasificación por selección, la clasificación por inserción, la clasificación rápida, la clasificación por fusión y la clasificación por montón, y explicamos su complejidad temporal y escenarios de aplicación. En términos de algoritmos de búsqueda, introdujimos la búsqueda lineal, la búsqueda binaria, la búsqueda por interpolación, la búsqueda de Fibonacci y la búsqueda hash, y explicamos su complejidad temporal y escenarios de aplicación.

En términos de gráficos, presentamos los conceptos básicos y los métodos de representación de los gráficos, incluida la matriz de adyacencia, la lista de adyacencia y la matriz de asociación, e introducimos dos algoritmos de búsqueda de gráficos comunes: búsqueda en profundidad y búsqueda en amplitud. También utilizamos el lenguaje Java para implementar códigos de muestra de varios algoritmos, lo cual es conveniente para leer, aprender y comprender.

En resumen, dominar las estructuras de datos y los algoritmos es un conocimiento básico muy importante en informática. Este artículo tiene como objetivo proporcionar a los lectores algunos conceptos básicos, ideas de implementación de algoritmos y códigos de muestra, con la esperanza de ser útiles para los lectores.

Si cree que el contenido de este blog es útil o inspirador para usted, siga mi blog para obtener los últimos artículos técnicos y tutoriales lo antes posible. Al mismo tiempo, también puedes dejar un mensaje en el área de comentarios para compartir tus pensamientos y sugerencias. ¡Gracias por tu apoyo!

Supongo que te gusta

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