Labor, 10. hét: láncolt listák II.

Czirkos Zoltán, Pohl László · 2019.07.30.

Láncolt listákkal és fájlkezeléssel kapcsolatos gyakorlófeladatok régebbi ZH-kból.

Ez a labor új anyagot nem mutat be. A feladatok régebbi ZH-kból származnak. Hasonlók az idei második NZH-ban is lehetnek. Ha úgy érzed, ez már jól megy, helyette dolgozhatsz a nagy házidon is.

Felkészülés a laborra:

1. Debugmalloc

A nagy házihoz adunk segédletként egy Debugmalloc nevű programot, amely a memóriakezelési hibák egy részét képes fölfedezni. Erről bővebb leírást a Segédlet/Nagy házi menüpont alatt találsz: Debugmalloc. A mostani laborfeladat ennek az eszköznek a kipróbálása.

  • Hozz létre egy projektet a szokásos módon a Code::Blocksban (File, New project stb.)
  • Töltsd le az alábbi két fájlt: debugmalloc.h és debugmalloc-impl.h. Másold be ezeket a projekted mappájába, a main.c fájl mellé:
  • Add hozzá ezeket a fájlokat a projektedhez. Ehhez előbb kattints jobb gombbal a projekt nevére a management ablakban:
  • Aztán válaszd ki a fájlokat, és kattints az okéra! Ellenőrizd, hogy a fájlok tényleg be fognak-e kerülni a programba (Debug és Release be van-e jelölve):
  • Végül ehhez kell jutnod:

Ha kész vagy, a helló világ programod írd felül az alábbi forráskóddal! Ez kettő memóriakezelési hibát tartalmaz. Látod, melyek ezek? Ha igen, akkor se javítsd még ki őket!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "debugmalloc.h"

int main(void) {
    char txt[] = "hello";

    char *p = malloc(strlen(txt) * sizeof(char));
    strcpy(p, txt);
    printf("TXT: %s\n", p);

    return 0;
}

Futtasd le a programot, és figyeld meg a Debugmalloc hibaüzeneteit! Javítsd ki a hibákat!

2. Átszállások

Szöveges fájlokban metrójáratok megállóinak neveit tároljuk, soronként egyet. A nevek maximum 50 betűsek. A programod feladata, hogy láncolt listákba beolvassa a járatok adatait, és megmondja két adott járatról, hogy át lehet-e szállni egyikről a másikra, vagy nem; ha igen, akkor az is kérdés, hogy hol.

  • Írj függvényt, amely paraméterként egy fájlnevet kap, visszatérési értéke pedig egy járat megállóinak listája – a fájlban szereplő sorrendben.
  • Írj függvényt, amely megvizsgál két, paraméterként kapott megállólistát, hogy van-e átszállási lehetőség (azonos megállónév). A függvény visszatérési értéke egy sztring legyen, amely a megálló neve. (Mivel kell visszatérnie a függvénynek, ha nem lehet átszállni?)
  • Egészítsd ki mindezt teljes programmá, amelyben beolvasod az m2.txt és az m3.txt nevű fájlokat! Ha át lehet szállni, írd ki a megálló nevét, ha nem, akkor pedig írd ki azt! Ne felejtsd el felszabadítani a memóriát, amit foglaltál!

Adatok a teszteléshez: m1.txt, m2.txt, m3.txt és m4.txt.

3. Fésűs lista

Írj programot, amelyik tankörszámokat, NEPTUN kódokat, neveket és átlagokat tárol:

13 MZPERX 5.1 Köb Üki

Ez azt jelenti, hogy Köb Üki a 13-as tankörbe jár, MZPERX a kódja és 5.1-es az ösztöndíjátlaga. Írd meg a programot úgy, hogy fájlt olvasson be! Teszteléshez minta: nevsor-latin2.txt (Latin2, Windows), illetve nevsor-utf8.txt (UTF-8, Linux). Válaszd az alábbi adatszerkezetek valamelyikét:

  • Egyszerűbb változat: építs egyszeresen láncolt listát az adatokból!
  • Nehezebb változat: építs fésűs listát! Ebben a „külső” lista a tankörök listája, azokon belül pedig a „belső” listák a hallgatókat tartalmazzák elemekként.

