AAO 3 Csink László 2007
Algoritmus fogalma - ismétlés
Az algoritmus egy eljárás (jóldefiniált utasítások véges halmaza), amelyet valamely feladat megoldására készítünk. A feladat egy adott kezdeti állapotból egy meghatározott végállapotba jut (szakszóval terminál).
2
Kiválogatás
Ez az algoritmus egy t tömb (indexek 0-tól n-1 ig) bizonyos tulajdonságú elemeit (a negatívokat) teszi egy másik tömbbe. db változó számolja, hogy a másik tömbbe hány elem került, és válogassuk ki a negatív számokat. Az eredmény b tömbben lesz (deklarációnál b tömböt n eleműre kell választani, hacsak nem tudjuk előre, hány negatív szám van t-ben, illetve nem használunk dinamikus tömböt).
3
Kiválogatás pszeudokódja és C# kódja KONSTANS n EGÉSZ VÁLTOZÓK i, db EGÉSZ, t[i] , b[i] VALÓS db ← 0; i ← 0; ISMÉTELNI HA( i kisebb mint n){ HA t[i] <0 AKKOR { b[db] ← t[i]; db ← db + 1 ; } i ← i + 1; } i = 0; db = 0; while (i < n ){ if (t[i] < 0 ) { b[db] = t[i] ; db++; } i++; }
4
const int n = 10; int j, i = 0, db = 0; int[] t = new int[n]; t[0] = 1; for (j = 1; j < n; j++) t[j] = -t[j-1]*2; // tesztanyag int[] b = new int[n]; while (i < n) { if (t[i] < 0) { b[db] = t[i]; db++; } i++; } for (i = 0; i < n; i++) Console.Write(t[i]+" "); Console.WriteLine(); for (i = 0; i < db; i++) Console.Write(b[i] + " "); Console.WriteLine();
5
Szétválogatás (két tömbbe)
A feladat hasonló az előzőhöz, de a feltételnek nem megfelelő elemeket is egy újabb tömbbe kell elhelyezni (tehát kétfelé válogatjuk az eredeti tömböt). Ez az algoritmus egy t tömb (indexek 0-tól n-1 ig) pozitív elemeit teszi egy p tömbbe, a nem-pozitív elemeket a np tömbbe. pdb illetve npdb változó számolja, hogy az illető tömbbe hány elem került.
6
Szétválogatás (két tömbbe) C# kód
int n = 10; int[] t = new int [n]; // index 0-tól n-1 ig int[] p = new int [n]; int[] np = new int [n]; int i , pdb, npdb; i = 0; pdb = 0; npdb = 0; while (i < n ) { if (t[i] > 0 ) { p[pdb] = t[i] ; pdb++; } else { np[npdb] = t[i]; npdb++; } i++; } 7
Szétválogatás (egy tömbbe)
Memóriafoglalás szempontjából a két tömböt használó előző algoritmus nem hatékony: mind a p, mind az np tömböt n eleműre kell deklarálni, de a két tömbben összesen csak n elem van. Használhatunk egy tömböt is, akkor annak első felébe tesszük a pozitív számokat, a második felébe (hátulról kezdve a feltöltést) a többit. Ez az algoritmus egy t tömb (indexek 0-tól n-1 ig) pozitív elemeit teszi egy p tömb elejére, a nem-pozitív elemeket p tömb végére.
8
Szétválogatás (egy p tömbbe) C# kód i = 0; pdb = 0; veg = n-1; while (i < n ) { if (t[i] > 0 ) p[pdb++] = t[i]; else p[veg--] = t[i]; i++; }
9
Halmazműveletek: metszet A feladat most két tömb a[0..n-1] és b [0..m-1] azonos elemeinek kiválogatása c tömbbe. A feladat csak úgy értelmezhető pontosan, ha az egyes tömbökben egy elem nem szerepel kétszer. (Mivel most a matematikai halmazokat tömbként ábrázoljuk.) Az algoritmus lényege: menjünk végig az a tömb elemein, és válogassuk ki azokat (kiválogatás), melyek szerepelnek b-ben (eldöntés). Így a feladat a korábbi tételekre visszavezethetõ. c maximális elemszáma n és m közül a kisebbik. Feltételeztük, hogy egyik halmaz sem üres.
10
Deklarációk a metszethez int n = 10, m=6, db =Math.Min(n,m); // tömbméretek int i,j,k; // ciklusváltozók int[] a = new int [n]; // a halmaz elemei 0..n-1 int[] b = new int [m]; // b halmaz elemei 0..m-1 int[] c = new int [db];
// a metszet elemei
// db csak a maximális lehetséges tömbméret. Ha a metszet // üres, akkor nyilván nincs elem a metszetben.
11
Metszet C# kód k = 0; for (i=0; i
a[i] if (j<m) c[k++]=a[i]; // ha j<m, akkor a[i] szerepelt b-ben } for (i = 0; i < k; i++) Console.WriteLine(c[i]); // metszet kiíratása
12
Halmazműveletek: unió A feladat most két tömb a[0..n-1] és b [0..m-1] elemeinek egyesítése c tömbbe. Az egyes tömbökben egy elem nem szerepel kétszer. (mint az előbb) A legkézenfekvőbb megoldás: tegyük be c-be a összes elemét, majd b-ből azokat, melyek nem szerepelnek a-ban. c elemszáma legfeljebb n+m. Feltételeztük, hogy egyik halmaz sem üres.
13
Unió C# kód for (i = 0; i < n; i++) c[i] = a[i]; //a-t áttöltjük c-be k = n; for (j = 0; j < m; j++){ // keressük azt a b-belit, ami nincs a-ban for (i = 0; (i < n) && (b[j] != a[i]); i++) ; if (i >= n) c[k++] = b[j]; // ha volt b-beli, ami nincs a-ban , c-be tesszük } for (i = 0; i < k; i++) Console.WriteLine(c[i]); // Futásidő n*m nagyságrendű! 14
Unió speciális esetben: az a és b (halmazokat reprezentáló) tömbök rendezettek (összefuttatás)
i = 0; j = 0; k = 0; while(( i < n) && ( j < m)) if (a[i]
15
Miért érdemes rendezni?
Például azért, mert rendezés után könnyebb keresni! A következő pszeudokód visszaadja egy rendezett tömb ama indexét, amelynél a tömb értéke a keresett (value); illetve a nem talált üzenetet.
16
Pszeudokód BinarySearch(A[0..N-1], value) { low = 0; high = N – 1; while (low <= high) { mid = (low + high) / 2; if (A[mid] > value) high = mid – 1; else if (A[mid] < value) low = mid + 1; else return mid; } return not_found; }
17
Buborékrendezés
Előkészítés:
int n = 10, i; int[] a = new int[n]; // a tömb 0..n-1, ezt rendezzük Random RandomClass = new Random(); for (i = 0; i < n; i++) a[i] = RandomClass.Next(10, 30); // 10 és 30 közötti egész értékeket generálok for (i = 0; i < n; i++) Console.WriteLine(a[i]);
18
Buborék C# kódja int csere_volt = 1; // azért, hogy a while elinduljon reset flag while (csere_volt == 1) { csere_volt = 0; //reset flag for (i = 0; i < n - 1; i++) if (a[i] > a[i + 1]) // ha rossz a sorrend, cserélünk { int cs; cs = a[i]; a[i] = a[i + 1]; a[i + 1] = cs; csere_volt = 1; // még nem biztos, hogy jó a sorrend! } } // while vége
19
Miért működik?
A while ciklus egyszeri lefutásának hatására a legnagyobb elem az utolsó lesz Ha a sorozat (véletlenül) eleve rendezett, készen vagyunk, ha legalább egy csere történt, újra fut a while, és a következő legnagyobb elem az utolsó előtti lesz Minden lépésben a helyére kerül egy elem, tehát a while nem futhat n-nél többször.
20
A „legrosszabb” eset
A buborékrendezés a legrosszabb esetben Θ(n*n) lépést igényel Minden elem egy összehasonlítás árán legfeljebb egy pozíciót mozdulhat el (szomszédos elemeket hasonlítunk). Egy elem legfeljebb n - 1 távolságra lehet a sorrendileg megfelelő helyétől, így legfeljebb n - 1 = O(n) művelettel helyrekerül, így (n - 1)*(n - 1) = O(n*n) műveletnél nem lehet több a teljes rendezéshez. 21
Kétirányú buborék – cocktail sort bottom = 0; top = n-1; bool csere_volt = true; while (csere_volt == true){ csere_volt = false; for (i = bottom; i < top; i++) if (a[i] > a[i + 1]) { csere_volt= true; cs = a[i]; a[i]=a[i+1]; a[i+1]=cs; } top = top - 1; for (i = top; i > bottom; i--) if (a[i] < a[i - 1]) { csere_volt = true; cs = a[i]; a[i] = a[i-1]; a[i-1] = cs; } bottom = bottom + 1; } 22
Kerti törpe rendezés
Wikipedia: The name comes from the supposed behavior of the
Dutch garden gnome in sorting a line of flowerpots. It is conceptually simple, requiring no nested loops.
i = 1; while (i < n) { if (a[i - 1] <= a[i]) i++; // ha jó a sorrend, előre! else { int cs = a[i - 1]; a[i - 1] = a[i]; a[i] = cs; i--; if (i == 0) i = 1; } // else vége } // while vége 23
Hogyan működik?
Az algoritmus megkeresi az első olyan helyet, ahol két egymást követő elem rossz sorrendben van, és megcseréli őket. Ha egy ilyen csere után rossz sorrend keletkezik, az csak közvetlenül a legutolsó csere előtt lehet, így ezt is ellenőrizzük. Talán ez az elképzelhető legegyszerűbb rendezés. 24
Buborék javítása: fésűs rendezés http://yagni.com/combsort/
Szinte bármely programkönyvtárban a quicksort (fogjuk tanulni!) implementációja szerepel, mivel ezt tartják a leghatékonyabb rendezésnek. Mindenki tudja, hogy a buborék a világ egyik legrosszabb rendezése. Azonban, egy egyszerű módosítással annyira javítható, hogy vetekszik a quicksorttal – állítja Wayne Conrad. Az eredeti fésűs rendezést Stephen Lacey és Richard Box fejlesztette, és a Byte Magazine publikálta 1991-ben. 25
Futásidők: 10 ezer kevert egész szám rendezése CPU másodpercben Wayne szerint
C++ Quicksort (in-place) 0.0038 Combsort 0.0042 Bubblesort 1.36
26
int gap = n; for (;;) {
// az a táv, mely az összehasonlítandókat elválasztja
gap = (gap * 10) / 13; // konvertál egészre, ha kell if (gap == 9 || gap == 10) gap = 11; if (gap < 1) gap = 1;
bool csere_volt = false; for (i = 0; i < n - gap; i++) { int j = i + gap; if (a[i] > a[j]) { int cs = a[i]; a[i] = a[j]; a[j] = cs; csere_volt = true; } } // belső for ciklus vége if (gap == 1 && !csere_volt) break; // kilépés a külső for-ból } // külső for ciklus vége
27
Megjegyzések
10 ezer elemű tömbön végzett kísérletek szerint a fésűs rendezés alig rosszabb a quicksortnál (10 %-kal); a változtatás a buborékhoz képest nem nagy. Ugyanakkor nem kell gondoskodni az eleve rendezett esetről, ami a quicksortot nagyon lelassítja (látni fogjuk). A gap beállításával először a távollevő elemeket rendezzük. Ezután a gap csökken, míg végül egy lesz. Ez esetben azonos a program a buborékkal; következésképpen korrekt. Lacey és Richard Box megmutatták, hogy a gap minden lépésben 1.3-mal osztandó. Továbbá felfedezték, hogy 9 és 10 nem alkalmas gap-nek, és 11-gyel helyettesítendő.
28
Sorting demo [ellenőrizve 2007. nov. 8.]
http://www.cs.ubc.ca/~harrison/Java/sorting -demo.html
29