Algoritmi za sortiranje Sorting Algorithms

					Složenost algoritama i
sortiranje
Algoritam

   Algoritam je opis za
    rešavanje nekog problema.
   Reč dolazi iz prezimena
    persijskog matematičara Al
    Horezmija.
   Algoritam je bio izraz koji
    opisuje način računanja
    decimalnim brojevima
    uvedenim oko 1600. godine
    u Evropi.
                                  Dijagram toka se često koriste za
                                       predstavljanje algoritma
O nastanku
   Algoritam je u matematiku uveo arapski
    matematičar Muhamed Al Horezmi.
   Napisao je knjigu Al Horezmi o indijskoj
    veštini računanja gde u arapsku
    matematiku uvodi indijske cifre i decimalni
    brojni sistem.
   Ova knjiga biva kasnije prevedena na
    latinski kao Algoritmi de numero indorum.
   Od lošeg latinskog prevoda njegovog
    prezimena i potiče reč algoritam, i dugo je
    označavala postupak za račun sa
    decimalnim brojnim sistemom (i indijskim
    odnosno, kako se kasnije pričalo, arapskim
    ciframa).
Algoritam

   U novije vreme, algoritam je pojam koji se gotovo
    isključivo vezuje za informatiku i, mada ne postoji
    jedinstvena opšteprihvaćena definicija,
    podrazumeva se da je u pitanju nekako opisana
    procedura za obavljanje posla.
   U tu svrhu se definišu algoritamski jezici-
    formalizovani jezici kojima se relativno lako opisuju
    postupci rešavanja problema predstavljenih
    algoritmom, npr. programski jezici Algol, Fortran i
    Kobol.
   U matematičkoj logici je algoritam generalizovan
    pojam i odnosi se na postupak za postupno
    pretvaranje nizova znakova.
Definicija
   Algoritam je konačna i precizno definisana procedura, niz dobro
    definisanih pravila, kojom se ulazne vrednosti transformišu u
    izlazne, ili se opisuje izvršavanje nekog postupka.
   Danas se reč algoritam često vezuje za pojam računarstva mada
    uopšteno algoritam možemo smatrati kao uputstvo kako rešiti
    neki zadatak ili problem.
   Uputstvo može sadržati korake koji se ponavljaju više puta ili
    korake kada treba doneti neku odluku, na osnovu nekog
    kriterijuma.
   Dobro uputstvo predviĎa i postupak kada nisu svi uslovi ispunjeni
   Korektno izvršavanje svakog koraka ne rešava zadatak ako je
    algoritam bio pogrešan.
Definicija

   Različiti algoritmi mogu rešiti isti problem različitim
    nizom postupaka uz manje ili više napora, za kraće
    ili duže vreme. S obzirom da je rezultat isti, algoritmi
    se mogu porediti po svojoj efikasnosti, brzini ili
    kompleksnosti.
   Algoritmi mogu biti prikazani ili realizovani na više
    načina:
       prirodnim jezikom (razumljiv samo govornicima tog jezika)
       Grafički, dijagramom toka (blok-dijagramom) ili strukturnim
        dijagramima ili
       tekstualno - pseudokom, veštačkim precizno definisanim
        jezikom koji liči na programski jezik
       odgovarajućim programskim jezikom.
Primeri algoritama iz svakodnevnog života

        Primer algoritma iz svakodnevnog života je kuvanje čaja. Svaki
         korak pripremanja čaja mora biti izvršen pravilno kako bi se
         moglo preći na sledeći i u konačnom vremenu dobiti skuvan čaj.
    1.     Uzeti lonče i sipati vodu.
    2.     Uključiti ringlu.
    3.     Sačekati dok voda ne proključa.
    4.     Kad voda proključa, skinuti lonče i isključiti ringlu.
    5.     Staviti kesicu čaja u lonče.
    6.     Po želji, dodati kašiku šećera, mleko ili limun.
    7.     Sipati čaj u šolju.
        Iz ovog jednostavnog primera može se videti postupnost i
         konačnost algoritma. Naime, nema previše koristi od algoritma
         koji se nikada ne završava ili algoritma čije se naredbe izvode
         nepredvidljivo ili im je rezultat nepredvidljiv.
