Como encontrar o Kth maior elemento em Java
1. Introdução
Neste artigo, apresentaremos várias soluções para encontrar okth maior elemento em uma sequência de números únicos. Usaremos uma matriz de inteiros para nossos exemplos.
Também falaremos sobre a complexidade de tempo média e de pior caso de cada algoritmo.
2. Soluções
Agora vamos explorar algumas soluções possíveis - uma usando uma classificação simples e duas usando o algoritmo de seleção rápida derivado da classificação rápida.
2.1. Ordenação
Quando pensamos sobre o problema, talvezthe most obvious solution that comes to mind isto sort the array.
Vamos definir as etapas necessárias:
-
Classificar a matriz em ordem crescente
-
Como o último elemento da matriz seria o maior elemento, okth maior elemento estaria no índicexth, ondex = length(array) – k
Como podemos ver, a solução é direta, mas requer a classificação de toda a matriz. Portanto, a complexidade do tempo seráO(n*logn):
public int findKthLargestBySorting(Integer[] arr, int k) {
Arrays.sort(arr);
int targetIndex = arr.length - k;
return arr[targetIndex];
}
Uma abordagem alternativa é classificar a matriz em ordem decrescente e simplesmente retornar o elemento no índice(k-1)th:
public int findKthLargestBySortingDesc(Integer[] arr, int k) {
Arrays.sort(arr, Collections.reverseOrder());
return arr[k-1];
}
2.2. Seleção rápida
Isso pode ser considerado uma otimização da abordagem anterior. Neste, escolhemos oQuickSort para classificação. Analisando a declaração do problema, percebemos quewe don’t actually need to sort the entire array — we only need to rearrange its contents so that the kth element of the array is the kth largest or smallest.
No QuickSort, escolhemos um elemento dinâmico e o movemos para a posição correta. Também particionamos o array ao seu redor. In QuickSelect, the idea is to stop at the point where the pivot itself is the kth largest element.
Podemos otimizar o algoritmo ainda mais se não ocorrermos recorrentemente para os lados esquerdo e direito do pivô. Só precisamos repetir para um deles de acordo com a posição do pivô.
Vejamos as idéias básicas do algoritmo QuickSelect:
-
Escolha um elemento dinâmico e particione a matriz de acordo
-
Escolha o elemento mais à direita como pivô
-
Reorganize a matriz de modo que o elemento dinâmico seja colocado em seu devido lugar - todos os elementos menores que o dinâmico estariam em índices mais baixos e os elementos maiores que o dinâmico seriam colocados em índices mais altos que o dinâmico
-
-
Se o pivô for colocado no elementokth da matriz, saia do processo, pois o pivô é okth maior elemento
-
Se a posição do pivô for maior quek,, continue o processo com a submatriz esquerda, caso contrário, repita o processo com a submatriz direita
Podemos escrever lógica genérica que pode ser usada para encontrar o menor elementokth também. Definiremos um métodofindKthElementByQuickSelect() que retornará o elementokth na matriz classificada.
Se classificarmos a matriz em ordem crescente, o elementokth de uma matriz será o menor elementokth. Para encontrar okth maior elemento, podemos passark= length(Array) – k.
Vamos implementar esta solução:
public int
findKthElementByQuickSelect(Integer[] arr, int left, int right, int k) {
if (k >= 0 && k <= right - left + 1) {
int pos = partition(arr, left, right);
if (pos - left == k) {
return arr[pos];
}
if (pos - left > k) {
return findKthElementByQuickSelect(arr, left, pos - 1, k);
}
return findKthElementByQuickSelect(arr, pos + 1,
right, k - pos + left - 1);
}
return 0;
}
Agora vamos implementar o métodopartition, que escolhe o elemento mais à direita como um pivô, coloca-o no índice apropriado e particiona a matriz de forma que os elementos em índices mais baixos sejam menores que o elemento pivô.
Da mesma forma, os elementos em índices mais altos serão maiores que o elemento pivô:
public int partition(Integer[] arr, int left, int right) {
int pivot = arr[right];
Integer[] leftArr;
Integer[] rightArr;
leftArr = IntStream.range(left, right)
.filter(i -> arr[i] < pivot)
.map(i -> arr[i])
.boxed()
.toArray(Integer[]::new);
rightArr = IntStream.range(left, right)
.filter(i -> arr[i] > pivot)
.map(i -> arr[i])
.boxed()
.toArray(Integer[]::new);
int leftArraySize = leftArr.length;
System.arraycopy(leftArr, 0, arr, left, leftArraySize);
arr[leftArraySize+left] = pivot;
System.arraycopy(rightArr, 0, arr, left + leftArraySize + 1,
rightArr.length);
return left + leftArraySize;
}
Existe uma abordagem mais simples e iterativa para realizar o particionamento:
public int partitionIterative(Integer[] arr, int left, int right) {
int pivot = arr[right], i = left;
for (int j = left; j <= right - 1; j++) {
if (arr[j] <= pivot) {
swap(arr, i, j);
i++;
}
}
swap(arr, i, right);
return i;
}
public void swap(Integer[] arr, int n1, int n2) {
int temp = arr[n2];
arr[n2] = arr[n1];
arr[n1] = temp;
}
Esta solução funciona emO(n) tempo em média. No entanto, no pior caso, a complexidade do tempo seráO(n^2).
2.3. QuickSelect com partição aleatória
Essa abordagem é uma pequena modificação da abordagem anterior. Se a matriz estiver quase / totalmente classificada e se escolhermos o elemento mais à direita como um pivô, a partição dos subarrays esquerdo e direito será altamente desigual.
Este método sugerepicking the initial pivot element in a random manner. Porém, não precisamos alterar a lógica de particionamento.
Em vez de chamarpartition, chamamos o métodorandomPartition, que escolhe um elemento aleatório e o troca pelo elemento mais à direita antes de finalmente invocar o métodopartition.
Vamos implementar o métodorandomPartition:
public int randomPartition(Integer arr[], int left, int right) {
int n = right - left + 1;
int pivot = (int) (Math.random()) * n;
swap(arr, left + pivot, right);
return partition(arr, left, right);
}
Esta solução funciona melhor que o caso anterior na maioria dos casos.
A complexidade de tempo esperada do QuickSelect aleatório éO(n).
No entanto, a pior complexidade de tempo ainda permaneceO(n^2).
3. Conclusão
Neste artigo, discutimos diferentes soluções para encontrar o elementokth maior (ou menor) em uma matriz de números exclusivos. A solução mais simples é classificar a matriz e retornar o elementokth. Essa solução possui uma complexidade de tempo deO(n*logn).
Também discutimos duas variações do Quick Select. Este algoritmo não é direto, mas tem uma complexidade de tempo deO(n) em casos médios.
Como sempre, o código completo para o algoritmo pode ser encontradoover on GitHub.