Programozás I. C nyelv 12. előadás Bináris fa, bináris kereső fa, kupac, időkezelő függvények Veszprémi Egyetem Heckl István, heckl@dcs.vein.hu 1
Fogalmak Fa: összefüggő, körmentes gráf, azaz bármely két csúcsa között pontosan egy út van Bináris fa: olyan fa, amelynek csúcspontjaiból maximum 2 részfa nyílik A fát tekintjük irányítottnak, ekkor az élek a szülőktől a gyerekek felé mutatnak 2
Fogalmak Szülő: egy csomópont megelőző csomópontja a gyökeret kivéve valamennyi csomópontnak van szülője Ős: egy csomópont szülője és a gyökér által meghatározott út valamely csomópontja Gyerek: az adott csomópontot közvetlenül követő elem bináris fáknál megkülönböztethetünk bal, illetve jobb oldali gyerekeket a gyerekek egymásnak testvérei Leszármazott: azon csomópontok halmaza, amelyek irányított úton elérhetőek az adott csomópontból 3
Fogalmak Mélység / magasság: a fa leghosszabb útjának hossza Gyökér: az a csomópont, amelynek nincs őse Levél: olyan csomópont, amelynek nincs gyereke Szintszám: a fa gyökeréhez a 0 szintszám tartozik, a további csomópontokhoz pedig mindig 1 -gyel nagyobb szintszám, mint a szülő szintszáma 4
Példa Szülő: 7 Ős: {2,7} Bal gyerek: 1 Jobb gyerek: 5 Leszármazottak: {1, 5} Levelek: {1, 5, 8, 3} 2. szint: {4, 9} Gyökér: 2 Mélység: 4 5
Bináris fák bejárása Fajtái: preorder inorder postorder A bejárások rekurzívak és az aktuális elem feldolgozásának helyét jelölik 6
Bináris fák bejárása Preorder bejárás: - a gyökér feldolgozása - a bal részfa preorder bejárása - a jobb részfa preorder bejárása - eredmény: 1, 2, 4, 5, 3, 6, 7 Step 1 Step 2 Step 3 1 2 3 4 5 6 7 7
Inorder bejárás: Bináris fák bejárása a bal részfa inorder bejárása a gyökér feldolgozása a jobb részfa inorder bejárása eredmény: 4, 2, 5, 1, 6, 3, 7 bináris keresőfákból növekvő sorrendben olvassa ki az értékeket Step 2 Step 1 Step 3 1 2 3 4 5 6 7 8
Postorder bejárás: Bináris fák bejárása a bal részfa postorder bejárása a jobb részfa postorder bejárása a gyökér feldolgozása eredmény: 4, 5, 2, 6, 7, 3, 1 matematikai kifejezések kiértékelésekor használják Step 3 Step 1 Step 2 1 2 3 4 5 6 7 9
Bináris keresőfa Bináris keresőfa (bst -binary search tree): olyan bináris fa, amelynek minden csúcsára igaz, hogy a bal oldali leszármazottak kulcs értékei nem nagyobbak, mint az aktuális elem kulcs értéke, a jobb oldali leszármazottak kulcsértékei pedig nagyobbak Bináris keresőfa felépítése: új elem bekérése indulás a gyökértől ha az új elem kulcsa nem nagyobb, mint az aktuális elem kulcsa, akkor balra egyébként jobbra megyünk ha adott irányba nincs elem, akkor oda beszúrjuk az új elemet 10
Elemek: 12, 23, 8, 25, 10, 18 11
Bináris keresőfa A bináris keresőfa előnye a listával szemben, hogy egy elem gyorsabban megtalálható a fa mélysége általában kisebb, mint a lista hossza Adott kulcsú elem keresése: indulás a gyökértől ha a keresett elem és az aktuális elem kulcsa: megegyezik, akkor végeztünk ha az előbbi kisebb, akkor jobbra megyünk ha az előbbi nagyobb, akkor balra megyünk A fa felépítése függ az elemek sorrendjétől Ha az elemek beszúrása sorrendben történik, akkor a fa lánccá degenerálódik (legrosszabb eset) 12
Bináris keresőfa adatszerkezet megvalósítása Implementálás: döntsd el, hogy van-e szülő mutató válaszd ki a tárolni kívánt adatokat külön struktúrában tároljuk vagy nem? határozd meg a kulcs elemet döntsd el, hogy reprezentálod a fát: mutató, sentinel, osztály válaszd ki a megvalósítandó függvényeket függvény implementálásakor rajzold le az összes lehetséges esetet beszélő neveket használj 13
#include <stdio.h> #include <stdlib.h> struct node { int key; struct node *less, *bigger; int data1; }; void printnode(struct node* cs) { printf("[key:%d, data1: %d]", cs->key, cs->data1); } void createnode(struct node* act) { // filling up the node with data printf("creating a new node\n"); printf("key:"); scanf("%d", &act->key); printf("data1:"); scanf("%d", &act->data1); } 14
Magyarázat struct node: fa csomópont típusának a meghatározása nincs külön struktúra az adatoknak printnode: egy csomópont kiírása createnode: csomópont adatainak a bekérése paraméter átadás cím szerint, hogy ne kelljen pluszban rekordot másolni 15
void printtree(struct node* cs) { if (!cs) return; if(cs->less) printtree(cs->less); printnode(cs); if(cs->bigger) printtree(cs->bigger); } void freetree(struct node* cs) { if (!cs) return; if(cs->less) freetree(cs->less); if(cs->bigger) freetree(cs->bigger); free(cs); } 16
Magyarázat A fa listázása inorder sorrendben történik, mert így kapjuk meg az elemeket növekvő sorrendben először kilistázzuk rekurzívan az adott csomópontnál kisebb elemeket az adott csomópontot majd az adott csomópontnál nagyobb elemeket A fa törlése postorder sorrendben történik, egy elemet akkor törölhetünk, ha mindkét részfáját már töröltük egy elem törlése után már nem hivatkozhatunk a less és bigger adattagjaira 17
struct node* searchnode(struct node* root, int key) { if (!root) return NULL; struct node* act = root; while (act) { if (act->key==key) return act; if (key<=act->key) act = act->less; else act = act->bigger; } return NULL; } struct node* smallestnode(struct node* act) { // return the smallest element in subtree rooting at act if (!act) return NULL; if (!act->less) return act; return smallestnode(act->less); } 18
Magyarázat searchnode adott kulcsú elemet keres meg smallestnode a részfa legkisebb elemét keresi meg rekurzív a jobb szélső gyerek a legkisebb, a balszélső a legnagyobb Mindét függvény a keresőfa tulajdonságot használja ki 19
search: 25 smallest of 31 12 1 root 2 (25>12) 12 bigger bigger bigger bigger 23 3 (25>23) 23 start 1 less less 20 31 4 (25<31) 20 31 2 less less 25 3 25 less less 24 24 20
struct node** linkaddresstoinsert(struct node* act, int key) { } // return the address of the less or bigger pointer of the node to where key // have to be inserted if (key<=act->key) { } if (act->less) else else { } return linkaddresstoinsert(act->less, key); return &act->less; if (act->bigger) else return linkaddresstoinsert(act->bigger, key); return &act->bigger; 21
void insertnode(struct node** root, struct node* cs) { struct node* act = *root, *newnode, **linkadress; newnode = (struct node*)malloc(sizeof(struct node)); *newnode=*cs; newnode->less=null; newnode->bigger=null; if (*root==null) { *root=newnode; return; } linkadress=linkaddresstoinsert(*root, cs->key); *linkadress=newnode; } 22
Magyarázat linkaddresstoinsert: a beszúrás szabályai szerint megkeresi azt a csomópontot, amely az adott kulcsú elem szülője lesz visszaadja a less illetve bigger mutatók közül a megfelelőnek a címét ha csak a szülőnek adjuk vissza a címét, akkor utána még vizsgálni kell, hogy melyik mutatója megfelelő insertnode két esetet vizsgál: még nem létezik a fa: a gyökeret módosítani kell már létezik a fa: meg kell keresni az új elem szülőjét létezik nem rekurzív változat is 23
insert 18 12 **root NULL bigger bigger *linkaddress 23 less 20 31 NULL NULL less NULL 18 25 24 less NULL NULL NULL 24
void insertnode(struct node** root, struct node* cs) { // non-recursive struct node* act = *root, *newnode; newnode = (struct node*)malloc(sizeof(struct node)); *newnode=*cs; newnode->less=null; newnode->bigger=null; if (*root==null) { *root=newnode; return; } while (1) { if (newnode->key<=act->key) { if (act->less == NULL) { act->less = newnode; return; } else act = act->less; } else { if (act->bigger == NULL) { act->bigger = newnode; return; } else act = act->bigger; } } // while end } 25
struct node** linkaddressto(struct node* root, struct node* child) { } // address of the less or bigger pointer of the parent of 'child' // this is the address of the link pointing to child if (!root!child child==root) return NULL; struct node* act = root; int key=child->key; while (act) { if (act->less==child) return &act->less; // return according to address } if (act->bigger==child) return &act->bigger; if (key<=act->key) else return NULL; act = act->less; act = act->bigger; 26
Magyarázat Adott elem szülőjének a less illetve bigger mutató közül a megfelelőnek a címét adja vissza ha csomópontokban lenne parent mutató, akkor erre a függvényre nem lenne szükség 27
void deletenode(struct node** root, int key) { struct node *del, **linkaddresstodel, *newchild, *sub, **linkaddresstosub; if (*root==null) return; // no tree del=searchnode(*root, key); if (!del) return; // key not found linkaddresstodel=linkaddressto(*root, del); if (del->less==null del->bigger==null) {// 0,1 child if (del->less==null && del->bigger==null) newchild=null; // 0 child else if (del->less==null) newchild=del->bigger; // 1 child } else newchild=del->less; // 1 child if (linkaddresstodel) else free(del); return; *linkaddresstodel=newchild; *root=newchild; 28
} // 2 children sub=smallestnode(del->bigger); linkaddresstosub=linkaddressto(*root, sub); if (linkaddresstodel) *linkaddresstodel=sub; else *root=sub; sub->less=del->less; sub->bigger=del->bigger; *linkaddresstosub=null; free(del); 29
Magyarázat Csomópont törlésnél két eset van, a törlendő csomópontnak (del): 0 vagy 1 gyereke van 2 gyereke van Mindkét esetben vizsgálni kell, hogy a gyökeret töröljüke, ha igen akkor módosítani kell 30
Magyarázat Két gyerekű csomópont törlésekor a keresőfa tulajdonság megőrzése érdekében del helyére át kell mozgatni a kulcs szerinti szomszédját sub-t lehetséges sub értékek: smallestnode(del->bigger): a jobb oldali részfa legkisebb eleme biggestnode(del->smaller): a bal oldali részfa legnagyobb eleme sub-nak 0 vagy 1 gyereke van 31
delete 24 12 **root NULL bigger bigger bigger bigger 23 less less 20 31 NULL *linkaddresstodel NULL 25 less NULL less 24 less NULL *del less NULL NULL 32
bigger bigger bigger less less less less bigger 33
bigger bigger less less less bigger less less 34
void main() { struct node *root=null, newnode, node1={12, 0, 0, 12}, node2={23, 0, 0, 20}, node3={20, 0, 0, 7}, node4={31, 0, 0, 9}, node5={25, 0, 0, -1}, node6={24, 0, 0, 2}; // createnode(&newnode); insertnode(&root, &newnode); } insertnode(&root, &node1); insertnode(&root, &node2); insertnode(&root, &node3); insertnode(&root, &node4); insertnode(&root, &node5); insertnode(&root, &node6); printf("\nordered node list: "); printtree(root); if (searchnode(root, 25)) printf("\n25 is found in the tree\n"); deletenode(&root, 23); printf("\nordered node list after deleting 23: "); printtree(root); freetree(root); 35
Kupac (heap) Definíció: teljes fa: olyan fa, amelynek a szintjei jobbról balra folytonosan vannak feltöltve Max-kupac: olyan teljes bináris fa, amelynek minden csúcsára igaz, hogy a gyerekek kulcsértékei nem nagyobbak, mint a szülők kulcsértékei a gyökér a legnagyobb kulcsú Elsőbbségi sornak is hívják 36
Kupac megvalósítása Történhet: felhasználói adatszerkezetként az előbbi példához hasonlóan tömbként általában ha kupac rendezést valósítjuk meg és csak a kulcs a hasznos adat minden bináris fa tárolható tömbként nem bővíthető dinamikusan 37
Bináris fa reprezentálása tömbbel A tárolás szabályai: a fa gyökerét a tömb első eleme tárolja a k csomópont bal gyereke a tömb [2*k+1], jobb gyereke pedig a [2*k+2] helyet foglalja el a d mélységű bináris fa tárolásához 2^d elemet tartalmazó tömbre van szükség annál hatékonyabb, minél teljesebb a fa 38
void exchange(int* a, int i, int j) { } int t=a[i]; a[i]=a[j]; a[j]=t; void downheap(int* a, int size, int parent) { // the children of a[v] are already heaps // the root a[v] may violate the heap property // modify the tree to force the heap property } int left=2*parent+1, right=2*parent+2, maxchild=left; if (left>=size) return; // a[v] has no children if (right<size && a[right]>a[left]) maxchild=right; // if there are two children and the right is bigger, then it will be maxchild if (a[parent]>=a[maxchild]) return; // it is a heap else { exchange(a, parent, maxchild); // force heap property at the top downheap(a, size, maxchild); // force heap property in the changed subtree } 39
Magyarárzat downheap: egy bináris fát kupaccá alakit, amennyiben a gyökér bal és jobb oldali gyereke eredetileg már kupac ha a gyökérre nem érvényes a kupac tulajdonság, akkor fel kell cserélni a nagyobb értékű gyerekkel és a meg kell hívni újra a függvényt a módosult részfára létezik nem rekurzív algoritmus is magyarul felszívárog-nak hívják a függvényt, mert a maximumot hozza a gyökerébe 40
42 24 heap 26 25 31 20 5 42 downheap 31 26 25 24 20 5 41
void buildheap(int a[], int n) { // first create the basic heaps (they have 2 or elements) // when v==n/4-1, we regard such a tree whose two subtrees are already heaps // the new root may violate the heap property } for (int v=n/2-1; v>=0; v--) downheap(a, n, v); void heapsort(int* a, int n) { } buildheap(a, n); while (n>1) { n--; exchange(a, 0, n); // put the biggest of the current heap into its final place in the array // put the most right leaf at the top of the heap downheap(a, n, 0); // force the heap property on the decread heap } 42
Magyarázat buildheap: először kicsi aztán egyre nagyobb kupacokat hoz létre, addig míg minden csomópont része nem lesz a kupacnak egy különálló csomópont kupacnak tekinthető két kupacból egy új közös gyökér és a downheap segítségével lehet nagyobb kupacot létrehozni 43
44
24 42 26 0 1 2 3 4 5 6 24 42 26 25 31 20 5 25 31 20 5 42 downheap 31 26 0 1 2 3 4 5 6 42 31 26 25 24 20 5 25 24 20 5 45
Magyarázat heapsort: maxremove segítségével meghatározzuk először a legnagyobb elemet, aztán a maradék kupacból a legnagyobbat, stb. maxremove nincs külön megírva törli a gyökeret berakja helyére a legalsó szint levelei közül a jobb szélsőt meghívja az új gyökérre downheap-t 46
42 0 1 2 3 4 5 6 31 26 42 31 26 25 24 20 5 25 24 20 5 5 31 26 0 1 2 3 4 5 6 5 31 26 25 24 20 5 25 24 20 31 downheap 25 26 0 1 2 3 4 5 6 31 25 26 5 24 20 5 5 24 20 47
void printarray(int* array, int size) { int idxi=0; printf("["); for (;idxi<size;idxi++) printf("%d ", array[idxi]); printf("]"); } void main() { int a[]={15, 23, 48, 6, 89, 54, 2, 12}; int size = sizeof (a) / sizeof( int); printarray(a, size); heapsort(a, size); printarray(a, size); } 48
Egyéb fa típusú adatszerkezetek Kiegyenlített fa: olyan bináris keresőfa, amely biztosítja, hogy ne legyenek benne aránytalanul hosszú ágak a hatékonyság a mélységétől függ Binomiális kupac: olyan kupac, amely támogatja kupacok egyesítését B xy -fa: levelek azonos szinten vannak az elemek rendezettek egy csúcsnak x-y eleme lehet S-fa: olyan kiegyenlített bináris kereső fa amelyben a gyakran használt elemek vannak elől így azok gyorsan elérhetőek 49
Az időkezelés header fájlai Az idő és a dátum kezeléséhez a következő deklarációs állományokat kell beépíteni a programunkba: #include <time.h> #include <sys\timeb.h> 50
asctime függvény 26 karakteres sztringben adja vissza a *tblock struktúrában tárolt dátumot és időt Példa: char *asctime(const struct tm *tblock) Sun Jun 19 04:08:30 1994\n\0 51
clock függvény clock_t clock(void); Processzor idő lekérdezése visszatérési értéke a program indulása óta eltelt processzor idő belső egységben ANSI C kompatibilis A függvény segítségével két esemény közötti intervallum határozható meg Ha az időértéket másodpercben kívánjuk megkapni, a visszaadott értéket el kell osztani a CLK_TCK konstanssal 52
ctime függvény char *ctime(const time_t *time) Adátumot és az időt sztringgé alakítja, és a 26 karakteres sztringre mutató pointerrel tér vissza *time paraméter értéke a time függvény hívásával állítható be ANSI C kompatibilis 53
Példa #include<time.h> void main() { time_t now; now=time((time_t *)NULL); printf( It s %.24s,ctime(&now)); } 54
difftime függvény Afüggvény a megadott két time_t típusú időpont különbségét határozza meg másodpercekben double difftime(time_t time2,time_t timer); 55
ftime függvény Az aktuális időpontot és dátumot a timeb típusú struktúrába tölti a timeb struktúra szerkezete : struct timeb{ long time;/*1970.1.1 éjfél óta eltelt idő(mp)*/ short millitm;/*a másodpercek törtrésze (ms)*/ short timezone;/*a helyi és a GMT idő eltérése (mp)*/ }; short dstflag;/*0 ha nincs nyári időszámítás*/ 56
gmtime függvény Az aktuális dátumot és az időpontot Greenwich-i idővé (GMT) konvertálja és egy struct tm típusú struktúrába tölti time feltöltését a time hívásával végezhetjük A tm struktúra definíciója a time.h include file-ban található Visszatérési értéke a struktúrára mutató pointer DOS specifikus struct tm *gmtime(const time_t *timer); 57
localtime függvény A helyi time_t típusú dátumot és az ídőpontot egy struct tm típusú struktúrába tölti struct tm *localtime(const time_t *timer); 58
A tm struktúra szerkezete: struct tm{ }; int tm_sec;/*másodperc*/ int tm_min;/*perc*/ int tm_hour;/*óra (0-23)*/ int tm_mday;/*hónap napja (0-31)*/ int tm_mon;/*hó (0-11)*/ int tm_year;/*év (a naptári év- 1900)*/ int tm_wday;/*a hét napja (0-6 ;vasárnap=0)*/ int tm_yday;/*az év napja (0-365)*/ int tm_isdst;/*0 ha nincs nyári időszámítás*/ 59
mktime függvény Kitölti a tm struktúra hiányzó adattagjait és time_t formátumúra alakítja a struktúrában tárolt időt és dátumot time_t mktime(struct tm *t); 60
stime függvény A rendszeridőt és a dátumot állítja be A tp pointer mutat az idő értékére, amely 1970.január 1. 00:00:00 óra(gmt) óta eltelt időt tartalmazza másodpercekben int *stime(time_t *tp); 61
time függvény Az aktuális időt kérdezi le Visszatérési értékként és a nem NULL timer paraméterrel kijelölt objektumban megadja az aktuális időt (1970. Január 1. 00:00:00 óra GMT óta eltelt idő másodpercekben ANSI C és UNIX kompatibilis 62
Példa #include<time.h> void main() { int j,it,eltelt,s; for(j=0;j<2;j++) { it[j].s=ido_sec(&it[j]); } int eltelt=it[1].s-it[0].s; } 63
tzsed függvény a TZ környezeti változó értéke alapján beállítja a daylight, timezone és tzname globális változókat Szökő napok? 64