Greitojo rikiavimo algoritmas

Greito rikiavimo algoritmas () – vienas iš rikiavimo algoritmų, pasiūlytas C. A. R. Hoare 1962 metais, naudojantis „skaldyk ir valdyk“ metodiką. 

Dėl realizavimo paprastumo ir efektyvaus atminties naudojimo, šis algoritmas labai paplitęs. Yra šiek tiek greitesnis negu sąlajos rikiavimo algoritmas ir krūvos rikiavimo algoritmas, jeigu turimas duomenų rinkinys yra atsitiktinis arba didesnio pasiskirstymo.

 Savybės 
Algoritmas yra nestabilus, jis naudojasi skaldyk ir valdyk paradigma, pagrindinė idėja – išskaidžius duomenų seką į dvi dalis, kad vienoje dalyje visi elementai būtų mažesni už kitos dalies elementus, šias dvi dalis galima rikiuoti nepriklausomai viena nuo kitos. Tai daroma rekursiškai. Elementas, atskiriantis dalis – slenkstis.

Teigiamos algoritmo savybės:
 beveik nenaudojama papildoma atmintis;
 laukiamu atveju algoritmo sudėtingumas yra O(N log N);
 algoritmo realizavime gali būti apibrėžti labai trumpi vidiniai ciklai.
Neigiamos algoritmo savybės:
 algoritme naudojama rekursija, todėl nepatogus, kai nėra rekursijos mechanizmo;
 blogiausiu atveju algoritmo sudėtingumas yra O(N²);
 labai jautrus programavimo klaidoms.

 Sudėtingumas 
Greito rikiavimo algoritmas geriausiai veikia, kai kiekvienąkart slenkstis skaido duomenis po lygiai. Šiuo atveju algoritmo sudėtingumas yra O(N  log N), lyginimo operacijų naudojama 2N  log2N. Tačiau esant „blogiems“ duomenims, algoritmo sudėtingumas gali siekti O(N²), dėl to siekiant nuspėjamo rezultato, šis algoritmas derinamas su kitais.

 Algoritmas 
Algoritmas veikia tokiu principu:
 Pasirenkamas tam tikras masyvo elementas (angl. pivot); geriausiai kai jis yra mediana, tačiau praktikoje tai neefektyvu
 Perkeliant elementus, masyvas suskirstomas į dvi dalis: pirmoje dalyje yra tik elementai mažesni už pivot, kitame tik didesni
 Naudojantis algoritmu, rekursyviai rikiuojami šie du masyvai
 Kai nebelieka ką rikiuoti, pagrindinis masyvas yra surikiuotas

 Algoritmo pagerinimai 
Praktikoje, norint pagreitinti šio algoritmo veikimo laiką galima taikyti keletą būdų:
 Prieš rikiavimą reikia visus masyvo skaičius atsitiktinai sukeisti vietomis. Algoritmas su tokiu pagerinimu vadinamas Randomized Quicksort ir dirba vidutiniškai O(nlogn) laiko.
 Trumpiems masyvams naudojamas įterpimo rikiavimas. Dėl rekursijos ir kitų „blogų“ faktorių Quicksort tampa ne toks efektyvus trumpiems masyvams. Todėl, jei masyve mažiau nei 12 elementų, naudojamas įterpimo rikiavimas.
 Jei paskutinis funkcijos elementas yra „iškvietimas“ šios funkcijos, tai kalbama apie „uodeginę“ rekursiją. Ją reikėtų pakeisti iteracijomis, šiuo atveju geriausiai naudoti steką.
 Po skaidymo pirmiausia rikiuojama mažesnė dalis, tai pagerina steko naudojimą, nes trumpos sekos rikiuojamos greičiau ir joms reikalingas trumpesnis stekas. Atminties reikalavimai sumažėja nuo n iki logn.

 Algoritmo ypatumai 
 Rekursijos eliminavimas – reikia kruopščiai valdyti algoritmo vykdymo eigą ir iš anksto numatyti nepalankių arba išsigimusių sekų atvejus.
 Trumpi posekiai – kai reikia rikiuoti trumpą posekį, tarkime, tokį, kad r-1=<M, tikslinga naudoti kokį nors tiesioginį metodą, pvz., įterpimą, ribinį skaičių M parenkant tokį, kokio reikia.
Slenksčio parinkimas – dažniausiai naudojamas arba didesnio iš pirmųjų dviejų nesutampančių sekos elementų principas arba vadinamasis medianos iš trijų elementų principas.

 Realizacija 
 Realizacija C kalba 
void swap(int a, int b) {
  int t = list[a];
  list[a] = list[b];
  list[b] = t;
}