Ellenőrzésként írasd ki az adatokat a konzolra! Végül pedig ne feledkezz meg az adatszerkezet felszabadításáról sem!

Megoldás

A megoldásban egy fésűs listát kell alkalmazni: kívül a tankörök listája, és mindegyik tankör tartalmaz egy névsort (hallgatók listája).

Minden beolvasott hallgatónál előbb meg kell keresni a tankört (vagy létrehozni, ha még nincs), és utána abba a listába betenni. Mivel a feladat nem határozza meg a sorrendet, egyszerűen az elejére szúrjuk be az adatokat.

#include <stdio.h>
#include <stdlib.h>


/* egy hallgato adatai - lancolt listaban */
typedef struct Hallgato {
    char neptun[6 + 1];     /* a neptun kodja, max 6 */
    char nev[30 + 1];       /* a neve, max 30 */
    double atlag;           /* az atlaga */
    struct Hallgato *kov;
} Hallgato;


/* egy tankor adatai: egy nevsor */
typedef struct Tankor {
    int szam;               /* sorszama */
    Hallgato *nevsor;       /* nevek */
    struct Tankor *kov;
} Tankor;


/* beolvassa a szabvanyos bemenetrol a sorokat.
 * visszater egy uj Tankor listaval. */
Tankor *tklista_beolvas(void) {
    Tankor *tklista = NULL; /* ez lesz a kimenet */
    Hallgato temp;          /* a beolvasashoz */
    int tk;

    /* %[^\n] - sztring enterig */
    while (scanf("%d %s %lf %[^\n]", &tk,
                 temp.neptun, &temp.atlag, temp.nev) == 4) {
        /* meglevo tankor keresese */
        Tankor *iter = tklista;
        while (iter != NULL && iter->szam != tk)
            iter = iter->kov;
        /* ha null, nem lett meg, uj kell */
        if (iter == NULL) {
            iter = (Tankor*) malloc(sizeof(Tankor));
            iter->szam = tk;
            iter->kov = tklista;
            iter->nevsor = NULL;
            tklista = iter; /* elejere */
        }
        Hallgato *ujh = (Hallgato*) malloc(sizeof(Hallgato));
        *ujh = temp;        /* minden adatot bemasolunk! */
        ujh->kov = iter->nevsor;
        iter->nevsor = ujh; /* elejere */
    }
    return tklista;
}


/* kilistazza a tankort es a nevsorokat. */
void tklista_listaz(Tankor *tklista) {
    Tankor *tkiter;         /* tankorok iteratora */
    Hallgato *hgiter;       /* hallgatok iteratora */

    for (tkiter = tklista; tkiter != NULL; tkiter = tkiter->kov) {
        printf("%2d. tankor -- \n", tkiter->szam);
        for (hgiter = tkiter->nevsor; hgiter != NULL; hgiter = hgiter->kov)
            printf("%6s %-30s %g\n", hgiter->neptun, hgiter->nev, hgiter->atlag);
    }
}


void tklista_felszabadit(Tankor *tklista) {
    while (tklista != NULL) {
        Tankor *kov = tklista->kov;     /* kimentjuk */

        Hallgato *nevsor = tklista->nevsor;
        while (nevsor != NULL) {
            Hallgato *kov = nevsor->kov;   /* kimentjuk */
            free(nevsor);
            nevsor = kov;
        }

        free(tklista);
        tklista = kov;
    }
}


int main(void) {
    Tankor *tklista = tklista_beolvas();
    tklista_listaz(tklista);
    tklista_felszabadit(tklista);

    return 0;
}

4. Szállóvendégek