Primeri algoritama

        Primer kompleksnijeg algoritma je Euklidov
         algoritam za odreĎivanje najvećeg
         zajedničkog delioca:
    1.    Podeliti broj a brojem b pri čemu se dobija količnik c i ostatak r
    2.    Broj a uzima vrednost broja b
    3.    Broj b uzima vrednost r
    4.    Ponavljati sve dok ostatak r ne bude jednak 0. Najveći zajednički delilac je
          trenutna vrednost broja a
Istorija
   Prvi algoritam koji se može smatrati procedurom čija je namena račun
    na automatskoj mašini je napisala Ejda Bajron 1842. godine.
   U pitanju je algoritam za odreĎivanje Bernulijevih brojeva na analitičkoj
    mašini Čarlsa Bebidža. Ta mašina nikad nije proradila, ali je njen
    algoritam ostavio dubok trag.
   Danas se to priznaje kao prvi računarski algoritam, a Ejda Bajron , ledi
    Lavlejs, je priznata kao prvi programer u istoriji. U njenu čast je i jedan
    od najkompleksnijih programskih jezika dobio naziv Ada.
   Značajan napredak u formalizaciji uvoĎenja algoritma u matematiku i
    logiku je učinio Alan Tjuring u svojim radovima definisanjem Tjuringove
    mašine.
   To je primitivan automat, misaona tvorevina, ali poseduje mogućnost
    izvoĎenja nekoliko operacija koje su dovoljne za izvoĎenje skoro svih
    algoritama. Čerč-Tjuringova teza tvrdi da se svaki algoritam koji je
    dobro definisan može izvršiti na takvoj mašini.
Osobine

   Algoritmi poseduju sledeće osobine:
       diskretnost — u odvojenim koracima se izvršavaju
        diskretne operacije algoritma koji vode ka konačnom cilju;
       rezultativnost — označava sposobnost algoritma da posle
        konačnog broja koraka daje izlazne podatke;
       determinisanost — za iste ulazne podatke algoritam uvek
        generiše iste vrednosti na izlazima i
       masovnost — algoritam je primenljiv na veći broj ulaznih
        vrednosti.
Složenost algoritama
   Za rešavanje istog problema se mogu naći različiti
    algoritmi. Pitanje: koji koristiti ?
   Potrebno je odrediti meru kvaliteta algoritama da bi
    se pri izboru algoritma moglo odrediti koji je bolji.
   Meri se količina resursa koje algoritam zahteva da bi
    rešio problem.
   Dve mere koje se najčešće koriste su vreme
    potrebno za izvršenje algoritma i prostor potreban
    za pohranjivanje ulaznih podataka, meĎurezultata i
    izlaznih rezultata.
Vremenska i prostorna složenost
algoritma
   Mi ćemo se baviti samo vremenskom složenošću
    algoritama (češće se koristi u ocenjivanju kvaliteta
    algoritma)
   Prvi problem kod odreĎivanja vremenske složenosti
    algoritma je pronalaženje mere koja ne zavisi od brzine
    računara na kojem se algoritam izvodi, već samo o
    samom algoritmu
   Zato se vremenska složenost algoritma ne izražava
    vremenom potrebnim da algoritam izračuna rezultat, već
    brojem elementarnih operacija koje algoritam mora izvesti
    u proračunu
Vremenska i prostorna složenost
algoritma
   Svrha: predviĎanje vremena izračunavanja i pronalaženje
    efikasnijih algoritama.
   Pretpostavke: fiksno vreme dohvatanja sadržaja memorijske
    lokacije, vreme obavljanja elementarnih operacija
    (aritmetičke, logičke, pridruživanje, poziv funkcije) je
    ograničeno nekom konstantom kao gornjom granicom.
   Broj operacija koje algoritam izvodi zavisi od veličine ulaza
   Instance problema različitih dimenzija zahtevaju različit broj
    operacija.
   Složenost algoritma nije funkcija samo veličine ulaza, mogu
    postojati različite instance iste dužine ulaza, ali različitih
    složenosti (različite vrednosti ulaza)
Analize “a priori” i “a posteriori”

   izbor skupova podataka za iscrpno testiranje algoritma:
       ponašanje u najboljem slučaju (best case scenario)
       ponašanje u najgorem slučaju (worst case scenario)
       prosečno (tipično) ponašanje

   a priori
       trajanje izvoĎenja algoritma (u najgorem slučaju) kao vrednost funkcije
        nekih relevantnih argumenata (npr. broja podataka)

   a posteriori
       statistika dobijena merenjem na računaru
