Entrevista de Dachang: ¿Cómo encontrar el K-ésimo elemento más grande en O (n) con la idea de clasificación rápida?

En el artículo anterior, aprendimos sobre los tres algoritmos de clasificación: clasificación de burbujas, clasificación por inserción y clasificación por selección (haga clic en el enlace al final del artículo para verlo). Su complejidad de tiempo es O (n2), que es relativamente alta y adecuada para clasificar datos a pequeña escala. Hoy, aprendemos sobre algoritmos de clasificación con complejidad de tiempo O (nlogn), clasificación por fusión y clasificación rápida . Estos dos algoritmos de clasificación son adecuados para la clasificación de datos a gran escala y se utilizan con más frecuencia que los tres algoritmos de clasificación aprendidos en el artículo anterior.

Combinar ordenación

La idea central de la ordenación por fusión es bastante simple. Si desea ordenar una matriz, primero dividimos la matriz en dos partes desde el medio, y luego clasificamos las dos partes por separado, y luego fusionamos las dos partes ordenadas juntas, de modo que toda la matriz esté en orden.

El tipo de combinación utiliza la idea de dividir y conquistar . Dividir y conquistar, como su nombre indica, es dividir y conquistar, resolver un gran problema en subproblemas más pequeños. Cuando se resuelven los pequeños subproblemas, se resuelven los grandes problemas.

Los algoritmos de divide y vencerás generalmente se implementan por recursividad . Dividir y conquistar es una idea de procesamiento para resolver un problema, y ​​la recursividad es una técnica de programación, las dos no entran en conflicto.

El punto clave aquí es explicar que la técnica de escribir código recursivo es analizar la fórmula recursiva , encontrar la condición de terminación y finalmente traducir la fórmula recursiva en código recursivo. Por lo tanto, para escribir el código para la ordenación por combinación, primero escribimos la fórmula recursiva para la ordenación por combinación.

递推公式:
merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))
终止条件:
p >= r 不用再继续分解

Los amigos que recién están aprendiendo pueden tener dificultades para comprender, aquí hay una explicación:

merge_sort (p ... r) significa ordenar la matriz de subíndices de p a r. Transformamos este problema de clasificación en dos subproblemas, merge_sort (p ... q) y merge_sort (q + 1 ... r), donde el subíndice q es igual a la posición media de py r, que es (p + r) / 2. Cuando se ordenan las dos submatrices de subíndices de p a q y de q + 1 a r, fusionamos las dos submatrices ordenadas juntas, de modo que los datos entre los subíndices de p a r también sean Resuelto.

Los amigos a los que les gusta pensar pueden notar que en la fórmula recursiva anterior, la función de fusión más externa es fusionar las matrices ordenadas pequeñas descompuestas en una matriz ordenada. ¿Qué debo hacer?

Solicitamos una matriz temporal tmp con el mismo tamaño que A [p ... r]. Usamos dos cursores i y j para apuntar al primer elemento de A [p ... q] y A [q + 1 ... r] respectivamente. Compare estos dos elementos A [i] y A [j], si A [i] <= A [j], colocamos A [i] en la matriz temporal tmp, y movemos i un bit, de lo contrario A [j] se coloca en la matriz tmp, j se desplaza un bit hacia atrás.

Continúe con el proceso de comparación anterior hasta que todos los datos de una de las submatrices se coloquen en la matriz temporal, y luego los datos de la otra matriz se agreguen al final de la matriz temporal sucesivamente. En este momento, la matriz temporal es la combinación de las dos submatrices. El resultado después de eso. Finalmente, copie los datos de la matriz temporal tmp en la matriz original A [p ... r]. Puede consultar la figura a continuación para profundizar su comprensión.

Lo siguiente es mostrar el tiempo del código, puede consultar mi implementación aquí:

Ordenación rápida

Veamos el algoritmo de ordenación rápida (Quicksort), lo llamamos habitualmente "Quicksort". La idea de divide y vencerás también se usa en la fila rápida. A primera vista, se parece un poco al tipo de combinación, pero la idea es completamente diferente.

