Salta al contenuto
Note per Studenti Ricerca lineare e binaria

Ricerca lineare e binaria

In questa pagina 5
In questa pagina 4

Problema: data una sequenza v e un valore x, trovare un indice i con v[i] == x, o segnalare che non c'è (convenzione: restituire -1).

Ricerca lineare

Definizione (ricerca lineare). Funziona su qualunque sequenza, anche non ordinata.

def ricerca_lineare(v, x):
    for i in range(len(v)):
        if v[i] == x:
            return i
    return -1
  • Caso migliore O(1)O(1), caso peggiore O(n)O(n) (elemento assente).
  • Restituisce la prima occorrenza.
  • È ciò che fanno x in v e v.index(x).

Esempio. ricerca_lineare([4, 7, 7, 1], 7) restituisce 1 (la prima occorrenza) e ricerca_lineare([4, 7], 5) restituisce -1.

Ricerca binaria

Definizione (ricerca binaria). Richiede v ordinata. Si confronta x con l'elemento centrale e si scarta metà dell'intervallo a ogni passo.

def ricerca_binaria(v, x):
    lo, hi = 0, len(v) - 1          # intervallo di ricerca [lo, hi], estremi inclusi
    while lo <= hi:
        m = (lo + hi) // 2
        if v[m] == x:
            return m
        elif v[m] < x:
            lo = m + 1              # x, se c'è, sta a destra di m
        else:
            hi = m - 1              # x, se c'è, sta a sinistra di m
    return -1

Invariante: se x è in v, allora si trova in v[lo..hi]. Il ciclo termina perché a ogni giro l'intervallo si riduce di almeno un elemento (m + 1, m - 1); quando lo > hi l'intervallo è vuoto e x non c'è.

Costo: dopo kk iterazioni restano circa n/2kn / 2^k elementi, quindi al più ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1 iterazioni: O(log⁡n)O(\log n). Con n=106n = 10^6 bastano 20 confronti.

Esempio. ricerca_binaria([2, 5, 8, 12, 16, 23, 38], 23) restituisce 5; con n=106n=10^6 bastano al più ⌊log⁡2106⌋+1=20\lfloor\log_210^6\rfloor+1=20 iterazioni.

Esempio su v = [2, 5, 8, 12, 16, 23, 38], x = 23:

lo hi m v[m] decisione
0 6 3 12 12 < 23, lo = 4
4 6 5 23 trovato

Versione ricorsiva

python
def ricerca_binaria_ric(v, x, lo=0, hi=None):
    if hi is None:
        hi = len(v) - 1
    if lo > hi:
        return -1
    m = (lo + hi) // 2
    if v[m] == x:
        return m
    if v[m] < x:
        return ricerca_binaria_ric(v, x, m + 1, hi)
    return ricerca_binaria_ric(v, x, lo, m - 1)

Equazione del costo T(n)=T(n/2)+O(1)T(n) = T(n/2) + O(1) (vedi RicorsioneFunzioni che chiamano se stesse: caso base e passo ricorsivo, stack delle chiamate, esempi su numeri, stringhe e liste, ricorsione multipla e suo costo, divide et impera, confronto con l'iterazione.Ricorsione →, Complessità computazionaleCosto di un algoritmo in funzione della dimensione dell'input; caso peggiore, migliore e medio; notazione O-grande e classi di crescita; come contare i passi di cicli e ricorsioni; costo delle operazioni Python.Complessità computazionale →). Passare gli indici e non v[:m]: lo slicing costerebbe O(n)O(n) a ogni chiamata.

Quale usare

Modulo bisect

Definizione (modulo bisect)

import bisect
v = [2, 5, 8, 8, 12]
bisect.bisect_left(v, 8)     # 2: primo indice dove inserire 8 mantenendo l'ordine
bisect.bisect_right(v, 8)    # 4
bisect.insort(v, 7)          # inserimento ordinato: [2, 5, 7, 8, 8, 12]

bisect_left(v, x) restituisce un indice i con v[i] == x se x c'è (e i < len(v)).

Esempio. Con v = [2, 5, 8, 8, 12], bisect_left(v, 8) restituisce 2 e bisect_right(v, 8) restituisce 4.

Errori tipici

  • Ricerca binaria su una lista non ordinata: risultati sbagliati senza errori.
  • while lo < hi con estremi inclusi: l'ultimo elemento candidato non viene controllato.
  • lo = m o hi = m invece di m + 1 / m - 1: ciclo infinito quando l'intervallo ha due elementi.
  • return dentro il ciclo lineare nel ramo else (si esce dopo il primo confronto).

Versione ripasso

Ricerca lineare

Funziona su qualunque sequenza, anche non ordinata.

  • Caso migliore O(1)O(1), caso peggiore O(n)O(n).
  • Restituisce la prima occorrenza (x in v, v.index(x)).

Ricerca binaria

Richiede v ordinataSelection sort, insertion sort e bubble sort (quadratici), merge sort (n log n, divide et impera); stabilità, ordinamento sul posto, costo nei vari casi; sort e sorted con key.Algoritmi di ordinamento →. A ogni passo si scarta metà intervallo.

python
def ricerca_binaria(v, x):
    lo, hi = 0, len(v) - 1
    while lo <= hi:
        m = (lo + hi) // 2
        if v[m] == x: return m
        elif v[m] < x: lo = m + 1
        else: hi = m - 1
    return -1
  • Invariante: se x è in v, si trova in v[lo..hi].
  • Costo: O(log⁡n)O(\log n) (al più ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1 iterazioni).

Versione ricorsiva

python
def ricerca_binaria_ric(v, x, lo=0, hi=None):
    if hi is None: hi = len(v) - 1
    if lo > hi: return -1
    m = (lo + hi) // 2
    if v[m] == x: return m
    if v[m] < x: return ricerca_binaria_ric(v, x, m + 1, hi)
    return ricerca_binaria_ric(v, x, lo, m - 1)

Costo T(n)=T(n/2)+O(1)T(n) = T(n/2) + O(1) (RicorsioneFunzioni che chiamano se stesse: caso base e passo ricorsivo, stack delle chiamate, esempi su numeri, stringhe e liste, ricorsione multipla e suo costo, divide et impera, confronto con l'iterazione.Ricorsione →, Complessità computazionaleCosto di un algoritmo in funzione della dimensione dell'input; caso peggiore, migliore e medio; notazione O-grande e classi di crescita; come contare i passi di cicli e ricorsioni; costo delle operazioni Python.Complessità computazionale →). Passare gli indici, non lo slicing O(n)O(n).

Modulo bisect

python
import bisect
bisect.bisect_left(v, 8)     # primo indice per inserire 8 mantenendo l'ordine
bisect.bisect_right(v, 8)
bisect.insort(v, 7)          # inserimento ordinato

Errori tipici

  • Ricerca binaria su lista non ordinata.
  • while lo < hi con estremi inclusi (ultimo elemento non controllato).
  • lo = m o hi = m (rischio ciclo infinito).

Esercizi su questo argomento

Teoria collegata