Analiza “a priori”

   procena vremena izvoĎenja, nezavisno od
    računara, programskog jezika, prevodioca
    (compilera)
   primeri:
    a) x += y;                          1

    b) for(i = 1; i <= n; i++) {        n
             x += y;
         }

    c)   for(i = 1; i <= n; i++) {      n2
           for(j = 1; j <= n; j++) {
             x += y;
           }
         }
    Funkcija složenosti algoritma
   Uvodi se notacija koja jednostavno opisuje brzinu rasta
    složenosti algoritma.
   Asimptotska ocena rasta funkcije: pri izračunavanju
    složenosti aproksimacija se vrši tako da aproksimativna
    funkcija koja je jednostavnija od same funkcije složenosti,
    dobro asimptotski opisuje rast funkcije složenosti.
   Ne zanima nas stvarni iznos funkcije složenosti, već samo
    koliko brzo ta funkcija raste, zanima nas njen red veličine
   Primer: vreme izvršavanja Gausovog algoritma za
    invertovanje matrice je proporcionalno sa n3, što znači da
    ako se red matrice udvostruči, invertovanje može trajati do 8
    puta duže
O - notacija

   f(n) = O(g(n)) ako postoje dve pozitivne konstante c i n0 takve da
    vredi f(n)  cg(n) za sve n n0
       traži se najmanje g(n) za koje to vredi
   drugim rečima, procjenjuje se trajanje kao red veličine odreĎen
    na osnovu broja podataka n, pomnoženo s nekom konstantom c
   primeri:
       n3+5n2+77n = O(n3)
       Koliki je posao preneti 1 stolicu iz sale A u salu B?
       Koliki je posao preneti n stolica iz A u B?
       Koliki je posao preneti n stolica iz A u B, s time da se kod donošenja svake nove
        stolice sve do tada donetee stolice u B moraju pomeriti, pri čemu se za
        pomeranje stolice u dvorani B ulaže isti trud kao i kod prenosa jedne stolice iz A u
        B?
        1 + 2 + 3 + 4 + ...+ n = n(n+1)/2 = n2/2 + n/2 = O (n2)
   O - notacija

       analizom ―a priori‖ dobija se vreme izvoĎenja
        algoritma O(g(n))
       ako je broj izvoĎenja operacija nekog algoritma
        zavisi od nekog ulaznog argumenta n oblika
        polinoma m-tog stepena, onda je vreme izvoĎenja
        za taj algoritam O(nm)




Algoritmi i strukture podataka, FER,                      18 / 35
   O - notacija

       vredi za dovoljno veliko n:
        O(1) < O(log n) < O(n) < O(nlog n) < O(n2) < O(n3) < ...< O(2n) < O(n!)

            O(1) znači da je vreme izvoĎenja ograničeno konstantom
            ostale vrednosti, do predzadnje, predstavljaju polinomna
             vremena izvoĎenja algoritma
              svako sledeće vreme izvoĎenja je veće za red veličine

            predzadnji izraz predstavlja eksponencijalno vreme izvoĎenja
            algoritmi koji zahtijevaju eksponencijalno vreme mogu biti
             nerešivi u razumnom vremenu, bez obzira na brzinu računara




Algoritmi i strukture podataka, FER,                                          19 / 35
Primer: traženje najvećeg člana u nizu

 int maxclan (int A[ ], int n) {
   int i, imax;
   i = n-1;
                                      petlja se uvek
                                      obavi n-1 puta
 imax =n-1;
                                       O(n)
    while (i > 0) {
     i--;
     if (A[i] > A[imax]) imax = i;}
   return imax;}
Primer: traženje zadatog elementa u nizu

 int trazi (int A[ ], int x, int n, int i) {
  // A-niz, x-trazeni, i-indeks od kojeg se trazi
   int ret;
   if (i >= n) ret = -1;
   else if (A[i] == x) ret = i;
   else ret = trazi (A, x, n, i+1);
   return ret;
 }
 - vreme izvoĎenja je O(n), ali je donja granica  (1).
    U najboljem slučaju u prvom koraku naĎe se
    traženi član niza, a u najgorem mora pregledati
    sve članove niza.
