HW 9 - Řazení haldou (Heap Sort)

Termín odevzdání 17.05.2025@23:59 PDT
Povinné zadání 3b
Volitelné zadání Není
Bonusové zadání Není
Počet uploadů 20
Výchozí soubory bab36prga-hw9.zip
Podklady pro prioritní frontu jsou v přednášce věnované prioritní frontě. https://cw.fel.cvut.cz/wiki/courses/bab36prga/lectures/start#.

Povinné zadaní

Cílem úlohy je seznámit se s realizací prioritní fronty haldou a využit princip haldy v implementaci řazení v HeapSortu (řazení haldou). Pro jednoduchost jsou uvažována pouze celá čísla v rozsahu 0 až 4294967294 (UINT_MAX-1, viz limits.h), která zároveň definují priority, nižší číslo má vyšší priority (min-heap). Řešení se skládá z implementace rozhraní pq_heap.h do samostatné dynamické knihovny libpq_heap.so a programu, který načte vstupní čísla ze stdin a zapíše je vzestupně na stdout. Součástí výchozích souboru jsou kromě rozhraní pq_heap.h také binární programy generující vstup, refereční řešení a testovací program dynamicky linkovaný k příslušené variantě1) knihovny libpq_heap.so.

Jako refereční řešení lze použít také dostupný nástroj sort s přepínačem numerického řazení -n, viz man sort.

Datová struktura halda (heap) je plný binární strom reprezentovaný v poli pevné délky splňující vlastnost haldy, viz lec09. Pro tzv. min haldu tedy platí, že libovolný uzel strom má hodnotu nižší než jeho levý nebo pravý následník, pokud existují, a kořen stromu je tedy prvek s nejnižší hodntou.

Implementace řazení haldou je možné realizovat funkcí heapify nad existující haldou, pole valso n prvcích.

Z pohledu implementace prioritní fronty haldou v poli vals o n prvcích, můžeme implementovat i tak, že postupně zaměňuje hodnotu kořene, vals[0] s prvkem na konci pole vals[n-1] a nový kořen zaměňuje v poli o uvažované velikosti n-1 podobně jako při operaci pq_pop() tak, aby platila vlastnost haldy. Postupně tak zkracujeme uvažovanou délku pole haldy a postupnou záměnou všech n prvků jsou hodnoty v poli uspořádány sestupně. Proto následně pro zachování vlastnosti haldy na původním polem o velikosti n obrátíme pořadí prvků.

Implementace načtení hodnot a jejich vytištění v roustoucím pořadí postačí načtené hodnoty vkládat do priortiní fronty funkcí pq_push() a po načtení je postupně vyjímat funkcí pq_pop() a tisknout na stdout.

Součástí knihovny pq_heap jsou následující funkce pro práci s prioritní frontou.

  • Funkce pq_alloc() vytvoření datové struktury prioritní fronty.
  • Funkce pq_release() uvolnění alokované paměti struktury prioritní fronty.
  • Funkce pq_size() vrací počet prvků v prioritní frontě.
  • Funkce pq_is_empty() test, zdali je prioritní fronta prázdná.
  • Funkce pq_push() vložení prvku do prioritní fronty.
  • Funkce pq_pop() vyjmutí nejprioritnějšího (nejmenšího) prvku z prioritní fronty.
  • Funkce pq_peek() vrací hodnotu nejprioritnějšího (nejmenšího) prvku prioritní fronty.
  • Funkce pq_seek() vrací hodnotu prvku v poli priortní fronty haldy na předané pozici v poli.
  • Funkce pq_heapify() uspořádá akutální pole haldy vzestupně.
Volání pq_peek() a pq_seek() vyžaduje alespoň jeden prvek ve frontě. Rozhraní neumožňuje indikovat nevhodné volání návratovou hodnoutou. Proto v případě volání na prázdnou frontou je možné program předčasně ukončit voláním exit(), např. s hodnotou EAGAIN tak, jako v referenčním řešení.

Rozhraní definované v souboru ''pq_heap.h''

#include <inttypes.h>
 
/*
 * Initialize priority queue (pq) structure using heap as full binary tree implemented in array to store up to  the capacity items.
 *
 * return pointer to the dynamically allocated pq structure or NULL on a failure
 */
void *pq_alloc(uint32_t capacity);
 
/*
 * Release all allocated memory of the passed
 * pq structure that is supposed to be allocated by 
 * pq_alloc()
 */
void pq_release(void *pq);
 
/* 
 * Return number of items in the pq
 */
uint32_t pq_size(const void *pq);
 
/*
 * Return true if pq is empty
 */
_Bool pq_is_empty(const void *pq);
 
