Búsqueda binaria recursiva en Python -- python campo con algorithm campo con recursion campo con binary-search camp codereview Relacionados El problema

Recursive binary search in Python


8
vote

problema

Español

He implementado una búsqueda binaria recursiva en Python e intenté implementar alguna verificación a mi código. Aparte de eso, ¿hay alguna optimización que me esté perdiendo?

  def isSorted(ary):     for i in range(1, len(ary)):         if (ary[i] < ary[i - 1]):             return False     return True   def binSearch(ary, elem, first, last):     if not isSorted(ary):         raise AssertionError("Elements are not in sorted order!")     mid = (first + last) / 2     if (ary[mid] < elem):         return binSearch(ary, elem, mid + 1, last)     elif (ary[mid] > elem):         return binSearch(ary, elem, first, mid - 1)     else:         return mid  ary = (1, 2, 3, 4, 5, 6, 7) print binSearch(ary, 5, 0, len(ary) -1)   
Original en ingles

I have implemented a recursive binary search in Python and tried to implement some verification to my code. Other than that, is there any optimization I am missing?

def isSorted(ary):     for i in range(1, len(ary)):         if (ary[i] < ary[i - 1]):             return False     return True   def binSearch(ary, elem, first, last):     if not isSorted(ary):         raise AssertionError("Elements are not in sorted order!")     mid = (first + last) / 2     if (ary[mid] < elem):         return binSearch(ary, elem, mid + 1, last)     elif (ary[mid] > elem):         return binSearch(ary, elem, first, mid - 1)     else:         return mid  ary = (1, 2, 3, 4, 5, 6, 7) print binSearch(ary, 5, 0, len(ary) -1) 
           

Lista de respuestas

9
 
vote
vote
La mejor respuesta
 

especifique los requisitos en el frente