Analiza “a posteriori”
•stvarno vreme potrebno za izvođenje algoritma na
konkretnom računalu
   #include <sys\timeb.h> // gde je deklarisano

   struct timeb {
      time_t time; // broj sekundi od ponoći, 01.01.1970, UTC
      unsigned short millitm; // milisekunde
      short timezone; // razlika u minutama od UTC
      short dstflag; // <>0 ako je na snazi letnje vreme
   };

   void ftime(struct timeb *timeptr);
Analiza “a posteriori”

   u programu:
struct timeb vreme1, vreme2; long trajanjems;

ftime (&vreme1);
...
ftime (&vreme2);

trajanjems = 1000 * (vreme2.time - vreme1.time) +
               vreme2.millitm - vreme1.millitm;




   Universal Time Co-ordinated (UTC)
       novi naziv za Greenwich Mean Time (GMT)
Primeri zavisnosti trajanja od broja
podataka i složenosti
n     T(n) = n   T(n) = n lg(n)   T(n) = n2   T(n) = n3      T(n) = 2n

5     0.005 s      0.01 s       0.03 s     0.13 s        0.03 s

10    0.01 s       0.03 s        0.1 s       1 s           1 s

20    0.02 s       0.09 s        0.4 s       8 s           1 ms

50    0.05 s       0.28 s        2.5 s     125 s         13 dana

100    0.1 s       0.66 s        10 s        1 ms      4 x 1013 godina
 Što se može rešiti u zadatom vremenu
                          Veličina najvećeg slučaja problema koji se može rešiti za 1 sat
Vremenska složenost   danas raspoloživim         100 puta bržim            1000 puta bržim
                         računarom                 računarom                 računarom

        n                    N1                       100N1                    1000N1

        n2                   N2                       10 N2                    31.6 N2

        n3                   N3                      4.64 N3                    10 N3

        2n                   N4                      N4+6.64                   N4+9.97

        3n                   N5                      N5+4.19                   N5+6.29
Sortiranje

   Sortiranje – jedan od klasičnih nenumeričkih
    problema.
   Čest problem koji se javlja u obradi podataka
    bilo samostalno, bilo kao sastavni deo
    složenijih problema.
Interno sortiranje

 Razlikuje se interno sortiranje i sortiranje na
  eksternim memorijama.
 Interno sortiranje – podrazumeva se situacija gde se
  u memoriji računara nalazi vektor x(1), x(2), ... x(n)
  čije vrednosti su proizvoljni brojevi.
  Vektor je potrebno preurediti tako da bude
  zadovoljen uslov da je:
x(1)<=x(2)<=...<=x(n)     rastuće sortiranje
x(1)>=x(2)>=...>=x(n)     opadajuće sortiranje
Eksterno sortiranje

   Sortiranje na eksternim memorijama- radi se
    po pravilu o relativno velikim datotekama čiji
    zapisi sadrže polje ključa i polja podataka, a
    problem se sastoji u tome da se zapisi
    datoteke poreĎaju po rastućim (ili
    opadajućim) vrednostima ključa.
Najčešće metode sortiranja

   Metoda umetanja (Insertion sort)
   Metoda zamene suseda (Bubble sort)
   Metoda izbora (Selection sort)
   Sortiranje po lancima ekvidistantnih zapisa
    (Shell sort)
   Sortiranje po metodi kupa (Heapsort)
   Particijska metoda sortiranja (Quicksort)
   Sortiranje spajanjem (Merge sort)
Složenost algoritama za sortiranje

   Algoritmi za sortiranje mogu se podeliti u dve
    grupe prema složenosti svojih algoritama.
       O(n) predstavlja algoritam koji ima linearnu
        složenost.
       O(n2), kvadratna složenost.
Složenost algoritama
   Dve klase algoritama za sortiranje su O(n2), gde
    spadaju buble, insertion, selection i shell sort i
    O(n log n) gde spadaju heap, merge i quick sort.
    f(n)                    Tip algoritma

    const.                  konstantan




                                                     Rastuća složenost
    log2n                   logaritamski

    n                       linearan

    nlog2n                  linearno-logaritamski

    n2                      kvadratni

    nk (k>2)                stepeni

    kn (k>1)                eksponecijalni

    n!                      faktorijelni
