Semestrální práce KIV/PC Řešení kolizí frekvencí sítě vysílačů
Zdeněk Bečvář A14B0466P
10. ledna 2016
Obsah 1 Zadání
1
2 Analýza úlohy 2.1 Uložení dat ze vstupního souboru 2.2 Graf kolizí . . . . . . . . . . . . . 2.3 Zásobník . . . . . . . . . . . . . . 2.4 Algoritmus řešení . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
2 2 3 4 4
3 Popis implementace 3.1 Použité datové struktury . . 3.1.1 Struktura vysilac . 3.1.2 Struktura frekvence 3.1.3 Struktura matice . . 3.1.4 Struktura zasobnik 3.2 Popis modulů . . . . . . . . 3.2.1 Modul main.c . . . . 3.2.2 Modul vstup.c . . . 3.2.3 Modul matice.c . . 3.2.4 Modul zasobnik.c . 3.3 Konstanty . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
6 6 6 6 6 6 6 6 7 7 8 8
. . . . . . . . . . .
4 Uživatelská příručka 4.1 Sestavení a spuštění programu Windows . . . . . . . . . . . . 4.2 Sestavení a spuštění programu 4.3 Vstupní data . . . . . . . . . 4.4 Výstup programu . . . . . . . 5 Závěr
. . . . . . . . . . .
. . . . . . . . . . .
9 na systému . . . . . . . . . . na systému Linux . . . . . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. 9 . 9 . 10 . 10 11
1
Zadání
Naprogramujte v ANSI C přenositelnou konzolovou aplikaci, která jako vstup načte z parametru příkazové řádky název textového souboru obsahující informaci o pozici vysílačů na mapě a na jeho základě přidělí každému vysílači frekvenci tak, aby jeho signál nekolidoval s vysílači v blízkém okolí. Program se bude spouštět příkazem freq.exe <soubor-s-vysílači>. Symbol <soubor-s-vysílači> zastupuje jméno textového souboru, který obsahuje informaci o rozmístění vysílačů na mapě a o dostupných vysílacích frekvencích, které jim je možné přidělit. Váš program tedy může být během testování spuštěn například takto: freq.exe vysilace-25.txt Výsledkem práce programu bude výpis do konzole se seznamem přidělených frekvencí každému vysílači ze vstupního souboru (viz Specifikace výstupu programu). V případě chyby nebo neřešitelné situaci skončí program výpisem příslušné chybové hlášky. Pokud nebude na příkazové řádce uveden právě jeden argument, vypište chybové hlášení a stručný návod k použití programu v angličtině podle běžných zvyklostí (viz např. ukázková semestrální práce na webu předmětu Programování v jazyce C). Vstupem programu je pouze argument na příkazové řádce — interakce s uživatelem pomocí klávesnice či myši v průběhu práce programu se neočekává. Hotovou práci odevzdejte v jediném archivu typu ZIP prostřednictvím automatického odevzdávacího a validačního systému. Postupujte podle instrukcí uvedených na webu předmětu. Archiv nechť obsahuje všechny zdrojové soubory potřebné k přeložení programu, makefile pro Windows i Linux (pro překlad v Linuxu připravte soubor pojmenovaný makefile a pro Windows makefile.win) a dokumentaci ve formátu PDF vytvořenou v typografickém systému TEX, resp. LATEX. Bude-li některá z částí chybět, kontrolní skript Vaši práci odmítne. Úplný dokument zadání si lze prohlédnout na internetové adrese: http://www.kiv.zcu.cz/studies/predmety/pc/doc/work/sw2015-02.pdf
1
2 2.1
Analýza úlohy Uložení dat ze vstupního souboru
Pro uložení dat ze vstupního souboru můžeme použít dvě datové struktury. První je pole (obrázek 1). Statické pole nemá cenu používat, protože nikdy nevíme, kolik budeme mít od uživatele dat. Proto dynamické pole, které se bude za běhu programu dynamicky zvětšovat podle přibývajících dat, je mnohem vhodnější volba. Vkládání a vyhledávání v poli není časově náročné. Jelikož pole je uspořádáno v adresním prostoru za sebou, hned víme, na jakou adresu máme přistupovat. Oproti druhé datové struktuře, seznamu, pole může zabrat více paměti, pokud ho nezaplníme celé prvky.
Obrázek 1: Příklad pole. Druhou strukturou je již zmíněný spojový seznam (obrázek 2). Oproti poli má výhodu v menší paměťové náročnosti. Pro každý prvek se vyhrazuje samostatně paměť a nemusíme si ji vyhrazovat „dopředuÿ. Ovšem je zde nevýhoda, a to vkládání a vyhledávání prvků. Jelikož víme jen adresu prvního prvku, na libovolný prvek v seznamu musíme odkrokovat tolikrát, kolikátý je daný prvek. Pro data o tisících prvcích může být tento rozdíl znatelný.
Obrázek 2: Příklad spojového seznamu.
2
2.2
Graf kolizí
Graf kolizí má obdobné řešení jako ukládání dat ze vstupu. V prvním případě ale místo pole použijeme matici (obrázek 3). Pokud jsou dva vysílače sousedé (jsou v kolizi), matice sousednosti na daných indexech má hodnotu 1. Matice sousednosti je vhodná pro grafy husté. To jsou grafy s velkým počtem hran (počet hran přibližně roven druhé mocnině počtu vrcholů). Matice sousednosti má výhodu oproti spojovému seznamu v rychlosti zjištění existence hrany.
Obrázek 3: Příklad matice sousednosti. V druhém případě zůstane spojový seznam (obrázek 4). Pokud jsou dva vysílače sousedé, uloží si adresu svého souseda. Spojový seznam sousednosti je vhodný pro grafy řídké. To jsou grafy s malým počtem hran (počet hran mnohem menší než druhá mocnina počtu vrcholů).
Obrázek 4: Příklad spojového seznamu sousednosti.
3
2.3
Zásobník
Zásobník (obrázek 5) lze implementovat opět buď seznamem, nebo polem. Zásobník je určen pro dočasné uložení dat. Data uložena do zásobníku jako poslední se čtou jako první (LIFO „Last In - First Outÿ). Pro zásobník je také důležitý ukazatel adresy poslední přidané položky (vrchol zásobníku).
Obrázek 5: Ukázka zásobníku.
2.4
Algoritmus řešení
1. Načti vstupní data do paměti. 2. Vytvoř graf kolizí (obrázek 6). 3. Nastav všechny vrcholy grafu jako dosud neobarvené. 4. Procházej všechny vrcholy grafu a vlož do zásobníku první neobarvený vrchol. 5. Není-li zásobník prázdný, vyjmi vrchol ze zásobníku. 6. Vybranému vrcholu přiřaď nejmenší možnou barvu, takovou, kterou nemá žádný stávající soused (obrázek 7). Pokud je nejmenší možná barva větší než dostupný počet barev, ukonči algoritmus a označ úlohu jako neřešitelnou. 7. Vlož do zásobníku všechny dosud neobarvené sousední vrcholy právě obarveného vrcholu. 4
8. Pokud je zásobník prázdný, načti další neobarvený vrchol (bod 4). Jinak pokračuj od bodu 5.
Obrázek 6: Ukázka převodu informací o vysílačích na graf kolizí.
Obrázek 7: Ukázka neobarveného grafu kolizí a jedna ze správných variant obarveného grafu.
5
3
Popis implementace
3.1 3.1.1
Použité datové struktury Struktura vysilac
Struktura pro uložení informací o vysílačích. Je implementována dynamickým polem, které se v případě potřeby (nedostatek místa pro další prvky) dynamicky zvětší. 3.1.2
Struktura frekvence
Struktura pro uložení informací o frekvencích. Stejně jako struktura vysilac je implementována dynamickým pole s dynamickým zvětšením paměti. 3.1.3
Struktura matice
Struktura pro vytvoření grafu kolizí. Jak název napovídá, graf je implementován maticí. 3.1.4
Struktura zasobnik
Struktura pro vytvoření zásobníku. Zásobník je implementován polem, které se v případě nedostatku paměti dynamicky zvětší.
3.2 3.2.1
Popis modulů Modul main.c
Tento modul je hlavní modul programu. • int main(int argc, char *argv[]) je hlavní funkce programu, která spojuje všechny části programu, přijímá argumenty z příkazové řádky, řídí běh programu a obarvuje graf kolizí. • void napoveda() tiskne nápovědu k programu, pokud uživatel zadá špatný vstup. • void matice_sousednosti (matice *mat_soused) vytvoří graf kolizí.
6
3.2.2
Modul vstup.c
Tento modul načítá data ze vstupního souboru, vyhrazuje pro ně paměť a dále s nimi pracuje. • void nacti_vstup(char *soubor) načte a uloží data ze vstupu do alokovaného pole. • void vytvor_frekvence_seznam() alokuje paměť pro frekvence. • void vytvor_vysilace_seznam() alokuje paměť pro vysílače. • void pridani_vysilace(int poradi, double osa_x, double osa_y) uloží vysílač do pole a dynamicky zvětšuje pole. • void pridani_frekvence(int poradi, int vysilaci_f) uloží frekvence do pole a dynamicky zvětšuje pole. • void hodnoty_citacu(int *vysilac, int *frekvence) vrací celkový počet frekvencí a čítačů. • int vypocet_vzdalenosti(int i, int j) vypočítá vzdálenost dvou vysílačů. • int barva_vysilace(int i) vrací barvu vysílače. • void nastav_barvu(int i, int kmitocet) nastavuje přidělenou barvu vysílači. • void tisk_vysilace(vysilac *pom) vypisuje jeden vysílač s jeho přidělenou frekvencí na obrazovku. • void vypis_vysilacu() vypisuje všechny vysílače. • void uvolni_vstup() uvolňuje alokovanou paměť pole frekvencí a vysílačů. 3.2.3
Modul matice.c
• matice *vytvor_matici(int radky, int sloupce) alokuje paměť pro matici sousednosti a nastaví všechny prvky matice na výchozí hodnotu. • void uvolni_matici(matice* pom) uloží informaci o sousedovy na dané pozici matice.
7
• void nastav_souseda(matice* pom, int radek, int sloupec, int soused) vrací hodnotu souseda na dané pozici matice. • int zjisti_souseda(matice* pom, int radek, int sloupec) uvolňuje alokovanou paměť matice, její řádky a všechny sloupce. 3.2.4
Modul zasobnik.c
• zasobnik *vytvor_zasobnik(int velikost, int velikost_prvku, matice *mat_soused) alokuje paměť pro zásobník. • void zasobnik_push(zasobnik *pom, void *prvek_push, matice *mat_soused) vkládá data do zásobníku a dynamicky ho zvětšuje. • void zasobnik_pop(zasobnik *pom, void *prvek_pop) odebírá data ze zásobníku. • int zasobnik_citac(zasobnik *pom) zjišťuje počet prvků v zásobníku. • void uvolni_zasobnik(zasobnik *pom) uvolňuje alokovanou paměť zásobníku.
3.3
Konstanty
V programu jsou použity konstanty pro odstranění výskytu magických čísel. V modulu main.c: VYCHOZI_ZASOBNIK 20 výchozí velikost zásobníku. V modulu vstup.c: VYCHOZI_VYSILACE 100 výchozí velikost pole pro vysílače. VYCHOZI_FREKVENCE 10 výchozí velikost pole pro frekvence. MAX_DELKA_TEXTU 23 délka pole pro text ze vstupního souboru. VYCHOZI_BARVA -1 výchozí barva pro vysílač. V modulu matice.c: VYCHOZI_SOUSED 0 výchozí hodnota pro souseda vysílače.
8
4
Uživatelská příručka
Program freq.exe je konzolovou aplikací. Aplikace je přeložitelná a spustitelná na systémech Windows i Linux. Pokud aplikace není přeložena (nemáme k dispozici freq.exe), překládá se pomocí souboru makefile a je vyžadováno mít nainstalovaný překladač, např. GCC se správně nastavenou cestou Path.
4.1
Sestavení a spuštění programu na systému Windows
• Otevřete příkazovou řádku v adresáři se zdrojovými soubory. • Spusťte překlad a sestavení programu příkazem mingw32-make (obrázek 8).
Obrázek 8: Překlad a sestavení programu v příkazovém řádku. • Spusťte program příkazem freq.exe
, kde je název vstupního souboru s daty (obrázek 9).
4.2
Sestavení a spuštění programu na systému Linux
• Otevřete terminál v adresáři se zdrojovými soubory. • Spusťte překlad a sestavení programu příkazem make. • Spusťte program příkazem freq.exe , kde je název vstupního souboru s daty (obrázek 9).
9
4.3
Vstupní data
Soubor se vstupními daty (obrázek 9) by měl obsahovat informace o dostupných vysílacích frekvencích pro vysílače, dosah vysílače a souřadnice vysílačů. Formát dat: Available frequencies: (ID frekvence) (kmitočet [Hz]) Transmission radius: (rádius) Transmitters: (ID vysílače) (souřadnice na ose x [km]) (souřadnice na oes y [km])
Obrázek 9: Ukázkový vstupní soubor.
4.4
Výstup programu
Při spuštění programu se souborem s daty ve správném formátu má výstup (obrázek 10), pokud je zadání řešitelné, formát: (ID vysílače) (přidělená vysílací frekvence)
10
Obrázek 10: Ukázkový výstup programu.
5
Závěr
Semestrální práci jsem napsal na počítači (CPU Intel Xeon E3-1230 v3 3.30 Ghz, RAM 8 GB DDR3, GPU ASUS R9 280X) s Windows 8.1 64-bit ve vývojovém prostředí Dev-C++ s překladačem GCC verze 4.9.2. Čas běhu programu se zvyšuje s větším objemem vstupních dat. Přesto časy nejsou nijak závratné a běh programu je velmi dobrý. Pro data se čtyřmi frekvencemi a dvaceti pěti vysílači je průměrný čas běhu programu 32 ms a pro data s dvaceti čtyřmi frekvencemi a tisíci vysílači je průměrný čas běhu programu 200 ms. S jazykem C jsem do předmětu KIV/PC neměl téměř žádné zkušenosti (pouze náhled na Arduino a jeho vývojové prostředí) a obecně se nepovažuji za příliš dobrého programátora. I tak si myslím, že jsem semestrální práci vypracoval obstojně a dle zadání. S tím mi také velmi pomohlo cvičení, z kterého jsem čerpal nápady a konstrukce programu. Možné zlepšení by bylo implementace grafu kolizí spojovým seznamem, který by nejspíše (záleží na počtu hran) nezabíral tolik místa v paměti.
11
Při vypracovávání semestrální práce jsem se potýkal s problémem, kdy a jak začít. Nakonec jsem začal akorát a pod rouškou deadlinu jsem práci dokončil. Také se vyskytl problém v podobě plánované odstávky elektřiny, kterou jsem si ale vůbec nenaplánoval. A ještě k LATEXu. I přes bídný začátek samostudia (jukebox přednášky jsem se bohužel nemohl zúčastnit) jsem si základní prvky celkem osvojil. Ve výsledku mě tento typografický systém překvapil a rozhodně jsem rád za „donuceníÿ psání těchto řádků v LATEXu.
Pěkná dokumentace i kód.
12
Jako zpestření OK, ale tohle se do závěru většinou nepíše ;)