Procurando um elemento em uma matriz classificada circular
-
26-09-2019 - |
Pergunta
Queremos procurar um determinado elemento em uma matriz classificada circular em complexidade não maior do que O(log n)
.
Exemplo: Pesquise por 13
dentro {5,9,13,1,3}
.
Minha idéia era converter a matriz circular em uma matriz de classificação regular e depois fazer uma pesquisa binária na matriz resultante, mas meu problema foi o algoritmo que eu vim foi estúpido por ser necessário O(n)
No pior caso:
for(i = 1; i < a.length; i++){
if (a[i] < a[i-1]){
minIndex = i; break;
}
}
Em seguida, o índice correspondente do elemento Ith será determinado a partir da seguinte relação:
(i + minInex - 1) % a.length
É claro que minha conversão (do algoritmo circular para regular) pode tomar O (n), por isso precisamos de uma melhor.
De acordo com a idéia de IRE_AND_CURSES, aqui está a solução em Java:
public int circularArraySearch(int[] a, int low, int high, int x){
//instead of using the division op. (which surprisingly fails on big numbers)
//we will use the unsigned right shift to get the average
int mid = (low + high) >>> 1;
if(a[mid] == x){
return mid;
}
//a variable to indicate which half is sorted
//1 for left, 2 for right
int sortedHalf = 0;
if(a[low] <= a[mid]){
//the left half is sorted
sortedHalf = 1;
if(x <= a[mid] && x >= a[low]){
//the element is in this half
return binarySearch(a, low, mid, x);
}
}
if(a[mid] <= a[high]){
//the right half is sorted
sortedHalf = 2;
if(x >= a[mid] && x<= a[high] ){
return binarySearch(a, mid, high, x);
}
}
// repeat the process on the unsorted half
if(sortedHalf == 1){
//left is sorted, repeat the process on the right one
return circularArraySearch(a, mid, high, x);
}else{
//right is sorted, repeat the process on the left
return circularArraySearch(a, low, mid, x);
}
}
Espero que isso funcione.
Solução
Você pode fazer isso aproveitando o fato de que a matriz é classificada, exceto pelo caso especial do valor do pivô e de um de seus vizinhos.
- Encontre o valor médio da matriz a.
- Se
a[0] < a[mid]
, todos os valores na primeira metade da matriz são classificados. - Se
a[mid] < a[last]
, todos os valores na segunda metade da matriz são classificados. - Pegue a metade classificada e verifique se o seu valor está dentro dele (compare com o máximo IDX nessa metade).
- Nesse caso, basta pesquisar na metade.
- Caso contrário, deve estar na metade não classificada. Tome a metade e repita esse processo, determinando qual metade dessa metade é classificada, etc.
Outras dicas
Não é muito elegante, mas da parte superior da minha cabeça - basta usar a pesquisa binária para encontrar o pivô da matriz girada e, em seguida, execute a pesquisa binária novamente, compensando o deslocamento do pivô. Meio bobo para executar duas pesquisas completas, mas cumpre a condição, pois O (log n) + o (log n) == O (log n). Mantenha -o simples e estúpido (TM)!
Este é um exemplo que funciona em Java. Como essa é uma matriz classificada, você aproveita isso e executa uma pesquisa binária, no entanto, ela precisa ser ligeiramente modificada para atender à posição do pivô.
O método se parece com o seguinte:
private static int circularBinSearch ( int key, int low, int high )
{
if (low > high)
{
return -1; // not found
}
int mid = (low + high) / 2;
steps++;
if (A[mid] == key)
{
return mid;
}
else if (key < A[mid])
{
return ((A[low] <= A[mid]) && (A[low] > key)) ?
circularBinSearch(key, mid + 1, high) :
circularBinSearch(key, low, mid - 1);
}
else // key > A[mid]
{
return ((A[mid] <= A[high]) && (key > A[high])) ?
circularBinSearch(key, low, mid - 1) :
circularBinSearch(key, mid + 1, high);
}
}
Agora, para aliviar as preocupações, aqui está uma pequena classe que verifica o algoritmo:
public class CircularSortedArray
{
public static final int[] A = {23, 27, 29, 31, 37, 43, 49, 56, 64, 78,
91, 99, 1, 4, 11, 14, 15, 17, 19};
static int steps;
// ---- Private methods ------------------------------------------
private static int circularBinSearch ( int key, int low, int high )
{
... copy from above ...
}
private static void find ( int key )
{
steps = 0;
int index = circularBinSearch(key, 0, A.length-1);
System.out.printf("key %4d found at index %2d in %d steps\n",
key, index, steps);
}
// ---- Static main -----------------------------------------------
public static void main ( String[] args )
{
System.out.println("A = " + Arrays.toString(A));
find(44); // should not be found
find(230);
find(-123);
for (int key: A) // should be found at pos 0..18
{
find(key);
}
}
}
Que lhe dão uma saída de:
A = [23, 27, 29, 31, 37, 43, 49, 56, 64, 78, 91, 99, 1, 4, 11, 14, 15, 17, 19]
key 44 found at index -1 in 4 steps
key 230 found at index -1 in 4 steps
key -123 found at index -1 in 5 steps
key 23 found at index 0 in 4 steps
key 27 found at index 1 in 3 steps
key 29 found at index 2 in 4 steps
key 31 found at index 3 in 5 steps
key 37 found at index 4 in 2 steps
key 43 found at index 5 in 4 steps
key 49 found at index 6 in 3 steps
key 56 found at index 7 in 4 steps
key 64 found at index 8 in 5 steps
key 78 found at index 9 in 1 steps
key 91 found at index 10 in 4 steps
key 99 found at index 11 in 3 steps
key 1 found at index 12 in 4 steps
key 4 found at index 13 in 5 steps
key 11 found at index 14 in 2 steps
key 14 found at index 15 in 4 steps
key 15 found at index 16 in 3 steps
key 17 found at index 17 in 4 steps
key 19 found at index 18 in 5 steps
Você tem três valores, l
,m
,h
Para os valores nos índices baixos, médios e altos da sua pesquisa. Se você acha que continuaria procurando cada possibilidade:
// normal binary search
l < t < m - search(t,l,m)
m < t < h - search(t,m,h)
// search over a boundary
l > m, t < m - search(t,l,m)
l > m, t > l - search(t,l,m)
m > h, t > m - search(t,m,h)
m > h, t < h - search(t,m,h)
É uma questão de considerar onde o valor alvo poderia estar e pesquisar na metade do espaço. No máximo, metade do espaço terá a embalagem e é fácil determinar se o valor alvo está ou não nessa metade ou em outra.
É uma espécie de meta pergunta - você pensa em pesquisas binárias em termos de como é frequentemente apresentado - encontrando um valor entre dois pontos, ou mais geralmente como uma divisão repetida de um espaço de pesquisa abstrato.
Você pode usar a pesquisa binária para encontrar a localização do menor elemento e reduzi -lo para O (log n).
Você pode encontrar o local por (este é apenas um esboço do algoritmo, é impreciso, mas você pode obter a ideia):
1. I <- 1
2. J <- n
3. Enquanto eu <j
3.1. k <- (ji) / 2
3.2. Se arr [k] <arr [i] então j <- k 3.3. mais eu <- k
Depois de encontrar a localização do menor elemento, você pode tratar a matriz como duas matrizes classificadas.
Você apenas usa uma pesquisa binária simples como se fosse uma matriz de classificação regular. O único truque é que você precisa girar os índices de matriz:
(index + start-index) mod array-size
onde o índice de início é o deslocamento do primeiro elemento na matriz circular.
public static int _search(int[] buff, int query){
int s = 0;
int e = buff.length;
int m = 0;
while(e-s>1){
m = (s+e)/2;
if(buff[offset(m)] == query){
return offset(m);
} else if(query < buff[offset(m)]){
e = m;
} else{
s = m;
}
}
if(buff[offset(end)]==query) return end;
if(buff[offset(start)]==query) return start;
return -1;
}
public static int offset(int j){
return (dip+j) % N;
}
Verifique este Coe,
def findkey():
key = 3
A=[10,11,12,13,14,1,2,3]
l=0
h=len(A)-1
while True:
mid = l + (h-l)/2
if A[mid] == key:
return mid
if A[l] == key:
return l
if A[h] == key:
return h
if A[l] < A[mid]:
if key < A[mid] and key > A[l]:
h = mid - 1
else:
l = mid + 1
elif A[mid] < A[h]:
if key > A[mid] and key < A[h]:
l = mid + 1
else:
h = mid - 1
if __name__ == '__main__':
print findkey()
Aqui está uma ideia, relacionada à pesquisa binária. Basta fazer backup do seu índice para o limite certo do índice de matriz, o limite do índice esquerdo é armazenado no tamanho da etapa:
step = n
pos = n
while( step > 0 ):
test_idx = pos - step #back up your current position
if arr[test_idx-1] < arr[pos-1]:
pos = test_idx
if (pos == 1) break
step /= 2 #floor integer division
return arr[pos]
Para evitar a coisa (pos == 1), poderíamos fazer backup circularmente (entrar em números negativos) e pegar (pos-1) mod n.
Eu acho que você pode encontrar o deslocamento usando este código:
public static int findOffset(int [] arr){
return findOffset(arr,0,arr.length-1);
}
private static int findOffset(int[] arr, int start, int end) {
if(arr[start]<arr[end]){
return -1;
}
if(end-start==1){
return end;
}
int mid = start + ((end-start)/2);
if(arr[mid]<arr[start]){
return findOffset(arr,start,mid);
}else return findOffset(arr,mid,end);
}
Abaixo está uma implementação em C usando pesquisa binária.
int rotated_sorted_array_search(int arr[], int low, int high, int target)
{
while(low<=high)
{
int mid = (low+high)/2;
if(target == arr[mid])
return mid;
if(arr[low] <= arr[mid])
{
if(arr[low]<=target && target < arr[mid])
{
high = mid-1;
}
else
low = mid+1;
}
else
{
if(arr[mid]< target && target <=arr[high])
{
low = mid+1;
}
else
high = mid-1;
}
}
return -1;
}
Aqui está uma solução em JavaScript. Testou com algumas matrizes diferentes e parece funcionar. Ele basicamente usa o mesmo método descrito por IRE_and_Curses:
function search(array, query, left, right) {
if (left > right) {
return -1;
}
var midpoint = Math.floor((left + right) / 2);
var val = array[midpoint];
if(val == query) {
return midpoint;
}
// Look in left half if it is sorted and value is in that
// range, or if right side is sorted and it isn't in that range.
if((array[left] < array[midpoint] && query >= array[left] && query <= array[midpoint])
|| (array[midpoint] < array[right]
&& !(query >= array[midpoint] && query <= array[right]))) {
return search(array, query, left, midpoint - 1);
} else {
return search(array, query, midpoint + 1, right);
}
}
Pesquisa binária simples com uma pequena mudança.
Índice de matriz rotativa = (i+pivô) Tamanho
O pivô é o índice i+1 em que a [i]> a [i+1].
#include <stdio.h>
#define size 5
#define k 3
#define value 13
int binary_search(int l,int h,int arr[]){
int mid=(l+h)/2;
if(arr[(mid+k)%size]==value)
return (mid+k)%size;
if(arr[(mid+k)%size]<value)
binary_search(mid+1,h,arr);
else
binary_search(l,mid,arr);
}
int main() {
int arr[]={5,9,13,1,3};
printf("found at: %d\n", binary_search(0,4,arr));
return 0;
}
Um método simples em Ruby
def CircularArraySearch(a, x)
low = 0
high = (a.size) -1
while low <= high
mid = (low+high)/2
if a[mid] == x
return mid
end
if a[mid] <= a[high]
if (x > a[mid]) && (x <= a[high])
low = mid + 1
elsif high = mid -1
end
else
if (a[low] <= x) && (x < a[mid])
high = mid -1
else
low = mid +1
end
end
end
return -1
end
a = [12, 14, 18, 2, 3, 6, 8, 9]
x = gets.to_i
p CircularArraySearch(a, x)
Embora a resposta aprovada seja ideal, mas também podemos fazer com um algoritmo semelhante e mais limpo.
- Execute uma pesquisa binária para encontrar o elemento pivô (onde a matriz é girada).
O(logn)
- Metade esquerda do pivô será classificada em ordem decrescente, execute uma pesquisa binária atrasada aqui pela chave.
O(logn)
- A metade direita do pivô será classificada em ordem crescente, execute uma pesquisa binária para a frente nesta metade para a chave.
O(logn)
- Retorno encontrado Índice de chave das etapas 2 e 3.
Complexidade total de tempo: O(logn)
Pensamentos bem -vindos.
Guirgis: É coxo postar uma pergunta de entrevista, acho que você não conseguiu o emprego :-(
Use uma função CMP especial e você precisa de apenas uma passagem com pesquisa binária regular. Algo como:
def rotatedcmp(x, y):
if x and y < a[0]:
return cmp(x, y)
elif x and y >= a[0]:
return cmp(x, y)
elif x < a[0]:
return x is greater
else:
return y is greater
Se você puder depender do INT sub -fluxo, subtraia um [0] - min_int de cada elemento, pois é acessado e use a comparação regular.