Složenost algoritama   O(n 2)
Složenost algoritama O(nlog2n)
Bubble sort

   Poredi vrednosti dva susedna elementa i vrši
    zamenu ukoliko je potrebno. Algoritam
    ponavlja ovaj proces sve dok ne proĎe kroz
    ceo niz a da ne izvrši ni jednu zamenu. Na
    ovaj način veće vrednosti ―isplivaju‖ na kraj
    niza a manje vrednosti ―potonu‖ na početak
    niza.
   za: jednostavnost i lakoća primene.
    protiv: velika neefikasnost.
Bubble sort
Bubble sort algoritam
void bubbleSort(int numbers[ ], int array_size)
{
  int i, j, temp;
  for (i = (array_size - 1); i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
        if (numbers[j-1] > numbers[j])
        {
        temp = numbers[j-1];
        numbers[j-1] = numbers[j];
          numbers[j] = temp;
          }
        }
    }
}
Insertion sort

   Ubacuje svaki element na njegovo odgovarajuće
    mesto u finalnom nizu. Najjednostavnija primena
    zahteva dva niza – izvorni niz i niz u koji se element
    ubacuje. Kako bi se sačuvala memorija, najčešće se
    radi tako da element koji se trenutno pomera
    postavlja iza već sortiranih elemenata i ponavlja se
    zamena dok se ne postavi na odgovarajuću poziciju.
   za: relativno jednostavan i lak za primenu.
    protiv: neefikasan za velike nizove.
Insertion sort
Insertion sort algoritam
void insertionSort(int numbers[], int array_size)
{
int i, j, index;
for (i=1; i < array_size; i++)
    {
   index = numbers[i];
   j = i;
   while ((j > 0) && (numbers[j-1] > index))
    {
   numbers[j] = numbers[j-1];
    j = j - 1;
    }
   numbers[j] = index;
    }
}
Selection sort

   Radi tako da bira najmanji preostali
    nesortirani element u nizu, zatim ga menja sa
    elementom na sledećoj poziciji koja treba da
    se popuni.
   za: jednostavan i lak za primenu.
    protiv: neefikasan za velike nizove.
Selection sort
Selection sort algoritam
void selectionSort(int numbers[], int array_size)
{
int i, j;
int min, temp;
for (i = 0; i < array_size-1; i++)
   {
   min = i;
   for (j = i+1; j < array_size; j++)
       {
         if (numbers[j] < numbers[min]) min = j;
            }
                  temp = numbers[i];
            numbers[i] = numbers[min];
           numbers[min] = temp;
     }
}
Shell sort

   Najefikasniji od O(n2) algoritama ali
    istovremeno i najsloženiji (Donald Shell,
    1959.)
   Nekoliko puta prolazi kroz niz, sortirajući
    svaki put nizove istih dimenzija koristeći
    insertion sort. Svaki put prolaskom kroz niz
    uvećava se niz koji treba sortirati dok to ne
    bude ceo niz.
   za: efikasan za nizove srednje veličine.
    protiv: donekle složen algoritam.
Shell sort
Shell sort algoritam
void shellSort(int numbers[], int array_size)
 {
 int i, j, increment, temp;
increment = 3;
while (increment > 0)
   {
   for (i=0; i < array_size; i++)
   {
         j = i; temp = numbers[i];
          while ((j >= increment) && (numbers[j-increment] > temp))
          {
          numbers[j] = numbers[j - increment];
         j = j - increment;
         }
         numbers[j] = temp;
    }
   if (increment/2 != 0)
    increment = increment/2;
    else if (increment == 1)
   increment = 0;
   else increment = 1;
    }
}
Heap sort

   za: nerekurzivan, dobar izbor kada je
    potrebno sortirati ekstremno velike nizove.
    protiv: sporiji od merge i quick algoritama.
