Tema
/
Algoritmai ir duomenų struktūros GO
Pamoka 8 / 15 Efektyvus rūšiavimas ~60 min
Teorija

Pivotas: kur kvadratas grįžta

Palygink su spėjimu iš antro žingsnio.

Išmėtyti duomenys — viskas gerai

$ algo dc -in s10k.jsonl -order shuffled
n = 10000, order = shuffled

algorithm                   comparisons            moves         wall
merge                            120440           133616        7.5ms
quick (first pivot)              149913            75946      2.005ms
quick (median-of-3)              146520            78217      2.999ms
quick (random)                   156381            89939      2.499ms

Visi keturi ~120–156 tūkstančiai palyginimų. Septintoje pamokoje tas pats masyvas įterpimo rikiavimui kainavo 24 893 296. Skirtumas — 200 kartų, ir tai visos šios pamokos priežastis.

Greitasis rikiavimas atlieka daugiau palyginimų nei suliejimo, bet yra greitesnis: dirba vietoje, tad juda mažiau duomenų (75 946 prieš 133 616).

Surikiuoti duomenys — ir štai kur

$ algo dc -in s10k.jsonl -order sorted
n = 10000, order = sorted

algorithm                   comparisons            moves         wall
merge                             64608           133616      6.583ms
quick (first pivot)            49995000             9999    325.219ms
quick (median-of-3)              131343            70826          1ms
quick (random)                   159057            89759      2.008ms

49 995 000. Iš 149 913.

Tai 333 kartus daugiau palyginimų ir 162 kartus ilgiau — vien todėl, kad duomenys jau buvo tvarkingi.

Ir tas skaičius pažįstamas: 49 995 000 = n(n−1)/2. Tiksliai tiek pat, kiek septintos pamokos burbuliukų ir išrinkimo rikiavimai. Greitasis rikiavimas su pirmo elemento pivotu surikiuotiems duomenims yra elementarus rikiavimas.

Ta pati -sorted vėliavėlė, kuri septintoje pamokoje davė įterpimo rikiavimui geriausią atvejį, čia duoda blogiausią. Tai antrasis iš trijų atsipirkimų, pažadėtų pirmoje pamokoje.

Suliejimo rikiavimui tuo tarpu net lengviau: 64 608 vietoj 120 440. Jis neturi blogo atvejo — testas tai tikrina visoms trims tvarkoms.

Pivoto taisymas — ir kur jis nepakanka

Mediana iš trijų surikiuotus duomenis išgelbsti visiškai: 131 343 vietoj 49 995 000. 380 kartų geriau, ir net kiek mažiau nei išmėtytiems.

Bet paleisk atvirkščiai surikiuotus:

$ algo dc -in s10k.jsonl -order reverse
algorithm                   comparisons            moves         wall
merge                             69008           133616      5.004ms
quick (first pivot)            49995000         25009999    506.951ms
quick (median-of-3)             8379139          4187234     94.662ms
quick (random)                   151569            87173      2.083ms

Mediana iš trijų: 8 379 139. Ne 50 milijonų, bet ir ne 146 tūkstančiai — 57 kartus daugiau nei išmėtytiems duomenims.

Ar tai vis dar kvadratinis augimas? Pamatuok su dviem n:

n mediana iš trijų, atvirkščiai santykis
1 000 86 890
10 000 8 379 139 ×96

Dešimteriopas n — šimteriopas darbas. Taip, vis dar kvadratinis, tik su maždaug šešis kartus mažesne konstanta.

Atsitiktinis pivotas tuo pačiu metu: 10 837 → 151 569, tai ×14 — būtent tokio augimo ir tikimasi iš O(n log n).

Mediana iš trijų sutvarko tą atvejį, kurį tikrini. Atsitiktinis pivotas sutvarko ir tuos, kurių netikrinai.

Spąstai

Šis rezultatas priklauso nuo skaidymo būdo — čia naudojamas Lomuto tipo Partition. Su kitokiu skaidymu skaičiai bus kitokie. Tai ne teorinis teiginys apie medianą iš trijų apskritai, o matavimas su šituo kodu, ir būtent todėl jis įdomus: pataisymas, kuris atrodo bendras, pasirodo esąs susijęs su realizacija. Pratybose ieškosi priežasties.