3. Red 1980 450.00
4. Cyan 1975 800.00
5. Black 1981 500.00
6.
7. Green 1987 350.00
8. Gray 1968 900.00
9. White 1980 600.00
10. Yellow 1988 300.00
11.
12.
13. Magenta 1983 600.00
14.
15. Orange 1984 550.00
End of Table. Press any key ...
m=10, n=15, NCOMP=16, ALS=1.60, m/n=0.67, D(m/n)=2.00
Ana
liza rezultatelor rămâne ca exerciţiu.
Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa
hashing_table.
2. Supraîncărcaţi operatorul de extragere în clasa
hashing_table.
3. Găsiţi o funcţie de repartizare mai potrivită.
Tabele de repartizare cu
înlănţuirea externă
(repartizarea deschisă
,
înlănţuire
a
separată)
În metoda examinării
liniare
înregistrările
ce produc coliziuni
se includ în poziţiile
libere acel
uiaşi
vector de reflectare.
Însă pentru aceste înregistrări se poate crea un tabe
l aparte.
În tabelul adăugător
În tabele
de repartizare cu
înlănţuirea externă
lungimea medie de căutare pentru distribuirea
uniformă şi
aleatori
e a înregistrărilor se defineşte după for
mula:
Pentru a demonstra lucru cu tabele de
repartizare cu înlănţuirea externă, în primul rând să declarăm
clasa
hashing_linked_elemca clasa derivată de la clasa
usual_elemşi dotată cu câmpul
next,
pentru a crea lanţuri de legături.
//hashing_linked_elem()
hashing_linked_elem(char* init_name, int init_year, double init_salary):
usual_elem(init_name, init_year, init_salary)
{
next = -1;
}
virtual void hashing_linked_show(const char* opening=NULL,
const char* ending=NULL)
{
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
usual_elem::show("", "");
if(!free())
printf(" [%4d]", next);
printf("%s", ending);
}
int hf(int n) // hashing function
{
return (name[0]-'A')%n;
}
int get_next()
{
return next;
}
int set_next(int new_next)
{
return next=new_next;
}
};
Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa
hashing_linked_elem.
2. Supraîncărcaţi operatorul de extragere în clasa
hashing_linked_elem.
//
// C l a s s "e x t e r n _ h a s h i n g _ t a b l e"
// m/n
template <class el> class extern_hashing_table: public SD
{
protected:
int n;
int m;
el *t;
el *v;
public:
extern_hashing_table<el>(char* file_name, int init_n=0): SD(file_name)
{
n=init_n;
if(n<=0)
n=countn();
t=new el[n];
v=new el[n];
m=0;
el tmp;
int repeated, position;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
int i=tmp.hf(n);
if(t[i].free())
{
t[i]=tmp;
m++;
}
else
{
repeated=-1;
if( tmp==t[i] )
{
repeated=i;
t[i].show("", " !!!\n");
}
else
{
{
int j=0;
while(!v[j].free())
j++;
t[i].set_next(j);
v[j]=tmp;
m++;
}
else
{
i=t[i].get_next();
position=-1;
while((repeated==-1) && position==-1)
{
if( tmp==v[i] )
{
repeated=i;
v[i].show("", " !!!\n");
}
else
if(v[i].get_next()==-1)
{
position=i+1;
while(!v[position].free())
position++;
v[i].set_next(position);
v[position]=tmp;
m++;
}
else
i=v[i].get_next();
}
}
}
if ( repeated!=-1 )
{
char message[60];
char repeated_str[10];
message[0]='\0';
//strcat(message, "Key coincides with the key in the position: ");
//strcat(message, itoa(repeated+1, repeated_str, 10));
strcat(message, "Key coincides !!!\n");
error(message);
}
}
}
fclose(SD::pf), SD::pf=NULL;
}
virtual void show(const char* opening=NULL, const char* ending=NULL,
int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}
virtual void primary_show(const char* opening=NULL, const char* ending=NULL,
int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
for(int i=0; i<n; i++)
{
if(i>0 && i% nlinepp==0)
{
printf("%s", "Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].hashing_linked_show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}
virtual void secondary_show(const char* opening=NULL,
const char* ending=NULL, int nlinepp=20)
{
//clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("%s", "Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); v[i].hashing_linked_show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}
{
int position=-1;
int i=e.hf(n);
if(!t[i].free())
if(SD::ncomp++, e==t[i])
position=i;
else
if((i=t[i].get_next())!=-1)
do
{
if(SD::ncomp++, e==v[i])
position=i;
else
i=v[i].get_next();
}
while((position==-1) && (i!=-1));
return position;
}
int get_n()
{
return n;
}
int get_m()
{
return m;
}
protected:
int countn()
{
return 200;
}
};
În funcţia
main()să creăm pe baza fişierului
stud.txtun tabel de repartizare cu înlănţuirea
externă
pentru
n=15şi să demonstrăm căutarea elementelor.
void main()
{
clrscr();
ex_hashing_gr.primary_show("Primary table:\n","");
ex_hashing_gr.secondary_show("Secondary table:\n","");
char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
hashing_linked_elem e(surname, 2000, 0.0);
ex_hashing_gr.reset_ncomp();
int pos=ex_hashing_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", ex_hashing_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", ex_hashing_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n"); }
}
O variantă de
afişare poate arăta astfel:
Primary table:1.
2. Blue 1981 500.00 [ 2]
3. Red 1980 450.00 [ 1]
4.
5.
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.
13. Magenta 1983 600.00 [ -1]
15. Orange 1984 550.00 [ -1]
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ -1]
3. Black 1981 500.00 [ -1]
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
End of Table. Press any key ...
Enter a name to search: White
There are in the position 8. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Green
There are in the position 7. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 3. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 0
Done ? (y/n)
Pentru
n=10afişarea va fi:
Primary table:1.
2. Blue 1981 500.00 [ 4]
3. White 1980 600.00 [ 1]
4.
5. Orange 1984 550.00 [ 2]
6.
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
10.
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ 3]
3. Yellow 1988 300.00 [ -1]
4. Magenta 1983 600.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7.
8.
9.
10.
End of Table. Press any key ...
Enter a name to search: Gray
There are in the position 1. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Blue
There are in the position 2. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Magenta
There are in the position 4. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 0
Done ? (y/n)
Pentru a calcula lungimea medie de c
ăutare mod
ifi
căm funcţia
main().
void main()
{
clrscr();
extern_hashing_table<hashing_linked_elem> ex_hashing_gr("stud.txt", 15);
ex_hashing_gr.primary_show("Primary table:\n","");
ex_hashing_gr.secondary_show("Secondary table:\n","");
hashing_linked_elem sample;
long NCOMP=0;
FILE* pf=fopen("Stud.txt", "rt");
while(!feof(pf))
if(sample.fscanf_el(pf)>0)
ex_hashing_gr.reset_ncomp();
if(ex_hashing_gr.search(sample)>=0)
NCOMP+=ex_hashing_gr.get_ncomp();
}
fclose(pf);
printf("m=%d, n=%d, NCOMP=%d, ALS=%.2lf", ex_hashing_gr.get_m(),
ex_hashing_gr.get_n(), NCOMP, (double)NCOMP/ex_hashing_gr.get_m());
printf(", D(m/n)=%.2lf\n",
1.+(ex_hashing_gr.get_m()-1.)/(2.*ex_hashing_gr.get_n()));
getch();
}
Pentru
n=15rezultatul va fi:
Primary table:
1.
2. Blue 1981 500.00 [ 2]
3. Red 1980 450.00 [ 1]
4.
5.
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.
13. Magenta 1983 600.00 [ -1]
14.
15. Orange 1984 550.00 [ -1]
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ -1]
3. Black 1981 500.00 [ -1]
4.
5.
6.
7.
8.
9.
10.
12.
13.
14.
15.
End of Table. Press any key ...
m=10, n=15, NCOMP=13, ALS=1.30, D(m/n)=1.30
Iar pentru
n=10rezultatul va fi:
Primary table:
1.
2. Blue 1981 500.00 [ 4]
3. White 1980 600.00 [ 1]
4.
5. Orange 1984 550.00 [ 2]
6.
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9.
10.
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ 3]
3. Yellow 1988 300.00 [ -1]
4. Magenta 1983 600.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7.
8.
9.
10.
End of Table. Press any key ...
m=10, n=10, NCOMP=16, ALS=1.60, D(m/n)=1.45
Exerci
ţii.
Tabele de repartizare cu înlănţuirea internă
Încărcarea poziţiilor vectorului de
reflectare
constă din două etape:
prima etapă se aseamănă cu repartizarea cu înlănţuirea externă
;
a doua etapă se îndeplineşte după terminarea creării tabelului pri
mar
şi
celui secundar.
Ea constă
în mutarea lanţurilor din tabelul
secundar
r în poziţiile libere ale tabelului p
rimar.
Astfel tabelele din exemplul precedent vor fi transformate
în următorul tabel:
Prioritatea acestei metode în comparaţie cu precedenta –
economie de memorie, dar neajunsul
–
flexibilitatea mică şi algoritmul de încărcare a tabelului
este mai complicat.
Lungimea medie de căutare aici se defineşte după aceiaşi
formula:
metodă se foloseşte pentru tabele permanente, şi pentru tabele temporare,
care se
încărcă
la prima
etapă, dar
se folosesc la alta.
Pentru tabele constant
e înregistrări
le cel mai des folosite
se înscriu primele, atunci
accesările
lanţuril
or
interioare sunt rare, ce micşorează lungimea medie de căutare. Este caracter, că
la
folosirea lanţu
rilor interioare
toate poziţiile
ale vectorului de reflectare
pot fi încărcate (adică
m
=
n
),
Pentru a demonstra lucru cu tabele de
repartizare cu înlănţuirea
inte
rnă
să declarăm pe baza clasei
abstracte
SDclasa generică
extern_hashing_table, care na va da posibilitatea de a crea tabele de
repartizare cu
înlănţuirea
in
ternă
.
//
// C l a s s "i n t e r n _ h a s h i n g _ t a b l e"
// m/n
template <class el> class intern_hashing_table: public SD
{
protected:
int n;
int m;
el *t;
el *v;
public:
intern_hashing_table<el>(char* file_name, int init_n=0): SD(file_name)
{
n=init_n;
if(n<=0)
n=countn();
t=new el[n];
v=new el[n];
m=0;
el tmp;
int i, j;
int repeated, position;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
i=tmp.hf(n);
if(t[i].free())
{
t[i]=tmp;
m++;
}
else
{
repeated=-1;
if( tmp==t[i] )
{
repeated=i;
}
else
{
if(t[i].get_next()==-1)
{
j=0;
while(!v[j].free())
j++;
t[i].set_next(j);
v[j]=tmp;
m++;
}
else
{
i=t[i].get_next();
position=-1;
while((repeated==-1) && position==-1)
{
if( tmp==v[i] )
{
repeated=i;
v[i].show("", " !!!\n");
}
else
if(v[i].get_next()==-1)
{
position=i+1;
while(!v[position].free())
position++;
v[i].set_next(position);
v[position]=tmp;
m++;
}
else
i=v[i].get_next();
}
}
}
if ( repeated!=-1 )
{
char message[60];
message[0]='\0';
//strcat(message, "Key coincides with the key in the position: ");
//strcat(message, itoa(repeated+1, repeated_str, 10));
//strcat(message, "!\n");
strcat(message, "Key coincides !!!\n");
error(message);
}
}
}
fclose(SD::pf), SD::pf=NULL;
el empty;
int k=0;
for(j=0; j<n; j++)
if(!v[j].free())
{
i=v[j].hf(n);
int j1=j;
do
{
while(!t[k].free())
k++;
t[i].set_next(k);
i=k;
t[i]=v[j1];
t[i].set_next(-1);
int jtmp=j1;
j1=v[j1].get_next();
v[jtmp]=empty;
}
while (j1!=-1);
}
delete v;
}
virtual void show(const char* opening=NULL, const char* ending=NULL,
int nlinepp=20)
{
clrscr();
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i% nlinepp==0)
{
printf("Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}
virtual void primary_show(const char* opening=NULL, const char* ending=NULL,
int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("%s", "Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].hashing_linked_show();
}
printf("%s", ending);
getch();
}
int search(el e)
{
int position=-1;
int i=e.hf(n);
if(!t[i].free())
do
{
if(SD::ncomp++, e==t[i])
position=i;
else
i=t[i].get_next();
}
while((position==-1) && (i!=-1));
return position;
}
int get_n()
{
return n;
}
int get_m()
{
return m;
}
protected:
int countn()
{
return 200;
}
};
În funcţia
main()să creăm pe baza fişierului
stud.txtun tabel de repartizare cu înlănţuirea
internă pentru
n=15şi să demonstrăm căutarea elementelor.
void main()
{
clrscr();
in_hashing_gr.primary_show("Primary table:\n","");
char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
hashing_linked_elem e(surname, 2000, 0.0);
in_hashing_gr.reset_ncomp();
int pos=in_hashing_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", in_hashing_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", in_hashing_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}
}
O variantă de afişare poate arăta astfel:
Primary table:1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 4]
3. Red 1980 450.00 [ 3]
4. Cyan 1975 800.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.
13. Magenta 1983 600.00 [ -1]
15. Orange 1984 550.00 [ -1]
End of Table. Press any key ...
Enter a name to search: White
There are in the position 8. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Green
There are in the position 7. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 5. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 1
Done ? (y/n)
Pentru
n=10afişarea va fi:
Primary table:1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 9]
3. White 1980 600.00 [ 3]
4. Cyan 1975 800.00 [ 5]
5. Orange 1984 550.00 [ 8]
6. Magenta 1983 600.00 [ -1]
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9. Yellow 1988 300.00 [ -1]
10. Black 1981 500.00 [ -1]
End of Table. Press any key ...
Enter a name to search: Gray
There are in the position 1. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Blue
There are in the position 2. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Magenta
There are in the position 6. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 1
Done ? (y/n)
clrscr();
intern_hashing_table<hashing_linked_elem> in_hashing_gr("stud.txt", 15);
// intern_hashing_table<hashing_linked_elem> in_hashing_gr("stud.txt", 10);
in_hashing_gr.primary_show("Primary table:\n","");
hashing_linked_elem sample;
long NCOMP=0;
FILE* pf=fopen("Stud.txt", "rt");
while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
in_hashing_gr.reset_ncomp();
if(in_hashing_gr.search(sample)>=0)
NCOMP+=in_hashing_gr.get_ncomp();
}
fclose(pf);
printf("m=%d, n=%d, NCOMP=%d, ALS=%.2lf", in_hashing_gr.get_m(),
in_hashing_gr.get_n(), NCOMP, (double)NCOMP/in_hashing_gr.get_m());
printf(", D(m/n)=%.2lf\n",
1.+(in_hashing_gr.get_m()-1.)/(2.*in_hashing_gr.get_n()));
getch();
}
Pentru
n=15afişarea va fi:
Primary table:1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 4]
3. Red 1980 450.00 [ 3]
4. Cyan 1975 800.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.
13. Magenta 1983 600.00 [ -1]
14.
End of Table. Press any key ...
m=10, n=15, NCOMP=13, ALS=1.30, D(m/n)=1.30
Pentru
n=10afişarea va fi:
Primary table:1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 9]
3. White 1980 600.00 [ 3]
4. Cyan 1975 800.00 [ 5]
5. Orange 1984 550.00 [ 8]
6. Magenta 1983 600.00 [ -1]
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9. Yellow 1988 300.00 [ -1]
10. Black 1981 500.00 [ -1]
End of Table. Press any key ...
m=10, n=10, NCOMP=16, ALS=1.60, D(m/n)=1.45
Tabelele de repartizare aleatori
e se încarcă
destul de
simplu, nu necesit ordonarea înregistrărilor şi
asigură o căutare rapidă. Deaceea aceste tabele deseori se folosesc în practică
.
Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa
intern_hashing_table.
2. Supraîncărcaţi operatorul de extragere în clasa
intern_hashing_table.
Fun
cţii de repartizare
Timpul
calculării funcţi
ei de repartizare
f
(
k
) intră în timpul mediu de căutare, d
eaceiea trebuie de
ţinut cont la alegerea algoritmului, realizând funcţia de
repartizare.
O funcţie bună de
repartizare
trebuie să asigure repartizarea uniformă a înregistrări
lor
pe poziţiile
vectorului de reflectare, fiind
că distribuirea neuniformă măreşte timpul medi
u
de căutare. Însă
dacă
calcularea
valorii funcţiei de
repartizare
necesită îndeplin
irea unui
număr mare
de
operaţii
, aceasta
poate distruge toată economia în timpul căutării. Deci, algoritmul calculării
funcţiei de
repartizare
nu
trebuie să fie complicat. Să privim câteva metode de
calculare
a funcţiei de
repartizare:
1.
Una din metodele simple se
bazează pe evidenţierea unei părţi din codul numeric al chei
i. De
exemplu, fie dimensiunea maximă aşteptată a tabelului de nume
simbolice
nu întrece 256.
Atunci funcţia de
repartizare
poate avea în calitate de valoare 8
biţi, fiindcă 256=2
8. Se poate
pur şi simplu
de a
evidenţia primii 8 biţi din
codul binar al identificatorului sau de a lua careva 8
2.
Pentru asigurarea
distribuirii uniforme se foloseşte “sumare
a
” codul
ui identificatorului: prima
jumătate a codului se sumează cu a doua şi din rezultat se evidenţiază 8 biţi
. Se poate de
asemenea de
împărţi
t codul cheii
în
bucăţi câte 8 biţi
, de sumat
bucăţile şi
de pus suma pe
modulul 2
8. Ultima modificare are careva probabilitate teoretică: la presupunerea a statisticei
independente
de sumare a bucăţilor se primeşte repartizarea aproape
de uniform
ă
.
3.
O
altă metodă de
calculare
a funcţiei de
repartizare este
împărţire
a. Pentru vectorul de reflectare
de lungimea n, cheia
se priveşte ca un număr întreg
şi se împarte la mărimea
n. Experimentele
arată, că restul de la împărţire este repartizat aproape uniform în intervalul
[0, n-1]
şi poate fi
folosit ca va
loarea funcţiei de
repartizare.
Verificarea experimental
ă
a metodelor descrise pentru tabelele
de repartizare cu examinarea lineară
a arătat că evidenţierea
simplă a
bucăţii
din codul identificatorului
măreşte lungimea medie de
căutare în comparaţie cu
cea
teoretică, definită după formula:
2
2
2
)
(
D
,
în 4
-5 sau
şi mai
multe ori. L
ungimea medie de căutare pentru metoda
de sumare
a bucăţilor după modulul 2
kaproape de două ori mai mare ca teoretică
, dar pentru
împărţirea
, lungimea
medie de căuta
re practic
3. TEHNICI DE SORTARE
3.1.
Noţiuni generale
Sortarea este operaţia de aranjare a elementelor unui vector după valoarea
cheilor, pentru care este definită relaţia de ordine.
Tradiţional diferă sortarea internă de sortarea externă. Sortarea internă prelucrează datele păstrate în
memoria operativă a calculatorului, dar sortarea externă, operează cu datele care sunt păstrate în
fişier
e.
În cazul sortării interne se tinde la minimizarea numărului de comparaţii
şi permutări ale
elementelor.
În cazul sortării externe factorul hotărâtor este numărul de operaţii de intrare şi ieşire. În acest caz
numărul de comparaţii
trece pe planul doi, totuşi şi el se i
-
a în consideraţie.
Cazul sortării interne
Presupunem, că datele supuse sortării se păstrează în memoria operativă
într
-un vector
t. Fiecare
element
t[i]al acetui vector este obiect al clasei parametrizate
elîn care sunt supraîncărcaţi
operatorii de compara
ţie
. Deci, sunt admise expresii:
t[i]<t[j]
,
t[i]<=t[j], etc.
Presupunem, că funcţia
swap(i,j)schimbă cu locurile elementele
t[i]şi
t[j]a vectorului.
Funcţia
cmp(i,j)întoarce un număr întreg, care este egal cu
-
1, dacă
t[i]<t[j];
0, dacă
t[i]==t[j];
1, dacă
t[i]>t[j].
Definiţia 1
. Perechea de indici
(i,j), astfel ca
i<j, dar
t[i]>t[j]se numeşte
inversie.
Definiţia 2.
Vectorul
tse numeşte sortat în ordine crescătoare, dacă el nu
conţine nici o inversie.
Definiţia 3.
Vectorul
tse numeşte sortat în ordine descrescătoare, dacă pentru
Definiţia 4.
Algoritmul sortării se numeşte stabil, dacă el niciodată nu schimbă
ordinea relativă în vector la oricare 2 elemente cu cheile egale. Adică pentru
orice pereche
i,
j:
i<j;
t[i]=t[j],
i—>i',
j—>j' => i'<j'.
Stabilitatea poate fi o cerinţă definitoare, de exemplu când are loc sortarea după o anumită cheie a
elementelor deja sortate după o altă cheie.
Exemplu
: lista studenţilor care este deja sortată după numele se cere de aranjat după an
ul de studii,
dar pentru fiecare an de studii numele trebuie să rămână în ordinea alfabetică.
Despre complixitatea algoritmilor de sortare
La caracterizarea algoritmilor de sortare se foloseşte noţiunea de mărime (de exemplu
numărul de
comparaţii de chei) care
depinde de un număr natural
n
(de exemplu numărul de elemente) relativ la
o altă mărime cunoscută care tot depinde de numărul natural acesta. Se spune că mărimea
k
are
ordinul
comparabil cu mărimea
m
,
se notează
k
=
O
(
m
),
se citeşte “
k
are ordinul o de
m
”, dacă
:
const
m
k
n
lim
Din formula aceasta rezultă că mărimile
n
-1,
n
, 2
n
,
n
+3 au unul şi acelaşi ordin O(
n
). Iar mărimea
n
2are un ordin mai mare.
Algoritmul de sortare a
n
elementelor bazat pe compararea cheilor
are complexitatea minimă
O
(
n
)
sau mai mare.
Demonstrăm prin inducţia metematică:
Pentru
k
= 2
–
o compara
ţie. Presupunem că pentru
k
=
n
-1 trebuiesc
n
-2 compar
aţii. Se adaugă încă
un element, deci mai trebuie de făcut cel puţin încă o comparaţie
,
obţinem
n
-2+1 =
n
-1 compar
aţii
.
Deci, c
omplexitatea minimă
este
O
(
n
).
Teoretic este demonstrat [vezi 1]
că c
omplexitatea medie
a oricărui algoritm de sortare care
operează cu comparaţiile
nu poate fi mai mică
de
O
(
n
log2
n
).
Algoritmii triviali de sortare
bazaţi pe compararea cheilor a
u complexitatea
atât medie cât şi cea
maximă
O
(
n
2).
Complexitatea
medie a oricărui algoritm
bun de sortare bazat pe compararea cheilor este
O
(
n
log
2n
),
totodată
complexitatea maximă a lui poate fi ori
O
(
n
log
2n
) ori
O
(
n
2).
3.2.
Clase pentru exemple de sortare
Vom folosi
clasa abstractă
elemşi clasa concretizată
usual_elem.
Declarăm clasa generică
vectorparametrizată cu clasa
el.
//
// C l a s s "v e c t o r"
template <class el> class vector: public SD
{
protected:
int n; // numărul de elemente ale vectorului
el* t; // pointer la vectorul de elemente
public:
vector<el>(int NMAX=200)
{
n=NMAX;
t=new el[n];
}
vector<el>(char* file_name, int NMAX=200): SD(file_name)
{
t=new el[NMAX];
n=0;
while(!feof(SD::pf))
if(t[n].fscanf_el(SD::pf)>0)
n++;
fclose(SD::pf), SD::pf=NULL;
}
virtual void show(const char* opening, const char* ending, int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
printf("%s", opening);
if(!ending)
ending="\n";
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1));
}
printf("%s", ending);
printf("End of vector. Press any key ...\n");
getch();
}
int search(el e)
{
int position = -1;
for(int i=0; (position==-1) && i<n ; i++)
if(SD::ncomp++, e==this->t[i])
position=i;
return position;
}
int get_n()
{
return n;
}
long get_ncomp()
{
return SD::ncomp;
}
void reset_ncomp()
{
SD::ncomp=0;
}
protected:
void swap(int i, int j)
{
el tempor=t[i];
t[i]=t[j];
t[j]=tempor;
}
};
1. Supraîncărcaţi operatorul de inserţie în clasa
vector.
2. Supraîncărcaţi operatorul de extragere în clasa
vector.
3.3.
Sortarea prin interschimbare
Metoda de sortare prin interschimbare (engl. Exchange sort)
constă în parcurgerea elementelor a
le
vectorului, în aşa mod ca fiecare parcurgere micşorează numărul de inversii, până atunci când nu
rămâne nici o inversie. Problema constă în căutarea următoarei
inversiei (
i
,
j
).
Schematic:
while (este_inversie (i, j))
swap(i, j);
Sortarea prin intersc
himbare constă în modificări succesive de
tip
t[i]t[j], până când
elementele vectorului nu vor deveni
în ordine crescătoare.
Din această categorie fac parte metoda bulelor
(
bubblesort)
–
unul din cei mai slabi algoritmi de
sortare
şi sortarea rapidă
(
quicksort)
–
unul din cei mai buni algoritmi de sortare.
Sortarea prin metoda bulelor
Metoda bulelor constă în compararea
t[i]cu
t[i+1], d
acă ordinea este bună se compară
t[i+1]cu
t[i+2],
dacă ordinea nu este bună se interschimbă
t[i]cu
t[i+1]şi apoi se compară
t[i+1]cu
t[i+2]. După prima parcurgere a vectorului, pe ultima poziţie ajunge elementul având valoarea
cea mai mare, după a doua parcurgere ajunge următorul element
pe penultima poziţie,
etc.
Algoritmul are complexitatea
O
(
n
2).
void bubble_sort()
{
BOOLEAN inversion;
do
{
inversion = FALSE;
for(int i=0; i<n-1; i++)
if(ncomp++, t[i]>t[i+1])
{
swap(i,i+1);
inversion = TRUE;
}
}
while (inversion);
}
Complexitatea maximă
este
O
((
n
-1)
2)=
O
(
n
2). Dacă elementul minimal are indicele iniţial
n
-1,
atunci va fi nevoie de
n
-
1 executări a ciclului exterior, ca să
-
i dăm indicele
0
.
Pentru fiecare executare a ciclului exterior cu
n
-1 compar
aţii
: (
n
-1)*(
n
-1)=(
n
-1)
2compar
aţii
.
Complexitatea medie de
asemenea este egală cu
O
(
n
2), dacă elementul minimal căutat se află
aleator
printre indicii 0, 1,…,
n
-1.
Exerciţiu
:
Este posibilă îmbunătăţirea
acestui algoritm, c
e nu schimbă totuşi esenţial complexitatea.
Îmbunătăţiţi algoritmul de mai sus, prescurtând cu un element parcurgerea de rând faţă de
precedentă.
Metoda bulelor este unul din cei
mai răi algoritmi de sortar
e.
Neajunsul constă în aceea că
la fiecare
pas elementul
următor se compară numai cu vecinul său următor.
3.4.
Sortarea rapidă
Sortarea rapidă (
quicksort) a fost propusă de C.A.R. Hoare şi foloseşte
principiile
“
Divide Et
Impera”
şi “
Echilibru
”
.
Ideea metodei e
ste următo
area
: se selectează un element
arbitrar din vector numit principal (sau
pivot)
şi se rearanjează vectorul în doi subvectori, astfel încât cel din stânga are toate elementele
mai mici sau egale decât pivotul, iar cel din dreapta mai mari sau egale
ca pivotul. Procedeul se reia
în subvectorul din stânga şi apoi în cel din dreapta
, etc
. Procedeul se termină când se ajunge
la
subvectori dintr-un singur element.
În baza clasei generice
vectordeclarăm clasa derivată
vector_quicksort, dotată cu algoritm
ul de
sortare rapidă.
//// C l a s s " v e c t o r q u i c k s o r t"
//
template <class el> class vector_quicksort: public vector<el>
{
public:
vector_quicksort<el>(char* file_name, int NMAX=200):
vector<el>(file_name, NMAX)
{
}
void quicksort(int i=0, int j=-1)
{
if(j>=n || j==-1)
j=n-1;
if(i<0 || i>j)
i=0;
quicksort_intern(i, j);
protected:
void quicksort_intern(int i, int j);
int divide(int i, int j);
};
Presupune
m că există funcţia
numită
divide(), care într
-un anumit fel alege elementul principal cu
cheia
Kşi rea
ran
jează
vectorul astfel, ca
elementul principal primeşte un indice
imain,
iar toate
elementele cu cheile ≤
Kse aranjează de la stânga (adică au indici
i <
imain), dar toate elementele cu
cheile
≥
Kse aranjează de la dreapta
(adică au indicii >
imain):
Avem aici un caz tipic recursiv:
parametrizarea: se precaută pentru
subvectorul
t[i]÷t[j]; pentru vectorul
iniţial
i=0,
j=n-1;
cazul trivial:
i=j(nu avem ce sorta);
t
recerea de la cazul general la un caz mai simplu, care are loc datorită funcţiei
divide().
Dacă există
o astfel de
funcţie, atunci sortarea rapidă imediat se obţine
în formă recursivă
:
template <class el>
void vector_quicksort<el>::quicksort_intern(int i, int j)
{
if (j>i)
{
int imain=divide(i,j);
quicksort_intern(i,imain-1);
quicksort_intern(imain+1,j);
}
}
Algoritmul are loc pentru ambele ordine de apeluri recursive.
Schema metodei de divizare
în timpul
O
(
n
):
elementul cu cheia, ce întrece cheia elementului principal, dar din dreapta –
elementul cu cheia mai
mică ca cheia elementului principal.
După aceasta se poate de s
chimbat cu locurile aceste
două
elemente ≤
t[imain]e
lemente ≥
t[imain]continuă cu poziţiile deja găsite. Vectorul se
socoate
împărţit, când poziţi
ile
din stânga şi din
dreapta se
întâlnesc.
Valo
area comună a lor notăm prin
imain.
Evident, că complexitatea
divi
ză
rii
nu întrece
O
(
n
), sau mai bine spus
O
(
j-i
) când
divizarea se
aplică
la subvectorul
t[i]÷t[j].
Sfaturi practice la alegerea elementului principal
Alegerea elementului principal trebuie
să
fie
în aşa fel ca să se micşoreze probabilitatea
cazului
când
după divizarea
subvectorii (segmentele)
să difer
e
mult după lungime.
Prima strategie
: la fiecare divizare alegerea aleatorie (
folosind funcţia
-generator de numere
aleatoare) a valorii indicelui elementului principal dintre
i,
i+1, …,
j. Neajunsul acestei metode -
cheltuieli suplimentare de timp necesare
pentru această operaţie.
A două strategie
:
în calitate de elementul principal
se alege elementul cu valoarea medie dintr-un
set nu mare de elemente. C
el mai simplu şi mai uşor
de examinat setul
ce conţine trei elemente cu
indicii respectiv
i,
jşi
(i+j)/2.
Ambele metode micşorează probabilitatea cazului
catastrofal
O
(
n
2), doar totu
şi
aşa situaţie
nu este
exclusă. Sortarea rapidă întotdeauna poate să se degenereze. Paradoxal, că sortarea rapidă este unul
din cei mai buni algoritmi de sortare internă, dar suntem nevoiţi să ne refuzăm de e
a
în probleme
unde limitele superioare de timp (de tip
kn
log
2n
) necesare pentru sortarea, sunt critice.
Algori
tmul divizării
Există mai multe variante a
le algoritmului de divizare.
Toate din ele urmăresc cel puţin două
scopuri:
a accelera ciclurile interioare;
a prevedea caracterul
“
aleator
” a vectorului. Adică de a exclude introducerea întâmplătoare a
ordinei
î
n
segmentele de divizare din punct de vedere al productivităţii generale a algoritmului.
Sedgewick R. E. a propus următoare
a
metodă
de divizare:
a)
punem elementul principal
în poziţia
i(
îl schimbăm dacă este necesar cu elementul
t[i]).
b)
divizăm sub
vectorul
t[i+1],
t[i+2],…
t[j], cu ajutorul valoarei elementului principal
t[i]lăsând
pe
t[i]la locul său
. S
e primeşte
divizarea cu
poziţi
a intermediar
ă
imain, de exemplu:
c)
schim
băm
cu locurile elementul
t[i]cu elementul
t[imain]şi dăm valoarea
imainca
rezultatul
întors de către funcţia
divide.
template <class el> int vector_quicksort<el>::divide(int i, int j)
{
int imain, jmain, imed;
imed =(i+j)/2;
imain = (SD::ncomp++, t[i] < t[imed]) ?
((SD::ncomp++, t[imed] < t[j]) ?
imed
:
(SD::ncomp++, t[i] < t[j]) ? j : i)
:
((SD::ncomp++, t[imed] > t[j]) ?
imed
:
(SD::ncomp++, t[i] > t[j]) ? j : i);
i i+1 j
i imain j
imain+1 imain-1
elemente ≤
t[imain]elementul
elemente ≥
t[imain]principal
i imain j
imain+1 imain-1
if(imain > i)
swap(i, imain);
imain = i+1, jmain = j;
while(imain < jmain)
{
while((imain < jmain)&&(SD::ncomp++, t[imain] <= t[i]))
imain++;
while((jmain > imain)&&(SD::ncomp++, t[jmain] >= t[i]))
jmain--;
if(imain < jmain)
swap(imain, jmain);
}
if(SD::ncomp++, t[imain] > t[i])
imain--;
if(imain > i)
swap(i, imain);
return imain;
}
Este clar că funcţia
divide()are complexitatea
O
(
n
). Ciclul exterior
while(imain < jmain)
{
}
verifică fiecare element al vectorului
t[0],
t[i],…
t[n-1]cel mai mult de două ori, dar restul
operaţiilor cere un timp fix.
În funcţia
main()creăm un vector
si-il sortat
ăm
prin metoda
quicksort:void main()
{
clrscr();
vector_quicksort<usual_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.quicksort();
gr.show("Group sorted by name:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}
A
fişare
a va
arăta astfel:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by name:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=39, n*log2(n)=33.22, n*n=100
Analiza rezultatelor rămâne ca exerciţiu.
Dacă vrem să sortăm după anul de naştere, atunci declarăm în baza clasei
usual_elemclasa
year_elemla care suprascriem
funcţia
cmp().
//
// C l a s s "y e a r _ e l e m"
//
class year_elem : public usual_elem
{
public:
year_elem()
{
}
year_elem(char* init_name, int init_year, double init_salary):
usual_elem(init_name, init_year, init_salary)
{
virtual int cmp(elem& e2)
{
int result;
if(this->year < ((year_elem&)e2).year)
result=-1;
else
if(this->year > ((year_elem&)e2).year)
result=1;
else
result=0;
return result;
}
};
În funcţia
main()instanţiem clasa generică
vector_quicksortcu clasa-argument
year_elem.
void main(){
clrscr();
vector_quicksort<year_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.quicksort();
gr.show("Group sorted by year:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}
Afişarea va arăta astfel:
Unsorted group:1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
1. Gray 1968 900.00
2. Cyan 1975 800.00
3. Red 1980 450.00
4. White 1980 600.00
5. Black 1981 500.00
6. Blue 1981 500.00
7. Magenta 1983 600.00
8. Orange 1984 550.00
9. Green 1987 350.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=42, n*log2(n)=33.22, n*n=100
Analiza rezultatelor rămâne ca exerciţiu.
Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa
year_elem.
2. Supraîncărcaţi operatorul de extragere în clasa
year_elem.
În sfârşit declarăm în baza clasei
usual_elemclasa derivată
salary_elemcare c
ompară
obiecte
după salariu.
//
// C l a s s "s a l a r y _ e l e m"
//
class salary_elem : public usual_elem
{
public:
salary_elem()
{
}
salary_elem(char* init_name, int init_year, double init_salary):
usual_elem(init_name, init_year, init_salary)
{
}
virtual int cmp(elem& e2)
{
int result;
if(this->salary < ((salary_elem&)e2).salary)
result=-1;
if(this->salary > ((salary_elem&)e2).salary)
result=1;
else
result=0;
return result;
}
};
În funcţia
main()instanţiem clasa generică
vector_quicksortcu clasa-argument
salary_elem.
void main()
{
clrscr();
vector_quicksort<salary_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.quicksort();
gr.show("Group sorted by salary:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}
Afişarea de data aceasta va arăta astfel:
Unsorted group:1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by salary:
1. Yellow 1988 300.00
2. Green 1987 350.00
3. Red 1980 450.00
4. Blue 1981 500.00
7. White 1980 600.00
1. Supraîncărcaţi operatorul de inserţie în clasa
salary_elem.
2. Supraîncărcaţi operatorul de extragere în clasa
salary_elem.
Construirea sortării rapide efective
Apelurile recursive neterminate vor fi
înscrise în stivă. În cel mai nefavorabil caz
divizarea
secvenţială poate da sistematic
imain=0(
imain=i)
sau
imain=n-1(
imain=j).
În acest caz
divizarea
vectorului în doi sub vectori
va da permanent unul de lungime
0iar altul va avea lungimea
n-1(
j-i).
Adâncimea în apelurile recursive poate atinge valoarea
n
–
lungimea vectorului
iniţial.
Trebuie
să
prevedem
stiva de adâncime
n
(complexitatea spaţială
O
(
n
), ce nu este accesibil).
accesibil). Deci mod
ificăm funcţia
quicksort_intern()ca ea să se
arate astfel:
template <class el>
void vector_quicksort<el>::quicksort_intern(int i, int j)
{//începem cu intervalul drept
Deoarece
modificarea introdusă nu va schimba afişările
obţinute pr
in exemplele de sortare
precedente, nu le mai repetăm.
fi divizat de către funcţia
divide().
O situaţie ideală care ar putea fi
o
bţinută prin aplicarea principiu
lui
de echilibru, constă în
pe lângă
n
la complexitatea împărţirii.
Aşa
dar,
metoda obţine
O
(
n
log2
n
) ce este limita de jos pentru complexitatea algoritmilor de sortare
bazaţi
pe compararea cheilor.
Dacă
divizarea sistematic se
obţine
lângă primul sau lângă ultimul element
e ale subvectorilor
cercetaţi (adică
permanent
imain=i, sau
imain=j), atunci
fiecare dată rămâne de sortat o parte a
subvector
ului, în care numărul de elemente este cu o unitate mai mic
decât subvectorul precedent,
şi
rezultă că complexitatea
va fi
T
(
n
)
O
(
n
)
O
(
1
)
T
(
n
1
)
O
(
n
)
O
(
n
1
)
...
O
(
1
)
O
(
n
2)
.
În acest caz sortarea rapidă are complexitatea teoretică asemănătoare cu complexitatea c
elor mai
răi
algoritmi de sortare, de exemplu sortarea prin metoda
bulelor. Dar complexitatea practică probabil
va fi
şi mai mare
,
din cauza timpului necesar pentru realizarea recursiei dirijate de stivă.
Pentru
sortarea rapidă este arătat teoretic că complexitatea medie apreciată pentru probabilităţile egale a
tuturor permutărilor este egală cu
O
(
n
log
2n
) cu aproximativ 2
n
log
2n
compar
aţii
a cheilor, de
asemenea este arătat că probabilitatea celui mai nefavorabil caz cu complexitatea
O
(
n
2) este destul
de mică.
Posibilitatea celui mai nefavorabil caz
nu este exclusă când datele sunt deja sortate
sau parţial
sortate (
poate fi şi în ordine inversă).
Paradoxul sortării rapide în contrastul cu sortarea prin inserţie sau chiar
prin metoda bulelor,
constă
incomod,
fiindcă necesitate
a de a sorta datele
“aproape sortate” destul de des se întâlneşte în
practică.
Îmbunătăţirea sortării rapide
Ca
sortarea rapidă să devină
real un algoritm efectiv, ea mai
cere încă o îmbunătăţire.
Este evident,
că î
n versiunile precedente
recursia şi alegerea elementului principal devin destul de
grele pentru
subvectori mici
. Sortarea rapidă nu poate fi aplicată
la vectori mici. De
aceea recursia trebuie oprită
c
ând dimensiunea subvectorului devine mai mică decât careva constantă, numită prag. După aceasta
se foloseşte metoda, eficacitatea căreia poate să se îmbunătăţească la datele parţial sortate, de
exemplu sortarea prin inserţie simplă.
D.
Knuth a obţinut că
v
aloarea optimală teoretică a pragului
este
egală cu 9.
În
practică rezultatele bune ne dau valorile pragului de la 8 până la 20, iar valoarea optimă
se
conţine între 14 şi 16.
//// C l a s s " v e c t o r o p t i m q u i c k s o r t "
//
template <class el> class vector_optim_quicksort:
public vector_quicksort<el>
{
public:
vector_optim_quicksort<el>(char* file_name, int threshold_init=15,
int NMAX=200):
vector_quicksort<el>(file_name, NMAX)
{
threshold=threshold_init;
if(threshold<1)
threshold=1;
}
void quicksort(int i=0, int j=-1)
{
if(j>=n || j==-1)
j=n-1;
if(i<0 || i>j)
i=0;
quicksort_intern(i, j);
}
protected:
void quicksort_intern(int i, int j);
void insertsort(int i, int j);
};
template <class el>
void vector_optim_quicksort<el>::quicksort_intern(int i, int j)
{
if(j-i+1>threshold)
{
int imain=divide(i,j);
if(imain-i > j-imain)
{
quicksort_intern(i,imain-1);
quicksort_intern(imain+1,j);
}
else
{
quicksort_intern(imain+1,j);
quicksort_intern(i,imain-1);
}
}
else
insertsort(i, j);
}
template <class el>
void vector_optim_quicksort<el>::insertsort(int i, int j)
{
for(int k = i+1; k<=j; k++)
for(int l=k; (l>i)&&(ncomp++, t[l]<t[l-1]); l--)
swap(l-1, l);
}
Exemple de funcţia
main()luăm din trei exemple precedente, înlocuind în ele denumirea clasei
vector_quicksort
cu
vector_optim_quicksortşi adăugând la parametrii
constructorilor
valoarea pentru prag, de exemplu 4.
Afişările, analiza cărora rămâne ca exerciţiu,
vor
arăta astfel:
Pentru vectorul concretizat cu clasa
usual_elemUnsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by name:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=32, n*log2(n)=33.22, n*n=100
Pentru vectorul concretizat cu clasa
year_elemUnsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by year:
1. Gray 1968 900.00
2. Cyan 1975 800.00
3. White 1980 600.00
4. Red 1980 450.00
5. Black 1981 500.00
6. Blue 1981 500.00
8. Orange 1984 550.00
9. Green 1987 350.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=37, n*log2(n)=33.22, n*n=100
Pentru vectorul concretizat cu clasa
salary_elemUnsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by salary:
1. Yellow 1988 300.00
2. Green 1987 350.00
3. Red 1980 450.00
4. Blue 1981 500.00
5. Black 1981 500.00
6. Orange 1984 550.00
7. White 1980 600.00
8. Magenta 1983 600.00
9. Cyan 1975 800.00
10. Gray 1968 900.00
End of vector. Press any key ...
n=10, ncomp=33, n*log2(n)=33.22, n*n=100
În următorul tabel
este prezentat num
ărul
de comparări
efectuate la sortarea obiectelor
vector_optim_quicksort
create pe
baza fişierului
”stud.txt”pentru diferite valori ale pragului
şi clase de concretizare.
Prag
Câmpul de sortare
name year salary