Heap sort
Heap sort algoritam
void heapSort(int numbers[], int array_size)
 {
 int i, temp;
 for (i = (array_size / 2)-1; i >= 0; i--) siftDown(numbers, i, array_size);
for (i = array_size-1; i >= 1; i--)
 {
temp = numbers[0];
 numbers[0] = numbers[i];
numbers[i] = temp;
siftDown(numbers, 0, i-1);
 }
}
void siftDown(int numbers[], int root, int bottom)
{
 int done, maxChild, temp;
 done = 0;
while ((root*2 <= bottom) && (!done))
{
 if (root*2 == bottom)
 maxChild = root * 2;
else if (numbers[root * 2] > numbers[root * 2 + 1])
 maxChild = root * 2;
 else maxChild = root * 2 + 1;
 if (numbers[root] < numbers[maxChild])
{
temp = numbers[root];
numbers[root] = numbers[maxChild];
 numbers[maxChild] = temp;
 root = maxChild;
 }
 else done = 1;
 }
 }
Merge sort

   Niz koji treba sortirati deli se na dva jednaka dela
    koji se postavljaju u zasebne nizove. Svaki niz
    rekurzivno se sortira a zatim se spajaju da bi se
    dobio finalni sortirani niz.
   Najjednostavnija primena ove metode koristi tri niza
    – po jedan niz za svaku polovinu podataka i jedan
    niz za sortirane elemente.
   za: brže sortira nego heap sort za veće nizove
    podataka.
    protiv: najmanje dvostruko veći zahtev za
    memorijom nego kod drugih metoda sortiranja,
    rekurzivan.
Merge sort
Merge sort algoritam
void mergeSort(int numbers[], int temp[], int array_size)
{
m_sort(numbers, temp, 0, array_size - 1);
}
void m_sort(int numbers[], int temp[], int left, int right) {
int mid; if (right > left)
{ mid = (right + left) / 2;
m_sort(numbers, temp, left, mid);
  m_sort(numbers, temp, mid+1, right);
merge(numbers, temp, left, mid+1, right);
  }
}
Merge sort
void merge(int numbers[], int temp[], int left, int mid, int right)
 {
int i, left_end, num_elements, tmp_pos;
 left_end = mid - 1;
 tmp_pos = left;
num_elements = right - left + 1;
 while ((left <= left_end) && (mid <= right))
 {
if (numbers[left] <= numbers[mid])
{
temp[tmp_pos] = numbers[left];
tmp_pos = tmp_pos + 1; left = left +1;
 }
else {
 temp[tmp_pos] = numbers[mid];
 tmp_pos = tmp_pos + 1; mid = mid + 1;
 }
}
Merge sort
while (left <= left_end)
{
temp[tmp_pos] = numbers[left];
left = left + 1;
tmp_pos = tmp_pos + 1;
}
while (mid <= right)
{
 temp[tmp_pos] = numbers[mid];
mid = mid + 1;
tmp_pos = tmp_pos + 1;
}
for (i=0; i <= num_elements; i++)
{
numbers[right] = temp[right];
right = right - 1;
 }
 }
Quick sort

   Algoritam vrlo jednostavan u teoriji, ali veoma je
    teško pretočiti ga u kod.
   Rekurzivni algoritam čine četiri koraka:
       Ako niz ima jedan ili manje elemenata za soritiranje, kraj.
       Odaberi jedan element u nizu za pivot (za to se najčešće
        koristi krajnje levi element niza).
       Podeli niz na dva dela – u jednom su elementi veći od
        pivota a u drugom elementi manji od pivota.
       Rekurzivno ponavljaj algoritam za obe polovine originalnog
        niza.
   za: izuzetno brz.
    protiv: veoma složen algoritam, rekurzija.
Quick sort
Quick sort algoritam
void quickSort(int numbers[], int array_size)
 {
q_sort(numbers, 0, array_size - 1);
 }
 void q_sort(int numbers[], int left, int right)
 {
 int pivot, l_hold, r_hold;
l_hold = left; r_hold = right; pivot = numbers[left];
 while (left < right) {
 while ((numbers[right] >= pivot) && (left < right))
right--;
if (left != right) {
 numbers[left] = numbers[right]; left++; }
while ((numbers[left] <= pivot) && (left < right))
left++;
Quick sort algoritam
if (left != right) {
numbers[right] = numbers[left];
right--;
}
}
numbers[left] = pivot;
 pivot = left;
left = l_hold;
 right = r_hold;
 if (left < pivot)
q_sort(numbers, left, pivot-1);
 if (right > pivot)
q_sort(numbers, pivot+1, right);
 }

				
DOCUMENT INFO
Shared By:
Categories:
Tags:
Stats:
views:113
posted:7/11/2011
language:Serbian
pages:57