Egy hétemeletes szállodában a szobafoglalásokat egy olyan struktúrában tárolják, amely egy egészként tartalmazza a szobaszámot és egy maximum 50 karakteres sztringben a vendég nevét. A szobák a szokásos módon vannak számozva, a százasok helyiértéke adja meg az emeletet, a tízesek és egyesek pedig az adott szinten lévő szoba sorszámát (pl. 712 = 7. emelet, 12. szoba). A földszint a 0. szint.

  • Írj függvényt, amely a paramétereként megadott nevű szöveges fájlból beolvassa a vendégek listáját egy láncolt listába, és visszatér a lista címével! A fájlban az egyes foglalások külön sorban helyezkednek el, a sor elején a szobaszám áll, utána a név. (Hozz létre ilyen szövegfájlt néhány adattal, hogy tesztelni tudj!)
  • Írj függvényt, amely paraméterként kapja a vendégek listáját és a szintek tömbjét; az utóbbi, emelet sorszámával indexelt tömbbe pedig beírja, hogy az egyes emeleteken hány vendég lakik!
  • Írj függvényt, amely megkapja az így keletkezett szintek tömbjét, és visszatér a legzsúfoltabb emelet sorszámával, tehát azzal, ahol a legtöbb vendég van éppen!

Egészítsd ki ezeket teljes programmá, amely a felhasználótól beolvas egy nevet és megmondja, hogy az illető megszállt-e a szállóban a foglalasok.txt szerint, és ha igen, akkor a legzsúfoltabb emeleten van-e a szobája!

Megoldás
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct vendeg {
    int szobaszam;
    char nev[51];
    struct vendeg *kov;
} vendeg;

vendeg *beolvas(char const *fajlnev) {
    vendeg *lista = NULL, *uj, tmp;
    FILE *fajl;

    fajl = fopen(fajlnev, "rt");
    while (fscanf(fajl, "%d %[^\n]", &tmp.szoba, tmp.nev) == 2) {
        uj = (vendeg *) malloc(sizeof(vendeg));
        uj->szobaszam = tmp.szoba;
        strcpy(uj->nev, tmp.nev);       /* v. *uj = tmp */
        uj->kov = lista;
        lista = uj;
    }
    fclose(fajl);

    return lista;
}

void betoltottseg(vendeg *lista, int *szintek) {
    vendeg *iter;
    int i;
    for (i = 0; i < 8; ++i)
        szintek[i] = 0;
    for (iter = lista; iter != NULL; iter = iter->kov)
        szintek[iter->szobaszam / 100] += 1;
}

int legtobb_vendeg(int *szintek) {
    int i, max = 0;
    for (i = 1; i < 8; ++i)
        if (szintek[i] > szintek[max])
            max = i;
    return max;
}

vendeg *keres(vendeg *lista, char const *nev) {
    vendeg *iter;
    for (iter = lista; iter != NULL; iter = iter->kov)
        if (strcmp(iter->nev, nev) == 0)
            break;
    return iter;
}

int main(void) {
    char vendegnev[51];
    vendeg *lista = beolvas("foglalasok.txt");

    scanf("%[^\n]", vendegnev);
    vendeg *talalat = keres(lista, vendegnev);

    if (talalat == NULL)
        printf("A vendég nincs a szállóban.\n");
    else {
        int szintek[8];
        betoltottseg(lista, szintek);
        int legzsufoltabb = legtobb_vendeg(szintek);
        if (legzsufoltabb == talalat->szobaszam / 100)
            printf("A vendég a legzsúfoltabb szinten lakik.\n");
        else
            printf("A vendég nem a legzsúfoltabb szinten lakik.\n");
    }

    while (lista != NULL) {
        vendeg *temp = lista->kov;
        free(lista);
        lista = temp;
    }

    return 0;
}

5. További feladatok

Ha elkészültél, folytasd a feladatgyűjtemény kapcsolódó feladataival!