Salta al contenuto
Note per Studenti Esercizio - quickselect, confronti attesi per il minimo, la mediana e il massimo

Esercizio - quickselect, confronti attesi per il minimo, la mediana e il massimo

Teoria: Analisi probabilistica di quicksort e quickselectCon pivot casuale due elementi di rango $i<j$ vengono confrontati con probabilità $2/(j-i+1)$; sommando sulle coppie il numero atteso di confronti di quicksort è esattamente $2(n+1)H_n-4n\approx2n\ln n-2{,}85,n$ (stessa formula dalla ricorrenza $C_n=n-1+\frac2n\sum_{k<n}C_k$). Quickselect trova il $k$-esimo elemento con attesa lineare, al più $4n$ confronti (mediana $\approx3{,}39,n$). Pivot fisso su input casuale equivale a pivot casuale su input qualunque.Analisi probabilistica di quicksort e quickselect → (quickselect).

Testo. Quickselect con pivot casuale cerca il kk-esimo elemento più piccolo di nn chiavi distinte.

  1. Eseguire a mano l'algoritmo su S=[7,3,9,1,5,8]S=[7,3,9,1,5,8] con k=3k=3, con pivot nell'ordine 8,3,78,3,7, contando i confronti.
  2. Calcolare con la ricorrenza esatta il numero atteso di confronti Q(n,k)Q(n,k) per n=5n=5, k=1k=1 e k=3k=3.
  3. Calcolare con la formula chiusa Q(n,k)=2(n+3+(n+1)Hn−(k+2)Hk−(n+3−k)Hn+1−k)Q(n,k)=2\big(n+3+(n+1)H_n-(k+2)H_k-(n+3-k)H_{n+1-k}\big) i casi n=10n=10, k=1,5,10k=1,5,10 e confrontarli con il costo di quicksort.
  4. Per n=1001n=1001 e la mediana (k=501k=501) confrontare con il limite 4n4n e con 2(1+ln⁡2) n2(1+\ln2)\,n.

Soluzione

1. Esecuzione. Ogni passo costa (taglia −1-1) confronti.

  • Pivot 88 su [7,3,9,1,5,8][7,3,9,1,5,8]: 55 confronti; lo=[7,3,1,5]lo=[7,3,1,5], hi=[9]hi=[9]. Poiché k=3≤∣lo∣=4k=3\le|lo|=4 si prosegue su lolo.
  • Pivot 33 su [7,3,1,5][7,3,1,5]: 33 confronti; lo=[1]lo=[1], hi=[7,5]hi=[7,5]. Poiché k=3>∣lo∣+1=2k=3>|lo|+1=2, si cerca in hihi l'elemento di rango k=3−2=1k=3-2=1.
  • Pivot 77 su [7,5][7,5]: 11 confronto; lo=[5]lo=[5], e k=1≤1k=1\le1 si prosegue su [5][5]: un solo elemento, la risposta è 55 (che è il terzo più piccolo: 1,3,5,7,8,91,3,5,7,8,9).

Totale 5+3+1=95+3+1=9 confronti.