/* 
 * Insert the passed value v into the priority queue
 * according to its value. 
 *
 * Return true on success; otherwise false, e.g., pq is full).
 */
_Bool pq_push(void *pq, uint32_t v);
 
/* The priority queue (pq) is supposed to be implemented by min-heap, the item with the lowest value (priority) is popped first.
 *
 * Return true on success; otherwise false, e.g., pq is empty).
 */
_Bool pq_pop(void *pq, uint32_t *v);
 
/* 
 * Return the top item from the pq.
 * Function fail if call on empty queue
 */ 
uint32_t pq_peek(void *pq);
 
/* 
 * Return the item value from the seek index of the heap array
 * Function fail if seek is out of range.
 * For seek = 0, it behaves as pq_peek()
 *
 */ 
uint32_t pq_seek(void *pq, uint32_t seek);
 
/* 
 * Sort the internal representation using heap property, such that 
 * the complexity can be bounded by O(n log2 n). 
 * When heapifid, pq_seek for 0..pq_size() returns sorted values 
 */ 
void pq_heapify(void *pq);

Odevzdávané soubory

Odevzdávejte pouze soubor pq_heap.cs implementací prioritní fronty haldou. Soubory uložte přímo do zip archivu a nevytvářejte žádné složky. Správnost implementace bude ověřena testovacím programem.

Povinné soubory:

  • pq_heap.c - implementace rozhraní dle výchozího souboru pq_heap.h.

Způsob testování

Pro jednodušší testování jsme připravili testovací prgram bab36prga-hw9-test, který linkuje dynamickou knihovnu libpq_heap.so s implementací prioritní fronty a volá příslušené rozhraní a porovnává hodnoty ve frontě s referenčním řešením. Program zároveň testuje řazení a porovnává rychlost řazení, která je očekávána v rozsahu +/- 10 % času referečního řešení.

Referenční program bab36prga-hw9-genref lze použít pro generování náhodné posloupnosti celých čísel přepínačem -generate a uvedením počtu čísel, např. ./bab36prga-hw9-genref -generate 1000.

Příklad prostředí s knihovnou libpq_heap.so a testovacím programem binaries/bab36prga-hw9-test
ldd ./binaries/bab36prga-hw9-test
	linux-vdso.so.1 (0x00007b10c68ef000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007b10c67d9000)
	libpq_heap.so => ./libpq_heap.so (0x00007b10c67d4000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007b10c6400000)
	/lib64/ld-linux-x86-64.so.2 (0x00007b10c68f1000)
Spuštění testu vůči knihovně libpq_heap.so v aktuálním pracovním adresáři může vypadat například následovně.
./bab36prga-hw9-test
Basic test ........... PASSED
Multiple queues test . PASSED
Sort basic test ...... PASSED
DEBUG: Generate 42949672 random values in 5.8 seconds
DEBUG: Solution sort time  16.9 seconds
DEBUG: Reference sort time 16.6 seconds
DEBUG: Difference is 1.8 %
Sort time test ....... PASSED
All tests passed
Programem bab36prga-hw9-genref můžeme vygenerovat větší testovací soubor, který uložíme do ramdisku v adresáři /tmp. Nástrojem time můžeme změřit výkon řazení s využitím vlastní nebo výchozí prioritní fronty, například následovně.
clang -pedantic -Wall -O3 -std=gnu11 -g main.c -Wl,-rpath=. libpq_heap-fbsd.so -o main
 
./binaries/bab36prga-hw9-genref -generate 10000000 > /tmp/
 
time ./main </tmp/in >/tmp/out2
./main < /tmp/in > /tmp/out2  3.49s user 0.08s system 99% cpu 3.574 total
 
time sort -n /tmp/in > /tmp/out
sort -n /tmp/in > /tmp/out  20.54s user 0.94s system 99% cpu 21.475 total
 
time gsort -n /tmp/in > /tmp/out
gsort -n /tmp/in > /tmp/out  10.28s user 0.79s system 392% cpu 2.821 total
Kde si můžeme všimnout, že nástroj gsort je na daném stroji výrazně rychlejší, neboť využívá téměř 4 jádra procesoru. Naopak standardní sort je mnohem pomalejší než naše implementace.

Odevzdání a hodnocení

Povinné zadání
Název v BRUTE HW9
Odevzdávané soubory pq_heap.c
Argumenty při spuštění žádné
Kompilace clang -pedantic -Wall -Werror -std=gnu99 -O2 -g
Procvičované oblasti datové struktury
práce s polem
1)
Dle podporovaného OS.
courses/bab36prga/hw/hw9.txt · Last modified: 2025/02/17 19:19 by faiglj