El punto completo de la búsqueda binaria es que es return new Func<Dictionary<string, string>>[] { MyObjectPropertyConstants.BeginTime, MyObjectPropertyConstants.EndTime }.Select(f => AssembleMyObject(key, "@" + f.Name, f()).ToList(); 3 . Si va a verificar que la matriz se ordena primero, también puede hacer una búsqueda lineal. Sin mencionar que lo está haciendo aún peor, ya que está comprobando que la matriz se ordena en todas las llamadas recursivas , así que ahora tenemos una búsqueda 998877765555443314 . La búsqueda binaria debe ser asumir una lista ordenada. Si el usuario no proporciona uno, es basura en la basura.

use los valores predeterminados apropiados

SERVICIOS DE USO, tiene más sentido escribir:

  return new Func<Dictionary<string, string>>[] {     MyObjectPropertyConstants.BeginTime,     MyObjectPropertyConstants.EndTime }.Select(f => AssembleMyObject(key, "@" + f.Name, f()).ToList(); 5  

así que hagamos:

  return new Func<Dictionary<string, string>>[] {     MyObjectPropertyConstants.BeginTime,     MyObjectPropertyConstants.EndTime }.Select(f => AssembleMyObject(key, "@" + f.Name, f()).ToList(); 6  

¿Qué pasa si no se encuentra?

Debe tener un caso de error, si return new Func<Dictionary<string, string>>[] { MyObjectPropertyConstants.BeginTime, MyObjectPropertyConstants.EndTime }.Select(f => AssembleMyObject(key, "@" + f.Name, f()).ToList(); 7 , debe devolver return new Func<Dictionary<string, string>>[] { MyObjectPropertyConstants.BeginTime, MyObjectPropertyConstants.EndTime }.Select(f => AssembleMyObject(key, "@" + f.Name, f()).ToList(); 8 . De lo contrario, tienes una recursión infinita.

Solución completa:

  return new Func<Dictionary<string, string>>[] {     MyObjectPropertyConstants.BeginTime,     MyObjectPropertyConstants.EndTime }.Select(f => AssembleMyObject(key, "@" + f.Name, f()).ToList(); 9  
 

Specify Requirements Up Front

The whole point of binary search is that it's O(lg N). If you're going to check that the array is sorted first, you may as well do a linear search. Not to mention that you're doing even worse here since you're checking that the array is sorted on every recursive call, so now we have an O(N lg N) search. Binary search should assume a sorted list. If the user doesn't provide one, it's garbage in garbage out.

Use appropriate defaults

Usage-wise, it makes more sense to just write:

idx = binSearch(ary, 5) 

So let's do:

def binSearch(ary, elem):     def recurse(first, last):         ...      return recurse(0, len(ary)-1) 

What if it's not found?

You should have an error case, if first > last, you should return None. Otherwise, you have infinite recursion.

Full solution:

def binSearch(ary, elem):     def recurse(first, last):         mid = (first + last) / 2          if first > last:             return None         elif (ary[mid] < elem):             return recurse(mid + 1, last)         elif (ary[mid] > elem):             return recurse(first, mid - 1)         else:             return mid       return recurse(0, len(ary)-1) 
 
 
 
 
4
 
vote

Está haciendo lo que a menudo se llama una búsqueda binaria de tres vías, ya que en cada iteración tiene tres resultados posibles: más pequeño, más grande o igual. Esto puede parecer ventajoso, porque si encuentra el artículo temprano, se puede hacer con tan poco como una llamada recursiva. La alternativa es nunca verificar nunca la igualdad, y seguir dividiendo la matriz a la mitad hasta que se quede con un solo elemento. Esto tiene la ventaja de que, en cada iteración, solo realiza una comparación en lugar de dos, pero siempre tendrá que hacer las llamadas recursivas completas.

En promedio, saldrás por delante si haces las iteraciones completas de $ log n $ cada vez. La forma en que aprendí a implementar esto, es usar un índice para el primer elemento para buscar, y un índice más allá del último elemento para buscar:

  def binsearch(haystack, needle, lo=0, hi=None):     if hi is None:         hi = len(haystack)     if hi - lo <= 1:         if haystack[lo] == needle:             return lo         else:             raise ValueError('needle {} not in haystack'.format(needle))     mid = lo + (hi - lo) // 2     if haystack[mid] < needle:         lo = mid + 1     else:         hi = mid     return binsearch(haystack, needle, lo, hi)   

Si en lugar de elevar un error, devuelve lo sin comprobar si el 9988776655544332 está de hecho en el haystack , las cosas comienzan a ser interesantes: < / p>

  def binsearch_left(haystack, needle, lo=0, hi=None):     if hi is None:         hi = len(haystack)     if hi - lo <= 1:         return lo     mid = lo + (hi - lo) // 2     if haystack[mid] < needle:         lo = mid + 1     else:         hi = mid     return binsearch_left(haystack, needle, lo, hi)   

Esta función devuelve la primera posición en la que podría insertar needle , y mantener ordenado el

9988776655544336 , que también será la primera ocurrencia de needle En haystack Si hay valores repetidos. El giro limpio es que, al reemplazar un solo < con un lo0 Usted obtiene lo siguiente:

  lo1  

Esta función devuelve la última posición en la que lo2 podría ser insertado en lo3 y aún mantengalo ordenado, que corresponde a uno pasado la última aparición de lo4 en Haystack Si realmente está allí.

No solo este enfoque es más rápido que el enfoque de búsqueda binaria de tres vías, también le brinda mucho mejor devoluciones definidas si el 99887766555443315 tiene entradas repetidas: la primera o la última, no un indefinido.

No es de extrañar que la biblioteca estándar de Python's lo6 Paquete Implementa lo7 y lo8 Siguiendo estas ideas.

Tenga en cuenta que escribir estas funciones iterativamente es extremadamente simple, y una opción mucho mejor:

  lo9  

Además, tenga en cuenta que estoy informando needle0 como needle1 , NO needle2 . Esto no es relevante en Python, porque los enteros nunca se desborden. Pero en otros idiomas, el formulario que he usado nunca se desbordará si needle3 y 99887776655443324 sonboth valores positivos. Esta es una error más o menos famoso < / a>, que se envió con bibliotecas estándar durante años, hasta que alguien lo descubrió.

 

You are doing what is often called a three-way binary search, as in each iteration you have three possible results: smaller, larger or equal. This may seem advantageous, because if you find the item early, you may be done with as little as one recursive call. The alternative is to never check for equality, and keep dividing the array in half until you are left with a single item. This has the advantage that, in each iteration, you only do one comparison instead of two, but you will always have to do the full \$\log n\$ recursive calls.

On average you will come out ahead if you do the full \$\log n\$ iterations every time. The way I learned to implement this, is to use an index to the first item to search, and an index past the last item to search for:

def binsearch(haystack, needle, lo=0, hi=None):     if hi is None:         hi = len(haystack)     if hi - lo <= 1:         if haystack[lo] == needle:             return lo         else:             raise ValueError('needle {} not in haystack'.format(needle))     mid = lo + (hi - lo) // 2     if haystack[mid] < needle:         lo = mid + 1     else:         hi = mid     return binsearch(haystack, needle, lo, hi) 

If instead of raising an error you return lo without checking if the needle is indeed in the haystack, things start getting interesting:

def binsearch_left(haystack, needle, lo=0, hi=None):     if hi is None:         hi = len(haystack)     if hi - lo <= 1:         return lo     mid = lo + (hi - lo) // 2     if haystack[mid] < needle:         lo = mid + 1     else:         hi = mid     return binsearch_left(haystack, needle, lo, hi) 

This function returns the first position in which you could insert needle, and keep the haystack sorted, which will also be the first occurrence of needle in haystack if there are repeated values. The neat twist is that, by replacing a single < with a <= you get the following:

def binsearch_right(haystack, needle, lo=0, hi=None):     if hi is None:         hi = len(haystack)     if hi - lo <= 1:         return lo     mid = lo + (hi - lo) // 2     if haystack[mid] <= needle:  # Have replaced < with <=         lo = mid + 1     else:         hi = mid     return binsearch_right(haystack, needle, lo, hi) 

This functions returns the last position in which needle could be inserted in haystack and still keep it sorted, which corresponds to one past the last occurrence of needle in haystack if it indeed is there.

Not only is this approach often times faster than the three-way binary search approach, it also gives you much better defined returns if the haystack has repeated entries: either the first or the last, not an undefined one.

It is no surprise that Python's standard library's bisect package implements bisect_left and bisect_right following these ideas.

Note that writing this functions iteratively is extremely simple, and a much, much better option:

def binsearch_left(haystack, needle, lo=0, hi=None):     if hi is None:         hi = len(haystack)     while hi - lo > 1:         mid = lo + (hi - lo) // 2         if haystack[mid] < needle:             lo = mid + 1         else:             hi = mid     return lo 

Also, note that I am computing mid as lo + (hi - lo) // 2, not (lo + hi) // 2. This is not relevant in Python, because integers never overflow. But in other languages, the form I have used will never overflow if lo and hi areboth positive values. This is a more or less famous bug, that was shipped with standard libraries for years, until someone figured it out.

 
 
   
   
2
 
vote

Tu código está todo mal.

Escribí este código de tiempo:

  needle5  

La salida es:

  needle6  

Si observa los tiempos necesarios, se están incrementando linealmente con el tamaño de entrada (si la entrada es 10 veces más grande, el tiempo necesario es aproximadamente 10 veces más grande). Todo el punto de una búsqueda binaria es la complejidad logarítmica . Debe haber un error serio en su código.


Después de algún análisis, estoy de acuerdo con @Barry, el error serio es:

  needle7  

se ejecuta en tiempo lineal para que toda la complejidad del tiempo se degrada. Quitándolo los tiempos:

  needle8  

Alrededor de 6 órdenes de magnitud más rápido para grandes entradas y solo obtener relativamente más rápido.

 

Your code is all wrong.

I wrote this timing code:

for exp in range(1, 7):     ary = list(range(10**exp))     start = time.time()     res = binSearch(ary, 5, 0, len(ary) -1)     print("It took {} for 10**{}. Result = {}".format(         time.time() - start, exp, res)) 

The output is:

It took 3.0279159545898438e-05 for 10**1. Result = 5 It took 8.916854858398438e-05 for 10**2. Result = 5 It took 0.002868175506591797 for 10**3. Result = 5 It took 0.023810386657714844 for 10**4. Result = 5 It took 0.2799966335296631 for 10**5. Result = 5 It took 3.80861234664917 for 10**6. Result = 5 

If you look at the times needed, they are incrementing linearly with the input size (If the input is 10 times bigger, the time needed is about 10 times bigger). The whole point of a binary search is the logarithmic time complexity. There must be a serious bug in your code.


After some analysis, I agree with @Barry, the serious bug is:

if not isSorted(ary):     raise AssertionError("Elements are not in sorted order!") 

It runs in in linear time so the whole time complexity degrades. Removing it gives me the times:

It took 1.1444091796875e-05 for 10**1. Result = 5 It took 1.1920928955078125e-05 for 10**2. Result = 5 It took 1.8835067749023438e-05 for 10**3. Result = 5 It took 1.9788742065429688e-05 for 10**4. Result = 5 It took 3.123283386230469e-05 for 10**5. Result = 5 It took 3.695487976074219e-05 for 10**6. Result = 5 

About 6 orders of magnitude faster for big inputs and only getting relatively faster.

 
 

Relacionados problema

5  BinarySearch Kata en Ruby  ( Binarysearch kata in ruby ) 
Acabo de completar esta búsqueda binaria Kata, que tiene algunos requisitos extraños, y pasé un poco "limpiando" mi código y haciéndolo más compacto. Agradece...

1  Retroalimentación de búsqueda binaria  ( Binary search feedback ) 
He escrito un algoritmo de búsqueda binario, pero parece ser un poco diferente a otras personas que he visto. Esperaba que la comunidad pudiera darme algunos ...

3  ¿Es esta búsqueda binaria cerca de Idiomatic OCAML?  ( Is this binary search close to idiomatic ocaml ) 
let binsearch ~arr ~cmp x = ( let cmpx = cmp x in (* Assuming arr is ordered according to cmp, then (bs min max) is an *) (* index of a value v...

2  Eliminación de un nodo en un árbol de búsqueda binario  ( Deletion of a node in a binary search tree ) 
Estoy buscando ver si mi implementación del método de eliminación / eliminación en un árbol de búsqueda binario es suficiente, legible y se ejecuta en un tiem...

2  Encontrar el subconjunto máximo de intervalos no superpuestos  ( Finding the max subset of non overlapping intervals ) 
¿Cómo podría este código ser más limpio? Creo que la forma en que manejo las interfaces y la búsqueda binaria podría mejorarse. Estoy tratando de entender cóm...

5  Búsqueda binaria: primera / última / ocurrencia aleatoria  ( Binary search first last random occurrence ) 
He escrito un código para "búsqueda binaria" una lista, y devolver el primera ocurrencia del objetivo: def bsearch(a, left, right, target, first_or_last ...

7  Búsqueda binaria de Java (y obtenga índice si no está allí) Revisión  ( Java binary search and get index if not there review ) 
Quiero hacer una búsqueda binaria y si el objetivo no está allí, obtenga el índice de donde debería haber sido. Este es el código en el que se me ocurrió. P...

5  Búsqueda binaria eficiente  ( Efficient binary search ) 
Mi implementación: 157 Implementación normal: 158 La diferencia de ser mi implementación hace una conjetura en el índice del valor en función de l...

2  Búsqueda binaria, usando  ( Binary search using remove ) 
Soy consciente de que existe una función en la biblioteca estándar. Por favor, dame comentarios sobre buenos convenciones y estándares de codificación. fun...

10  Encuentre el duplicado en una matriz ordenada en menos de O (n)  ( Find the duplicate in a sorted array in less than on ) 
suposiciones: La matriz está ordenada. solo hay un duplicado. La matriz solo se rellena con números [0, N], donde n es la longitud de la matriz. Eje...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos