Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
pp:lheap [2019/04/01 12:31] dmihai |
pp:lheap [2019/04/18 14:23] (current) georgem [Exerciții] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== TODO ====== | + | ====== Structuri funcționale de date ====== |
Scopul laboratorului: | Scopul laboratorului: | ||
* recapitularea conceptelor învățate | * recapitularea conceptelor învățate | ||
- | * programarea cu o structură funcțională | + | * programarea cu o structură de date funcțională |
* înțelegerea conceptului de "leftist heap" | * înțelegerea conceptului de "leftist heap" | ||
Line 17: | Line 17: | ||
* ''top'' - returnează elementul cu prioritatea maximă | * ''top'' - returnează elementul cu prioritatea maximă | ||
* ''delete'' - scoate elementul cu prioritatea maximă din coadă | * ''delete'' - scoate elementul cu prioritatea maximă din coadă | ||
+ | |||
+ | O altă operație utilă (și necesară în unele implementări) este ''merge'', care primește două liste de priorități și le combină într-una singură. | ||
Coada de priorități este o structură abstractă, ce poate avea diverse implementări care diferă prin complexitatea diverselor operații (e.g. ''insert'', ''delete''). | Coada de priorități este o structură abstractă, ce poate avea diverse implementări care diferă prin complexitatea diverselor operații (e.g. ''insert'', ''delete''). | ||
Line 22: | Line 24: | ||
Un exemplu naiv este implementarea unei cozi de priorități folosind o listă. Avem două posibilități: să introducem complexitatea determinării priorității în funcția ''top'', sau în ''insert''. | Un exemplu naiv este implementarea unei cozi de priorități folosind o listă. Avem două posibilități: să introducem complexitatea determinării priorității în funcția ''top'', sau în ''insert''. | ||
- | Putem pune mereu un element la începutul listei (astfel ''insert'' e echivalent cu '':''). Atunci când avem nevoie de cel cu prioritatea cea mai mare, pornim o căutare liniară prin elementele listei. Similar pentru ștergere. Avem astfel complexitățile: | + | Putem pune mereu un element la începutul listei (astfel ''insert'' e echivalent cu '':''). Atunci când avem nevoie de cel cu prioritatea cea mai mare, pornim o căutare liniară prin elementele listei. Similar pentru ștergere. Pentru operația de ''merge'' putem folosi append din Haskell (''%%+%%%%+%%''). Avem astfel complexitățile: |
| Funcție | Complexitate | | | Funcție | Complexitate | | ||
Line 29: | Line 31: | ||
| ''top'' | ''O(n)'' | | | ''top'' | ''O(n)'' | | ||
| ''delete'' | ''O(n)'' | | | ''delete'' | ''O(n)'' | | ||
+ | | ''merge'' | ''O(n)'' | | ||
+ | |||
+ | <note> | ||
+ | ''n'' este lungimea listei care stă la baza cozii. Pentru ''merge'', ''n'' este lungimea listei din stânga. | ||
+ | </note> | ||
- | Alternativ, simplificând funcția ''top'', ne asigurăm că elementele sunt mereu //ordonate// în listă (astfel, ''top'' este echivalent cu ''head''). Inserarea unui element într-o listă ordonată se face în timp liniar. Avem astfel complexitățile: | + | Alternativ, simplificând funcția ''top'', ne asigurăm că elementele sunt mereu //ordonate// în listă (astfel, ''top'' este echivalent cu ''head''). Inserarea unui element într-o listă ordonată se face în timp liniar (amintiți-vă de //insert sort//). Combinarea a două liste ordonate se face, deasemena în timp liniar (amintiți-vă de //merge sort//). Avem astfel complexitățile: |
| Funcție | Complexitate | | | Funcție | Complexitate | | ||
Line 37: | Line 44: | ||
| ''top'' | ''O(1)'' | | | ''top'' | ''O(1)'' | | ||
| ''delete'' | ''O(1)'' | | | ''delete'' | ''O(1)'' | | ||
+ | | ''merge'' | ''O(n+m)'' | | ||
+ | |||
+ | <note> | ||
+ | Pentru ''merge'', considerăm două liste cu dimensiunile ''n'', respectiv ''m''. | ||
+ | </note> | ||
Implementarea cu liste nu este ideală și putem obține performanțe mai bune. | Implementarea cu liste nu este ideală și putem obține performanțe mai bune. | ||
+ | |||
==== Binary Heap ==== | ==== Binary Heap ==== | ||
Un [[https://en.wikipedia.org/wiki/Binary_heap|binary heap]] este un arbore binar cu următoarele două proprietăți: | Un [[https://en.wikipedia.org/wiki/Binary_heap|binary heap]] este un arbore binar cu următoarele două proprietăți: | ||
- | * pentru orice nod, valoarea asociată este **mai mare sau egală** cu valorile copiiilor | + | * pentru orice nod, valoarea asociată acestuia este fie mai mare sau egală, fie mai mica sau egală cu valorile copiilor |
- | * arborele este [[https://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees|complet]]: fiecare nivel este plin, cu excepția ultimului, pe care nodurile sunt cât mai la stânga | + | * arborele este [[https://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees|complet]]: fiecare nivel este plin, cu excepția ultimului, pe care nodurile sunt cât mai la stânga - acest lucru permite ca arborele să fie ținut într-un array. |
+ | |||
+ | Pentru inserare se va introduce noul element la finalul array-ului. Noua valoare introdusă poate schimba locul cu părintele, în cazul în care prioritățile nu corespund (nu respectă ordinea impusă de noi). | ||
+ | |||
+ | Această operație poartă diverse denumiri: ''sift-up'', ''percolate-up'', ''cascade-up'', ... | ||
+ | |||
+ | Pentru ștergere se va elimina primul element, se mută ultimul element din array ca nou "varf" al arborelui, după care se va face swap între acesta și copilul cel mai mare/mic (în funcție de ordine). | ||
+ | |||
+ | Acestă operație se numește: ''sift-down'', ''percolate-down'', ''cascade-down'', ... | ||
+ | |||
+ | Se poate observa că aceste operații ar fi greu de implementat într-un stil funcțional. | ||
==== Leftist Heap === | ==== Leftist Heap === | ||
- | Un [[https://en.wikipedia.org/wiki/Leftist_tree|leftist heap]] este | + | Un [[https://en.wikipedia.org/wiki/Leftist_tree|leftist heap]] este o structură de date pur funcțională. |
+ | În acest tip de heap se mai menține o informație și anume un rank. Acesta reprezintă distanța la cea mai apropiată frunză. | ||
+ | Leftist heap-ul are următoarea proprietate: | ||
+ | * rank-ul oricărui subarbore stâng este cel puțin la fel de mare ca cel al subarborelui drept. | ||
+ | Leftist heap-ul utilizeză o operație generală de ''merge'' pentru a-și defini operațiile de ''insert'' și ''delete''. | ||
+ | |||
+ | Astfel, un ''insert'' înseamnă crearea unui nou nod și apelarea funcției de ''merge'' pe ''root'' și pe acesta. | ||
+ | |||
+ | Un ''delete'' se implementează prin eliminarea ''root-ului'' și apelarea funcției de ''merge'' pe copilul stâng și cel drept. | ||
+ | |||
+ | [[https://courses.cs.washington.edu/courses/cse326/08sp/lectures/markup/05-leftist-heaps-markup.pdf | Leftist heap - more info ]] | ||
+ | |||
+ | Obținem complexitățile: | ||
+ | |||
+ | | Funcție | Complexitate | | ||
+ | | ''isEmpty'' | ''O(1)'' | | ||
+ | | ''insert'' | ''O(log(n))'' | | ||
+ | | ''top'' | ''O(1)'' | | ||
+ | | ''delete'' | ''O(log(n))'' | | ||
+ | | ''merge'' | ''O(log(n))'' | | ||
===== newtype ===== | ===== newtype ===== | ||
Line 140: | Line 182: | ||
- Folosiți liste Haskell pentru a implementa o coadă de priorități. | - Folosiți liste Haskell pentru a implementa o coadă de priorități. | ||
- implementați o coadă de priorități care scoate valoarea //minimă// | - implementați o coadă de priorități care scoate valoarea //minimă// | ||
- | - implementați o coadă de priorități care scoate valoarea //maximă// | + | - implementați o coadă de priorități care scoate valoarea //maximă// (//hint:// folosiți ''newtype'' pentru a face diferența) |
- Folosiți arbori binari pentru a implementa un leftist heap | - Folosiți arbori binari pentru a implementa un leftist heap | ||
- | <note tip> | + | {{:pp:laborator_6_-_schelet.zip|Lab 6 - Schelet}}\\ |
- | Clasa ''PQueue'' conține unele funcții cu implementări default. Considerați înlocuirea acestora cu implementări particularizate. | + | |
- | </note> | + | |
===== Recommended Reading ===== | ===== Recommended Reading ===== | ||