Prednáška 7
- Oznamy
- Parametre funkcií: opakovanie
- Funkcie a polia
- Funkcie a polia: načítanie a vypísanie poľa
- Triedenia
- Eratostenovo sito
Oznamy
Čo nás čaká v najbližšom čase
- Po prednáške bude na testovači zverejnené zadanie miniprojektu: väčšej úlohy na dlhší čas a viac bodov.
- Dnes algoritmy s poliami.
- Na cvičeniach dnes a zajtra príklady na polia, vrátane rozcvičiek (pri počítači).
- Za rozcvičky môžete tento týždeň dostať bonusový bod, ak ich na testovač odovzdáte na prvýkrát dobre.
- Cieľom je, aby ste si precvičili kompilovanie a spúšťanie programu a kontrolu jeho výstupov.
Prebrali sme premenné, polia, podmienky, cykly a funkcie.
- Z týchto stavebných prvkov sa dajú vystavať pomerne komplikované programy.
- Menej skúsení programátori si potrebujú prácu s týmito pojmami čo
najviac precvičiť. Skúste vyriešiť všetky príklady z cvičení,
pracujte na miniprojekte.
- Neodpisujte, nič sa tým nenaučíte.
- Kontaktujte nás, ak potrebujete pomôcť.
Parametre funkcií: opakovanie
Jednoduché typy, napr. int, double, bool: skopíruje sa hodnota do novej lokálnej premennej vo funkcii. Zmeny sa neprenesú do volajúcej funkcie.
#include <stdio.h>
void f1(int x) {
x++; // zmena x sa neprenesie do main (y zostane rovnaká)
printf("x: %d\n", x);
}
int main(void) {
int y = 0;
printf("y: %d\n", y);
f1(y);
printf("y: %d\n", y);
f1(y + 1);
printf("y: %d\n", y);
}
Funkcie a polia
- Keď do funkcie posielame pole, nerobí sa jeho kópia.
- Zmeny prvkov v poli zostanú aj po skončení funkcie.
- V hlavičke funkcie sa pole uvádza ako
int a[]. - Väčšinou potrebujeme poslať do funkcie aj veľkosť poľa v zvláštnej premennej.
V nasledujúcom programe
- funkcia
sucetspočíta súčet prvkov poľaasnprvkami - funkcia
prepisprepíše všetky prvky poľaasnprvkami hodnotoux - funkcia
mainvytvorí poleb, zavolá funkciesucetna pôvodné pole, zmení pole pomocouprepisa znovu spočíta súčet.
#include <stdio.h>
int sucet(int a[], int n) {
int sucet = 0;
for (int i = 0; i < n; i++) {
sucet += a[i];
}
return sucet;
}
int prepis(int a[], int n, int x) {
for (int i = 0; i < n; i++) {
a[i] = x;
}
}
int main() {
int b[3] = {1, 2, 3};
printf("Sucet: %d\n", sucet(b, 3));
prepis(b, 3, 10);
printf("Sucet: %d\n", sucet(b, 3));
}
Funkcie a polia: načítanie a vypísanie poľa
Dve základné funkcie, ktoré sa nám môžu zísť v programoch
- precvičíme si tiež ako sa pracuje s poliami vo funkciách
#include <assert.h>
#include <stdio.h>
int readArray(int a[], int maxSize) {
/* Od užívateľa načíta počet vstupných čísel.
* Potom načíta zadaný počet celých čísel a uloží ich do poľa a,
* Hodnota maxSize je veľkosť poľa,
* ktorú nemožno prekročiť.
* Funkcia vráti počet načítaných čísel. */
int n;
scanf("%d", &n);
assert(n <= maxSize);
for (int i = 0; i < n; i++) {
scanf("%d", &(a[i]));
}
return n;
}
void printArray(int a[], int n) {
for (int i = 0; i < n; i++) {
printf(" %d", a[i]);
}
printf("\n");
}
#define NMAX 100
int main() {
int a[NMAX];
int n = readArray(a, NMAX);
// tu môžeme pole nejako upraviť
printArray(a, n);
}
Triedenia
Máme pole čísel, chceme ich usporiadať od najmenšieho po najväčšie.
- Napr. pre pole 9 3 7 4 5 2 chceme dostať 2 3 4 5 7 9
- Jeden z najštudovanejších problémov v informatike.
- Súčasť mnohých zložitejších algoritmov.
- Veľa spôsobov, ako triediť, dnes si ukážeme zopár najjednoduchších.
Pomocná funkcia swap
Dostane pole a dva indexy a vymení hodnoty prvkov na týchto indexoch.
- Zíde sa pri triedení.
- Potrebujeme pomocnú premennú, kam si dočasne uložíme jednu z hodnôt, aby sme ju nestratili.
void swap(int a[], int i, int j) {
/* Vymeň hodnoty prvkov v poli a na pozíciách i a j. */
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
Bublinkové triedenie (Bubble Sort)
Idea: Kontrolujeme všetky dvojice susedných prvkov a keď vidíme menšie číslo za väčším, vymeníme ich
for (int i = 1; i < n; i++) {
if (a[i] < a[i - 1]) {
swap(a, i - 1, i);
}
}
- Ak sme nenašli v poli žiadnu dvojicu, ktorú treba vymeniť, skončili sme.
- Inak celý proces opakujeme znova.
Celé triedenie:
void sort(int a[], int n) {
/* usporiadaj prvky v poli a od najmenšieho po najväčší */
bool hotovo = false;
while (!hotovo) {
bool vymenil = false;
/* porovnávaj všetky dvojice susedov,
vymeň ak menší za väčším */
for (int i = 1; i < n; i++) {
if (a[i] < a[i - 1]) {
swap(a, i - 1, i);
vymenil = true;
}
}
/* ak sme žiadnu dvojicu nevymenili,
môžeme skončiť. */
if (!vymenil) {
hotovo = true;
}
}
}
- Čo ak vo for cykle dáme
for (int i = 0; i < n; i++) {? - Ako nahradíme premennú
hotovopríkazombreak?
Príklad behu:
prvá iterácia while cyklu
9 3 7 4 5 2
3 9 7 4 5 2
3 7 9 4 5 2
3 7 4 9 5 2
3 7 4 5 9 2
3 7 4 5 2 9
druhá iterácia while cyklu
3 7 4 5 2 9
3 4 7 5 2 9
3 4 5 7 2 9
3 4 5 2 7 9
tretia iterácia while cyklu
3 4 5 2 7 9
3 4 2 5 7 9
štvrtá iterácia while cyklu
3 4 2 5 7 9
3 2 4 5 7 9
piata iterácia while cyklu
3 2 4 5 7 9
2 3 4 5 7 9
Cvičenie: Ako sa bude správať algoritmus na nasledujúcich vstupoch,
koľkokrát zopakuje vonkajší while cyklus?
- Utriedené pole 1,2,…,n
- Pole n,1,2,…,n-1
- Pole 2,3,…,n,1
- Pole n,n-1,…,1
Triedenie výberom (selection sort, max sort)
Idea: nájdime najväčší prvok a uložme ho na koniec. Potom nájdime najväčší medzi zvyšnými a uložme ho na druhé miesto odzadu atď.
- V cykle uvádzame ako komentár invariant, podmienku, ktorá na tom mieste vždy platí. Takýto invariant nám pomôže si uvedomiť, že je náš program správny.
int maxIndex(int a[], int n) {
/* vráť index, na ktorom je najväčší prvok
z prvkov a[0]...a[n-1] */
int index = 0;
for (int i = 1; i < n; i++) {
if (a[i] > a[index]) {
index = i;
}
/* invariant: a[j] <= a[index] pre vsetky j=0,...,i*/
}
return index;
}
void sort(int a[], int n) {
/* usporiadaj prvky v poli a od najmenšieho po najväčší */
for(int kam = n - 1; kam >= 1; kam--) {
/* invariant: a[kam+1]...a[n-1] sú utriedené
* a pre každé i,j také že 0<=i<=kam, kam<j<n
* platí a[i]<=a[j] */
int index = maxIndex(a, kam + 1);
swap(a, index, kam);
}
}
Príklad behu programu
Vstup 9 3 7 4 5 2
Po výmene (9,2) 2 3 7 4 5 9
Po výmene (7,5) 2 3 5 4 7 9
Po výmene (5,4) 2 3 4 5 7 9
Po výmene (4,4) 2 3 4 5 7 9
Po výmene (3,3) 2 3 4 5 7 9
Cvičenie: Bude čas behu algoritmu výrazne odlišný pre pole utriedené správne a pole utriedené v opačnom poradí?
Triedenie vkladaním (Insertion Sort)
Idea:
- v prvej časti poľa prvky v utriedenom poradí
- zober prvý prvok z druhej časti a vlož ho na správne miesto v utriedenom poradí
Príklad behu algoritmu:
9 3 7 4 5 2
3 9 7 4 5 2
3 7 9 4 5 2
3 4 7 9 5 2
3 4 5 7 9 2
2 3 4 5 7 9
Ako spraviť vkladanie:
- Vkladaný prvok si zapamätáme v pomocnej premennej
prvok - Utriedené prvky posúvame o jedno doprava, kým nenájdeme správne miesto pre odložený prvok
void sort(int a[], int n) {
/* usporiadaj prvky v poli a od najmenšieho po najväčší */
for (int i = 1; i < n; i++) {
int prvok = a[i];
int kam = i;
while (kam > 0 && a[kam - 1] > prvok) {
a[kam] = a[kam - 1];
kam--;
}
a[kam] = prvok;
}
}
- Všimnime si podmienku
(kam > 0 && a[kam - 1] > prvok)- Ak
kamje 0, prvá časť jefalse, druhá časť sa už nevyhodnocuje - Ak by sme prehodili časti, program by mohol spadnúť
(a[kam - 1]> prvok && kam > 0)
- Ak
Cvičenie: Ako sa bude správať algoritmus na nasledujúcich vstupoch,
koľkokrát zopakuje priradenie a[kam] = a[kam - 1]?
- Utriedené pole 1,2,…,n
- Pole n,1,2,…,n-1
- Pole 2,3,…,n,1
- Pole n,n-1,…,1
Triedenia: zhrnutie
- Videli sme tri jednoduché algoritmy na triedenie
- Celý program s triedeniami je na zvláštnej stránke
-
Neskôr sa naučíme rýchlejšie algoritmy na triedenie, ktoré používajú rekurziu
- Precvičili sme si funkcie, parametre a polia
- K funkciám je dobré napísať, čo robia
- Do cyklov si môžeme písať invarianty
- Používajú sa pri formálnom dokazovaní správnosti
- Pomáhajú pochopeniu kódu
- Môžeme ich použiť na ručnú alebo automatickú kontrolu správnosti hodnôt premenných
- Príkaz
assertv knižniciassert.hkontroluje podmienku, napr.assert(i>=0 && i<n);ukončí program s chybovou hláškou ak podmienka neplatí
Cvičenie:
- Na vstupe je postupnosť N celých čísel. Zistite, či sa v nej nachádza nejaké číslo viackrát.
- Viete pri tom použiť triedenie?
Eratostenovo sito
- Prvočíslo je prirodzené číslo väčšie ako 1, ktoré je deliteľné len samo sebou a číslom 1.
- Chceli by sme nájsť všetky prvočísla medzi 2 a n.
- Napríklad ak n=30, výsledok má byť 2, 3, 5, 7, 11, 13, 17, 19, 23, 29.
Mohli by sme ísť cez všetky čísla a pre každé testovať, koľko má deliteľov (deliteľov sme už hľadali predtým), ale vieme to spraviť aj rýchlejšie. Použijeme algoritmus zvaný Eratostenovo sito.
- Vytvoríme pole a pravdivostných hodnôt, kde a[i] nám hovorí, či je i ešte potenciálne prvočíslo.
- Na začiatku budú všetky hodnoty
true, lebo sme ešte žiadne číslo nevylúčili. - Začneme číslom 2. Toto je iste prvočíslo, tak ho vypíšeme. O jeho
násobkoch však vieme, že iste nemôžu byť prvočísla. Nastavíme preto
pre každý násobok j=2*k pravdivostnú hodnotu a[j] na
false. - Potom prechádzame v poli, kým nenájdeme najbližšiu ďalšiu hodnotu
true. Toto číslo je prvočíslo, teda ho vypíšeme a vyškrtáme jeho násobky.
#include <stdio.h>
#include <stdbool.h>
#define N 30
int main() {
bool a[N + 1];
for (int i = 2; i <= N; i++) {
a[i] = true;
}
for (int i = 2; i <= N; i++) {
if (a[i]) {
printf("%d ", i);
for (int j = 2 * i; j <= N; j = j + i) {
a[j] = false;
}
}
}
printf("\n");
}
Výstup programu
2 3 5 7 11 13 17 19 23 29
Priebeh programu:
0 1 2 3 4 5 6 7 8 9 10 11 12 ...
? ? T T T T T T T T T T T ... na zaciatku
? ? T T F T F T F T F T F ... po vyskrtani i=2
? ? T T F T F T F F F T F ... po vyskrtani i=3
? ? T T F T F T F F F T F ... dalej sa uz skrtaju len vacsie cisla
Cvičenie: Napíšte funkciu, ktorá uloží prvočísla medzi 2 a N do zadaného poľa (ak by sme ich chceli použiť na ďalšie výpočty) a vráti ich počet.