La idea de la clasificación rápida es la siguiente: si queremos clasificar un conjunto de datos de p a r en la matriz, elegimos cualquier dato de p a r como pivote (punto de partición).

Atravesamos los datos entre py r, y colocamos los más pequeños que pivote a la izquierda, los más grandes que pivote a la derecha y el pivote en el medio. Después de este paso, los datos entre la matriz pyr se dividen en tres partes, el frente entre py q-1 es menor que pivote, el medio es pivote y el reverso entre q + 1 y r es Mayor que pivote.

De acuerdo con la idea de procesamiento de dividir y conquistar y recursividad, podemos usar la clasificación recursiva de los datos entre los subíndices de p a q-1 y los datos entre los subíndices de q + 1 a r, hasta que el intervalo se reduzca a 1, lo que significa que todos Los datos están en orden.

递推公式:
quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1… r)
终止条件:
p >= r

Convierta la fórmula recursiva anterior en código recursivo de la siguiente manera:

// 快速排序,A是数组,n表示数组的大小
quick_sort(A, n) {
  quick_sort_c(A, 0, n-1)
}
// 快速排序递归函数,p,r为下标
quick_sort_c(A, p, r) {
  if p >= r then return
  
  q = partition(A, p, r) // 获取分区点
  quick_sort_c(A, p, q-1)
  quick_sort_c(A, q+1, r)
}

Hay una función de combinación merge () en el ordenamiento de combinación, aquí necesitamos una función de partición de partición (). Se trata de seleccionar aleatoriamente un elemento como pivote (en general, puede seleccionar el último elemento en el intervalo de p ar), y luego particionar A [p ... r]. La función devuelve el índice de pivote.

Porque todos sabemos que la clasificación rápida necesita lograr una complejidad de espacio de O (1), por lo que se requiere la partición in situ . El pseudocódigo es el siguiente:

partition(A, p, r) {
  pivot := A[r]
  i := p
  for j := p to r-1 do {
    if A[j] < pivot {
      swap A[i] with A[j]
      i := i+1
    }
  }
  swap A[i] with A[r]
  return i

El procesamiento aquí es algo similar al ordenamiento por selección. Dividimos A [p ... r-1] en dos partes mediante el cursor i. Los elementos de A [p ... i-1] son ​​todos más pequeños que el pivote, llamémoslo el "intervalo procesado" por el momento, y A [i ... r-1] es el "intervalo no procesado". Cada vez que tomamos un elemento A [j] del intervalo no procesado A [i ... r-1] y lo comparamos con pivote. Si es menor que pivote, lo sumamos al final del intervalo procesado, que es A [i ] s posición.

Insertar un elemento en una determinada posición en la matriz requiere mover datos, lo que requiere mucho tiempo. Aquí hay una técnica de procesamiento para aprender , es decir, el intercambio , que completa la operación de inserción en O (1) complejidad de tiempo. Simplemente intercambie A [i] con A [j], y A [j] se puede colocar en la posición del subíndice i dentro de O (1) complejidad de tiempo.

El texto no es tan intuitivo como la imagen, así que hice un dibujo para mostrar todo el proceso de partición.

Debido a que el proceso de partición implica operaciones de intercambio, si hay dos elementos idénticos en la matriz, como la secuencia 6, 3, 5, 9, 4, después de la primera operación de partición, los dos 6 son opuestos El orden cambiará. Por lo tanto, la clasificación rápida no es un algoritmo de clasificación estable (el mismo valor en el proceso de clasificación es diferente de la matriz original es inestable ).

muestre el código , para su referencia:

Pensamiento de contraste

Tanto la clasificación rápida como la combinación utilizan ideas para dividir y conquistar. Las fórmulas recursivas y los códigos recursivos también son muy similares. ¿Cuál es la diferencia entre ellos?

Se puede encontrar que el proceso de fusión y clasificación es de abajo hacia arriba , primero se ocupa de los subproblemas y luego se fusiona. La clasificación rápida es lo opuesto, su procesamiento es de arriba a abajo , particionando primero y luego subproblemas. Aunque la ordenación por fusión es estable, la complejidad temporal es un algoritmo de ordenación O (nlogn), pero es un algoritmo de ordenación sin lugar . La razón principal por la que la combinación es un algoritmo de clasificación fuera de lugar es que la función de combinación no se puede ejecutar in situ. La clasificación rápida puede realizar la clasificación in situ mediante el diseño de una función de partición in situ inteligente, que resuelve el problema de la combinación y la clasificación que ocupan demasiada memoria.

Extensión: preguntas comunes de la entrevista

Encuentre el K-ésimo elemento más grande en la matriz desordenada en O (n) complejidad de tiempo. Por ejemplo, para un conjunto de datos como 4, 2, 5, 12, 3, el tercer elemento más grande es 4.

Para resolver este problema primero, no hay duda de que todavía tenemos que pensar en dividir y conquistar y dividir.

Elegimos el último elemento A [n-1] del intervalo de matriz A [0 ... n-1] como pivote, y dividimos la matriz A [0 ... n-1] en su lugar, de modo que la matriz se divida en tres partes, A [0 … P-1], A [p], A [p + 1… n-1].

Si p + 1 = K, entonces A [p] es el elemento a resolver; si K> p + 1, significa que el K-ésimo elemento más grande aparece en el intervalo de A [p + 1… n-1], entonces seguimos las ideas anteriores Búsqueda recursiva en el intervalo A [p + 1 ... n-1]. De manera similar, si K <p + 1, buscaremos en el intervalo A [0 ... p-1].

Puede preguntar: ¿Por qué la complejidad temporal de la idea de solución mencionada anteriormente es O (n)?

Para la primera búsqueda de partición, necesitamos particionar una matriz de tamaño ny necesitamos atravesar n elementos. Para la búsqueda de la segunda partición, solo necesitamos particionar una matriz de tamaño n / 2 y necesitamos atravesar n / 2 elementos. Por analogía, el número de elementos transversales de la partición es n / 2, n / 4, n / 8, n / 16 ... hasta que el intervalo se reduce a 1.

Si sumamos el número de elementos atravesados ​​por cada partición, es: n + n / 2 + n / 4 + n / 8 + ... + 1. Esta es la suma de una secuencia geométrica, y la suma final es igual a 2n-1. Por tanto, la complejidad temporal de la solución anterior es O (n).

resumen

La clasificación por combinación y la clasificación rápida son dos algoritmos de clasificación un poco más complicados. Ambos utilizan la idea de dividir y conquistar. El código se implementa mediante recursividad y el proceso es muy similar. La clave para comprender la ordenación por combinación es comprender la fórmula de recursión y la función de combinación merge (). De la misma manera, la clave para comprender la ordenación rápida es comprender la fórmula de recursión, así como la función de partición partition ().

El algoritmo de clasificación por fusión es un algoritmo de clasificación con una complejidad de tiempo relativamente estable en cualquier circunstancia, lo que también hace que tenga un defecto fatal, es decir, la clasificación por fusión no es un algoritmo de clasificación en el lugar, y la complejidad del espacio es relativamente alta, que es O (n). Debido a esto, no se ha utilizado mucho en la clasificación rápida.

 

Lectura recomendada

Recientemente entrevisté a Byte y BAT, y compilé un material de entrevista "Java Interview BAT Clearance Manual", que cubre las tecnologías centrales de Java, JVM, concurrencia de Java, SSM, microservicios, bases de datos, estructuras de datos, etc. Método de obtención: haga clic en "Viendo", siga la cuenta oficial y responda al 666 para recibir, se proporcionará más contenido uno tras otro

Supongo que te gusta

Origin blog.csdn.net/taurus_7c/article/details/105179184
Recomendado
Clasificación