int partition(int a, int b, int piv) {
  int store, i;
  int piv_value = list[piv];

  store = a;
  swap(piv, b);
  for (i = a; i < b; i++) {
    if (list[i] < piv_value) {
      swap(i, store);
      store++;
    }
  }
  swap(store, b);
  return store;
}

void sort(int a, int b) {
  int piv;

  if (a < b) {
    piv = partition(a, b, (a+b)/2);
    sort(a, piv-1);
    sort(piv+1, b);
  }
}

 Realizacija C++ kalba 
#include <functional>
#include <algorithm>
#include <iterator>

template< typename BidirectionalIterator, typename Compare >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp ) {
  if( first != last ) {
    BidirectionalIterator left  = first;
    BidirectionalIterator right = last;
    BidirectionalIterator pivot = left++;

    while( left != right ) {
      if( cmp( *left, *pivot ) ) {
         ++left;
      } else {
         while( (left != --right) && cmp( *pivot, *right ) )
           ;
         std::iter_swap( left, right );
      }
    }

    --left;
    std::iter_swap( first, left );

    quick_sort( first, left, cmp );
    quick_sort( right, last, cmp );
  }
}

template< typename BidirectionalIterator >
inline void quick_sort( BidirectionalIterator first, BidirectionalIterator last ) {
  quick_sort( first, last,
    std::less_equal< typename std::iterator_traits< BidirectionalIterator >::value_type >()
  );
}

 Realizacija Java kalba 
import java.util.Random;
public class Pavyzdys {
   private static final Random rand = new Random();
   ....

   public static void keisti(int[] masyvas, int pirmas, int antras) {
       if (pirmas != antras) {
           masyvas[antras] -= masyvas[pirmas];
           masyvas[pirmas] += masyvas[antras];
           masyvas[antras] *= -1;
           masyvas[antras] += masyvas[pirmas];
       }
   }

   public static int dalinti(int[] masyvas, int kaire, int desine) {
       int t = kaire + rand.nextInt(desine - kaire + 1);
       keisti(masyvas, t, kaire);
       int indeksas = kaire;
       for (int j = kaire + 1; j <= desine; ++j) {
           if (masyvas[j] < masyvas[kaire]) {
               ++indeksas;
               keisti(masyvas, indeksas, j);                            
           }
       }
       keisti(masyvas, kaire, indeksas);
       return indeksas;
   }

   public static void rusiuoti(int[] masyvas, int kaire, int desine) {
       if (kaire < desine) {
           int indeksas = dalinti(masyvas, kaire, desine);
           rusiuoti(masyvas, kaire, indeksas - 1);
           rusiuoti(masyvas, indeksas + 1, desine);
       }
   }

   ...

}

 Realizacija Perl kalba 
@masyvas = ( -1, 0, 7, 8, 6, 9, 5, 2, 2, -3, 4, 6, 8, 9, -2 );
rusiuoti( 0, $#masyvas );
print "Rezultatas: @masyvas";

sub keisti {
    ( $_[0], $_[1] ) = ( $_[1], $_[0] );
}

sub dalinti {
    my ($kaire, $desine, $t, $indeksas) = ($_[0], $_[1], $_[0], $_[0]);
    for ( $j = $kaire + 1 ; $j <= $desine ; ++$j ) {
        if ( $masyvas[$j] < $masyvas[$kaire] ) {
            ++$indeksas;
            keisti( $masyvas[$indeksas], $masyvas[$j] );
        }
    }
    keisti( $masyvas[$kaire], $masyvas[$indeksas] );
    return $indeksas;
}

sub rusiuoti {
    my ( $kaire, $desine ) = ( $_[0], $_[1] );
    if ( $kaire < $desine ) {
        my ($indeksas) = dalinti( $kaire, $desine );
        rusiuoti( $kaire, $indeksas – 1 );
        rusiuoti( $indeksas + 1, $desine );
    }
}

 Realizacija Python kalba 
def dalinti ( kaire, desine ):
    t = indeksas = kaire;
    for j in range( kaire + 1, desine + 1 ):
        if masyvas[j] < masyvas[kaire]:
            indeksas += 1;
            masyvas[indeksas], masyvas[j] = masyvas[j], masyvas[indeksas]
    masyvas[kaire], masyvas[indeksas] = masyvas[indeksas], masyvas[kaire]
    return indeksas;

def rusiuoti( kaire, desine ):
    if kaire < desine :
        indeksas = dalinti( kaire, desine )
        rusiuoti( kaire, indeksas - 1)
        rusiuoti( indeksas + 1, desine )

masyvas = [ -1, 0, 7, 8, 6, 9, 5, 2, 2, -3, 4, 6, 8, 9, -2 ]
rusiuoti ( 0, len( masyvas ) - 1 )
print ("Rezultatas: ", masyvas)

 Šaltiniai 

Kategorija:Rikiavimo algoritmai