2. Ricorrenza esatta. Il pivot ha rango rr uniforme; costa n−1n-1; se r=kr=k ci si ferma, se r>kr>k si prosegue nei r−1r-1 elementi minori con lo stesso kk, se r<kr<k nei n−rn-r maggiori con k−rk-r: Q(n,k)=n−1+1n(∑r>kQ(r−1,k)+∑r<kQ(n−r,k−r)),Q(1,⋅)=0.Q(n,k)=n-1+\frac1n\Big(\sum_{r>k}Q(r-1,k)+\sum_{r<k}Q(n-r,k-r)\Big),\quad Q(1,\cdot)=0. Valori piccoli: Q(2,1)=1Q(2,1)=1 (r=1r=1 ferma, r=2r=2 ricade su Q(1,1)=0Q(1,1)=0). Per n=5n=5, k=1k=1: Q(5,1)=4+15(Q(1,1)+Q(2,1)+Q(3,1)+Q(4,1))Q(5,1)=4+\frac15\big(Q(1,1)+Q(2,1)+Q(3,1)+Q(4,1)\big) con Q(3,1)=2+13(0+1)=73Q(3,1)=2+\frac13(0+1)=\frac73 e Q(4,1)=3+14(0+1+73)=3+14⋅103=236Q(4,1)=3+\frac14\big(0+1+\frac73\big)=3+\frac14\cdot\frac{10}3=\frac{23}6, quindi Q(5,1)=4+15(0+1+73+236)=4+15⋅436=16330≈5,433Q(5,1)=4+\frac15\big(0+1+\frac73+\frac{23}6\big)=4+\frac15\cdot\frac{43}{6}=\frac{163}{30}\approx5{,}433. Per k=3k=3 il calcolo analogo dà Q(5,3)=10115≈6,733Q(5,3)=\frac{101}{15}\approx6{,}733: la mediana costa più del minimo, perché il pivot ha più probabilità di lasciare un sottoproblema grande.

3. Formula chiusa. Con H10=2,92897H_{10}=2{,}92897:

  • k=1k=1: 2(13+11H10−3H1−12H10)=2(13−H10−3)=2(10−H10)=14,1422\big(13+11H_{10}-3H_1-12H_{10}\big)=2(13-H_{10}-3)=2(10-H_{10})=14{,}142 (cioè 2(n−Hn)2(n-H_n));
  • k=10k=10: stesso valore per simmetria, 14,14214{,}142;
  • k=5k=5: 2(13+11H10−7H5−8H6)=2(13+32,2187−15,9833−19,6)=2⋅9,6354=19,2712\big(13+11H_{10}-7H_5-8H_6\big)=2\big(13+32{,}2187-15{,}9833-19{,}6\big)=2\cdot9{,}6354=19{,}271.

Quicksort su 1010 chiavi costa 2⋅11H10−40=24,4372\cdot11H_{10}-40=24{,}437: trovare la mediana costa 79%79\% di un ordinamento a n=10n=10, ma per nn grande il rapporto scende (lineare contro nln⁡nn\ln n).

4. n=1001n=1001, k=501k=501. La formula dà Q=3339,7Q=3339{,}7, cioè 3,336 n3{,}336\,n; il limite 4n=40044n=4004 è rispettato e 2(1+ln⁡2) n=3,386 n≈3389,72(1+\ln2)\,n=3{,}386\,n\approx3389{,}7 è una buona approssimazione (differenza 1,5%1{,}5\%, dovuta ai termini di ordine inferiore).

python
import random
def qselect(a, k):
    c = 0
    while True:
        if len(a) == 1: return c
        p = random.choice(a); c += len(a) - 1
        lo = [x for x in a if x < p]; hi = [x for x in a if x > p]
        if k <= len(lo): a = lo
        elif k == len(lo) + 1: return c
        else: k -= len(lo) + 1; a = hi
print(sum(qselect(list(range(1, 1002)), 501) for _ in range(5000)) / 5000)   # circa 3340

Risultato. Esecuzione a mano: 99 confronti. Q(5,1)=16330Q(5,1)=\frac{163}{30}, Q(5,3)=10115Q(5,3)=\frac{101}{15}; n=10n=10: 14,1414{,}14 (k=1,10k=1,10) e 19,2719{,}27 (k=5k=5); n=1001n=1001, mediana: 3339,7≈3,34 n<4n3339{,}7\approx3{,}34\,n<4n.

Verifica Python: ricorrenza esatta con Fraction e formula chiusa coincidono su tutti i casi (anche n=2,10,11,20n=2,10,11,20); simulazioni: n=10n=10, k=5k=5: 19,3019{,}30; n=1001n=1001, k=501k=501: 3345,83345{,}8 su 60006000 prove.

Versione ripasso

Teoria collegata