Lineární spojový seznam (úvod do dynamických datových struktur)

Podobné dokumenty
Stromy. Jan Hnilica Počítačové modelování 14

Pointery II. Jan Hnilica Počítačové modelování 17

Základní datové struktury

Programování v C++, 2. cvičení

Lineární datové struktury

Spojová implementace lineárních datových struktur

Dynamické datové struktury I.

Dynamické datové struktury IV.

Abstraktní datové typy FRONTA

Více o konstruktorech a destruktorech

int ii char [16] double dd název adresa / proměnná N = nevyužito xxx xxx xxx N xxx xxx N xxx N

Reprezentace aritmetického výrazu - binární strom reprezentující aritmetický výraz

Datové struktury. alg12 1

Semestrální práce 2 znakový strom

Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody

Algoritmizace a programování

Binární vyhledávací strom pomocí směrníků Miroslav Hostaša L06620

Programování v C++ 2, 4. cvičení

Konstruktory a destruktory

Rekurze. Jan Hnilica Počítačové modelování 12

Správné vytvoření a otevření textového souboru pro čtení a zápis představuje

1. D Y N A M I C K É DAT O V É STRUKTUR Y

Dynamické datové typy a struktury

Cílem kapitoly je seznámit studenta se seznamem a stromem. Jejich konstrukci, užití a základní vlastnosti.

Kolekce, cyklus foreach

Algoritmy a datové struktury

Prioritní fronta, halda

Základy programování (IZP)

Martin Flusser. Faculty of Nuclear Sciences and Physical Engineering Czech Technical University in Prague. December 7, 2016

Pole a kolekce. v C#, Javě a C++

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Základy C++ I. Jan Hnilica Počítačové modelování 18

Správa paměti. Karel Richta a kol. Katedra počítačů Fakulta elektrotechnická České vysoké učení technické v Praze Karel Richta, 2016

Práce s pamětí a předávání parametrů. Úvod do programování 1

Práce s binárními soubory. Základy programování 2 Tomáš Kühr

Ukazatel (Pointer) jako datový typ - proměnné jsou umístěny v paměti na určitém místě (adrese) a zabírají určitý prostor (počet bytů), který je daný

Dynamické datové struktury III.

Spojové struktury. Spojová struktura (linked structure):

4. Rekurze. BI-EP1 Efektivní programování Martin Kačer

Programování v C++ 1, 16. cvičení

C++ přetěžování funkcí a operátorů. Jan Hnilica Počítačové modelování 19

Střední škola pedagogická, hotelnictví a služeb, Litoměříce, příspěvková organizace

Strukturované typy a ukazatele. Úvod do programování 1 Tomáš Kühr

ZPRO v "C" Ing. Vít Hanousek. verze 0.3

Datové typy a struktury

Stromy. Strom: souvislý graf bez kružnic využití: počítačová grafika seznam objektů efektivní vyhledávání výpočetní stromy rozhodovací stromy

Da D to t v o é v ty t py IB111: Datové typy

Anotace. Pointery. Martin Pergel,

Algoritmy II. Otázky k průběžnému testu znalostí

Implementace seznamů do prostředí DELPHI pomocí lineárního seznamu

Ukazatele, dynamická alokace

8 Třídy, objekty, metody, předávání argumentů metod

ADT/ADS = abstraktní datové typy / struktury

Strukturu lze funkci předat: (pole[i])+j. switch(výraz) velikost ukazatele

Datové struktury. Obsah přednášky: Definice pojmů. Abstraktní datové typy a jejich implementace. Algoritmizace (Y36ALG), Šumperk - 12.

Šablony, kontejnery a iterátory

Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod.

IUJCE 07/08 Přednáška č. 6

přetížení operátorů (o)

TGH07 - Chytré stromové datové struktury

8. lekce Úvod do jazyka C 3. část Základní příkazy jazyka C Miroslav Jílek

Využití OOP v praxi -- Knihovna PHP -- Interval.cz

Jazyk C++ II. STL knihovna kontejnery část 1

Implementace LL(1) překladů

Př. další použití pointerů

Základy programování (IZP)

TGH07 - Chytré stromové datové struktury

Algoritmy na ohodnoceném grafu

B3B33ALP - Algoritmy a programování - Zkouška z předmětu B3B33ALP. Marek Boháč bohacm11

PA152. Implementace databázových systémů

B3B33ALP - Algoritmy a programování - Zkouška z předmětu B3B33ALP. Marek Boháč bohacm11

Výhody a nevýhody jednotlivých reprezentací jsou shrnuty na konci kapitoly.

Základy programování (IZP)

Algoritmizace řazení Bubble Sort

Mělká a hluboká kopie

Základy programování (IZP)

Šablony, kontejnery a iterátory

IAJCE Přednáška č. 9. int[] pole = new int[pocet] int max = pole[0]; int id; for(int i =1; i< pole.length; i++) { // nikoli 0 if (Pole[i] > max) {

Struktura programu v době běhu

Vyhledávání. doc. Mgr. Jiří Dvorský, Ph.D. Katedra informatiky Fakulta elektrotechniky a informatiky VŠB TU Ostrava. Prezentace ke dni 21.

Datový typ prioritní fronta Semestrální práce z předmětu 36PT

Dynamická vícerozměrná pole. Základy programování 2 Tomáš Kühr

Seznamy a iterátory. Kolekce obecně. Rozhraní kolekce. Procházení kolekcí

Úvodem 9. Zpětná vazba od čtenářů 10 Zdrojové kódy ke knize 10 Errata 10. Než začneme 11

Operační systémy. Cvičení 4: Programování v C pod Unixem

Programování: základní konstrukce, příklady, aplikace. IB111 Programování a algoritmizace

Programování v jazyce C a C++

Prioritní fronta, halda (heap), řazení

Stromy, haldy, prioritní fronty

Čtvrtek 8. prosince. Pascal - opakování základů. Struktura programu:

Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15

Abstraktní datové typy

Grafové algoritmy. Programovací techniky

Anotace. Spojové seznamy, haldy. AVL-stromy, A-B stromy. Martin Pergel,

Úvod do jazyka C. Ing. Jan Fikejz (KST, FEI) 28. prosince Fakulta elektrotechniky a informatiky Katedra softwarových technologií

Dynamické datové struktury II.

Programování v C++ 3, 3. cvičení

Grafové algoritmy. Programovací techniky

Fronta (Queue) Úvod do programování. Fronta implementace. Fronta implementace pomocí pole 1/4. Fronta implementace pomocí pole 3/4

IAJCE Přednáška č. 8. double tprumer = (t1 + t2 + t3 + t4 + t5 + t6 + t7) / 7; Console.Write("\nPrumerna teplota je {0}", tprumer);

Transkript:

Lineární spojový seznam (úvod do dynamických datových struktur) Jan Hnilica Počítačové modelování 11 1

Dynamické datové struktury Definice dynamické struktury jsou vytvářeny za běhu programu z dynamicky alokovaných proměnných jednotlivé prvky struktury jsou mezi sebou spojeny pomocí pointerů, struktura se může za běhu programu měnit připojováním nových prvků, nebo dealokací prvků stávajících technicky je prvek realizován jako nějaký složený datový typ (v C struktura struct, v Pascalu záznam - record), který má mezi položkami jeden či více pointerů na další prvky Poznámky k definici dynamickou datovou strukturou se tedy nemíní např. obyčejné pole, byť by bylo dynamicky alokované jazyk C umožňuje dynamickou alokaci na zásobníku a na haldě - v případě dynamických datových struktur je typicky míněna alokace na haldě, v C pomocí funkce malloc (už známe) Výhody a nevýhody + velikost datové struktury je určována za běhu programu podle skutečných nároků + provázání prvků pomocí pointerů umožňuje vytvářet různě složité vazby (stromy, grafy...) - komplikovanější pohyb ve struktuře pomocí pointerů (ve srovnání například s polem) - kromě paměti potřebné pro uložení užitečných informací je potřeba alokovat paměť pro pointery Jan Hnilica Počítačové modelování 11 2

Lineární spojový seznam (LSS) nejjednodušší dynamická struktura každý prvek obsahuje pointer na další prvek seznamu poslední prvek má tento pointer nastaven na NULL (zakončení seznamu) LSS budeme pro jednoduchost demonstrovat na seznamu, jehož prvky obsahují jeden znak (seznam v tomto provedení je tedy jakýsi plastický řetězec). typedef struct prvek char znak; // uložená hodnota struct prvek * dalsi; // pointer na další prvek seznamu Prvek; LSS obsahující pozdrav AHOJ! vypadá takto: Prvek * S; // počátek seznamu S A dalsi H dalsi O dalsi J dalsi! dalsi NULL Jan Hnilica Počítačové modelování 11 3

Vytvoření (alokace) jednoho prvku Prvek * P = (Prvek*) malloc(sizeof(prvek)); // nezapomeneme přetypovat P->znak = 'a'; // nastavení informace P->dalsi = NULL; // pointer pro pořádek nastavíme na NULL Alokace jako funkce - vhodné, pokud budeme v programu vytvářet nové prvky na více místech - funkce vrací pointer na alokovaný prvek (tzn. vrací paměťovou adresu, na které prvek leží) Prvek * AlokujPrvek(char z) Prvek * P = (Prvek*) malloc(sizeof(prvek)); P->znak = z; P->dalsi = NULL; return P; Jan Hnilica Počítačové modelování 11 4

Vytvoření seznamu obsahujícího malá písmena abecedy a - z - napíšeme jako funkci vracející pointer na začátek seznamu - první prvek vytvoříme zvlášť, další prvky pak postupně připojujeme na konec seznamu Prvek * VytvorSeznam() Prvek *S, *P, *Q; S = (Prvek*)malloc(sizeof(Prvek)); // první prvek vytvoříme zvlášť S->znak = 'a'; Q = S; // pomocný pointer na aktuální konec seznamu for (char z = 'b'; z <= 'z'; z++) // další prvky vytvoříme v cyklu P = (Prvek*)malloc(sizeof(Prvek)); // nový prvek P->znak = z; Q->dalsi = P; // připojíme ho na konec seznamu Q = P; // posuneme aktuální konec Q->dalsi = NULL; // zakončení seznamu return S; // vracíme pointer na začátek seznamu Jan Hnilica Počítačové modelování 11 5

Jiné provedení funkce pro vytvoření seznamu - funkce bude jednodušší, pokud seznam vytváříme od konce - nové prvky připojujeme na začátek seznamu, první prvek nemusíme vytvářet zvlášť Prvek * VytvorSeznam() Prvek *S, *P; S = NULL; // máme prázdný seznam for (char z = 'z'; z >= 'a'; z--) P = (Prvek*)malloc(sizeof(Prvek)); // nový prvek P->znak = z; P->dalsi = S; // připojíme za něj zbytek seznamu S = P; // posuneme počátek na nový prvek return S; // vracíme pointer na začátek seznamu Jan Hnilica Počítačové modelování 11 6

Průchod přes všechny prvky seznamu (spojený s nějakou akcí, např. s výpisem znaků) - napíšeme jako funkci, která jako parametr obdrží pointer S na začátek seznamu void VypisSeznam(Prvek * S) while (S!= NULL) printf("%c ", S->znak); // výpis aktuálního prvku S = S->dalsi; // posun na další prvek Poznámka: Je potřeba si uvědomit, že pointer S na začátek seznamu byl do funkce předán hodnotou. To znamená, že se vytvořila jeho lokální kopie, se kterou si uvnitř funkce můžeme dělat co chceme. Pomocí tohoto pointeru (kopie) můžeme dokráčet na konec seznamu, aniž bychom tím ovlivnili hodnotu pointeru ve volající funkci. Pokud bychom následující cyklus provedli ve volající funkci, nenávratně bychom ztratili začátek seznamu: while (S!= NULL) // průchod seznamem S = S->dalsi; // posun na další prvek, k předchozímu S už se nedostaneme Řešením by bylo vytvořit pomocný pointer na seznam: Prvek * P = S; a seznam projít tímto pomocným pointerem, původní hodnota S zůstane zachována. Jan Hnilica Počítačové modelování 11 7

Nalezení posledního prvku - funkce dostává pointer na počátek seznamu S, vrací pointer na poslední prvek (tedy na prvek, jehož následník je NULL) - pokud je seznam prázdný, funkce vrací NULL Prvek * NajdiPosledniPrvek(Prvek * S) if (S == NULL) return S; while (S->dalsi!= NULL) S = S->dalsi; return S; Spojení dvou seznamů P a S Prvek *P, *S;... // vytvoření seznamů Prvek * Q = NajdiPosledniPrvek(P); if (Q!= NULL) Q->dalsi = S; // spojení Jan Hnilica Počítačové modelování 11 8

Nalezení prvku obsahujícího daný znak - funkce přebírá pointer S na počátek seznamu a hledaný znak z, vrací pointer na první prvek obsahující hledaný znak - pokud znak v seznamu není nebo pokud je seznam prázdný, funkce vrací NULL Prvek * NajdiPrvek(Prvek * S, char z) if (S == NULL) return S; while (S!= NULL && S->znak!= z) S = S->dalsi; return S; Pro podmínku v cyklu while je nezbytné zkrácené vyhodnocování výrazů, které probíhá v jazyce C. Pokud jsme dokráčeli na konec seznamu a aktuálně S == NULL, vyhodnocování podmínky končí a test S->znak!= z se neprovede (pokud by se provedl, program by zhavaroval). Jan Hnilica Počítačové modelování 11 9

Smazání prvku ze seznamu - chceme ze seznamu smazat prvek, na který ukazuje pointer P a zachovat seznam propojený: Výchozí stav: P S NULL Cílový stav: P S NULL Potřebujeme tedy propojit předchůdce a následníka prvku P. Přitom je potřeba ošetřit možnost, že P ukazuje na první prvek seznamu, v tom případě se jen posune začátek seznamu (a nic se nepropojuje). Jan Hnilica Počítačové modelování 11 10

Smazání prvku ze seznamu - funkce přebírá pointery na začátek seznamu S a na rušený prvek P - předpokládáme, že P v seznamu je (nalezli jsme ho už dříve) Komplikace: pokud budeme rušit první prvek, musíme posunout začátek seznamu. To ale nejde provést, pokud pointer na začátek seznamu předáváme do funkce hodnotou. Řešení 1: ve funkci nebudeme tuto variantu uvažovat, ošetříme ji před jejím voláním: void SmazPrvek(Prvek * S, Prvek * P) while (S->dalsi!= P) // nalezneme předchůdce P S = S->dalsi; S->dalsi = P->dalsi; // propojíme předchůdce a následníka P free((void*)p); // smažeme P Použití funkce v programu: (ne moc hezké...) if (P == S) // mažeme první prvek S = S->dalsi; // posuneme začátek free((void*)p); // smažeme P else SmazPrvek(S, P); Jan Hnilica Počítačové modelování 11 11

Řešení 2: posunutí začátku vyřešíme v samotné funkci (hezčí) => funkce musí mít možnost změnit samotný obsah pointeru na začátek seznamu => funkce přebírá pointer odkazem, tzn přebírá pointer na pointer: **S => dereference uvnitř funkce: *S je pointer na začátek seznamu void SmazPrvek(Prvek ** S, Prvek * P) if (*S == P) // mažeme první prvek *S = (*S)->dalsi; // posuneme začátek seznamu else Prvek * Q = *S; // nalezneme předchůdce P while (Q->dalsi!= P) Q = Q->dalsi; Q->dalsi = P->dalsi; // propojíme předchůdce a následníka P free((void*)p); // smažeme P V programu: SmazPrvek(&S, P); // předáváme adresu pointeru na začátek seznamu Jan Hnilica Počítačové modelování 11 12

Poznámka: při mazání prvku P nemusíme procházet seznam a hledat jeho předchůdce - smažeme následníka P (na toho se dostaneme z P), předtím ale obsah následníka zkopírujeme do prvku P - toto nelze provést pro poslední prvek seznamu (který nemá následníka) if (P->dalsi!= NULL) // pokud P není poslední Prvek * Q = P->dalsi; // přidržíme si následníka *P = *Q; // zkopírování celého obsahu následníka do P free((void*)q); // smazání následníka - funkce využívá toho, že na struktury můžeme aplikovat operátor přiřazení (=), který způsobí kopírování celého obsahu struktury, tedy včetně pointeru na další prvek seznamu! Jan Hnilica Počítačové modelování 11 13

Smazání celého seznamu - je nutné projít prvek po prvku a všechny postupně smazat - pokud bychom pouze nastavili počátek na NULL, došlo by k tzv. úniku paměti (k prvkům seznamu by už nevedla žádná cesta a nešlo by uvolnit alokovanou paměť) void SmazSeznam(Prvek * S) Prvek * P = S; // pomocný pointer na aktuálně mazaný prvek while (S!= NULL) S = S->dalsi; // postup na další prvek free((void*)p); // smazání aktuálního prvku P = S; // posun pomocného pointeru - v programu by bylo vhodné po zrušení seznamu nastavit pointer S na NULL: SmazSeznam(S); S = NULL; Jan Hnilica Počítačové modelování 11 14

Vložení prvku do seznamu - vložení je jednoduché, pokud máme pointer na prvek ZA který vkládáme - funkce přebírá dva pointery: P (nový prvek) a Q (prvek za který vkládáme) void VlozPrvekZa(Prvek * P, Prvek * Q) P->dalsi = Q->dalsi; Q->dalsi = P; pořadí těchto dvou operací nesmíme prohodit! Q Výchozí stav: S NULL Q P Cílový stav: S NULL P Jan Hnilica Počítačové modelování 11 15

Vložení prvku do seznamu - pokud máme pointer Q na prvek PŘED který vkládáme, můžeme: 1) projít seznamem od začátku, najít předchůdce prvku Q a vložit nový prvek za tohoto předchůdce (už umíme) 2) zapojit nový prvek za Q a vzájemně vyměnit data těchto prvků (obdobně jako u mazání) - funkce přebírá dva pointery: P (nový prvek) a Q (prvek před který vkládáme) void VlozPrvekPred(Prvek * P, Prvek * Q) P->dalsi = Q->dalsi; // P zapojíme za Q Q->dalsi = P; char znak = P->znak; // vyměníme znaky P->znak = Q->znak; Q->znak = znak; void VlozPrvekPred(Prvek * P, Prvek * Q) char znak = P->znak; // uložíme si znak z P *P = *Q; // zkopírujeme veškerá data Q do P Q->znak = znak; // do Q uložíme znak z P Q->dalsi = P; // Q zapojíme před P 1. varianta - zapojení prvku a výměna znaků 2. varianta - využijeme přiřazení mezi strukturami Jan Hnilica Počítačové modelování 11 16

Obousměrný lineární seznam - každý prvek obsahuje pointery na předchůdce i následníka typedef struct prvek char znak; // uložená hodnota struct prvek * dalsi; // propojení dozadu struct prvek * predchozi; // propojení dopředu Prvek; S NULL A dalsi predchozi H dalsi predchozi O dalsi predchozi J dalsi predchozi! dalsi predchozi NULL - spotřebuje více paměti na pointery, ale umožňuje procházet seznamem oběma směry - snadnější operace mazání a vkládání prvku nemusíme procházet seznam a hledat předchůdce Jan Hnilica Počítačové modelování 11 17

Použití lineárních seznamů Náhrada pole užitečné např. pokud - načítáme data a nemáme žádnou představu o jejich možném počtu - potřebujeme přidávat a mazat data a udržovat přitom pole setříděné (do LSS snadno přidáme prvky na určené místo (nebo smažeme), aniž bychom museli posouvat ostatní) Nevýhody LSS oproti polím - v LSS nejde přistupovat k prvkům pomocí indexů (je potřeba projít seznam od začátku a prvek vyhledat, tzn. většina operací v LSS má lineární časovou složitost O(n), kde n je počet prvků seznamu) - spotřebujeme určitou paměť na uložení pointerů Jan Hnilica Počítačové modelování 11 18

Použití lineárních seznamů Spravování zásobníku a fronty pomocí LSS LSS představují ideální způsob k naprogramování zásobníku a fronty nejsme omezeni délkou pole (nemusíme ošetřovat případ, kdy už pro přidání prvku není místo) vyhneme se problému posouvání fronty Zásobník pomocí LSS přidávání i odebírání na zásobníku se děje na jeho vrcholu => začátek seznamu bude vrchol, protože na začátek se dobře přidává i odebírá prázdný zásobník je představován hodnotou NULL, test na prázdný zásobník je samozřejmě nutné provádět při operaci odebírání prvku (nebo před ní) vrchol zásobníku Prvek * Z NULL // vytvoření prázdného zásobníku Prvek * Z = NULL; Jan Hnilica Počítačové modelování 11 19

Použití lineárních seznamů Zásobník operace přidání prvku funkce dostává pointer Z na vrchol zásobníku (odkazem, protože ho musí změnit) a hodnotu nového prvku void PridejPrvek(Prvek ** Z, char znak) Prvek * P = (Prvek*) malloc(sizeof(prvek)); // alokace nového prvku P->znak = znak; P->dalsi = *Z; // zapojení nového prvku na vrchol *Z = P; // posunutí vrcholu volání funkce v programu PridejPrvek(&Z, znak); // předáváme adresu pointeru Z Jan Hnilica Počítačové modelování 11 20

Použití lineárních seznamů Zásobník operace odebrání prvku funkce opět mění pointer Z na vrchol zásobníku, takže ho dostává odkazem návratovou hodnotou je znak z vrcholu zásobníku char OdeberPrvek(Prvek ** Z) char znak = (*Z)->znak; // uložíme si odebíraný znak Prvek * P = *Z; // přidržíme si aktuální vrchol *Z = (*Z)->dalsi; // posuneme vrchol free((void*)p); // smažeme starý vrchol return znak; // návrat volání funkce v programu obsahuje test na prázdný zásobník char odebiranyznak; if (Z!= NULL) odebiranyznak = OdeberPrvek(&Z); Pozn. Test by šel zakomponovat i do funkce samotné, která by v takovém případě vracela nějaký dohodnutý chybový znak. Pak by ale musel být testován vrácený znak ve volající funkci. Jinou alternativou jsou tzv. výjimky (později). Jan Hnilica Počítačové modelování 11 21

Použití lineárních seznamů Fronta pomocí LSS do fronty se na jednom konci přidávají prvky a na druhém konci se odebírají na začátku LSS se dobře přidává i odebírá, ale na konci LSS se špatně odebírá (musíme najít předchůdce rušeného prvku a jeho pointer na další prvek nastavit na NULL) => na začátku seznamu budeme odebírat, na konci přidávat => budeme si udržovat dna pointery na odchod i na příchod z fronty // frontu si vytvoříme jako strukturu typedef struct fronta Fronta.odchod Prvek * odchod; Prvek * prichod; Fronta; // vytvoření prázdné fronty Fronta F; F.odchod = F.prichod = NULL; Fronta.prichod NULL Jan Hnilica Počítačové modelování 11 22

Použití lineárních seznamů Fronta operace přidání prvku funkce přebírá pointer na frontu a ukládanou hodnotu void PridejPrvek(Fronta * F, char z) Prvek * P = (Prvek*)malloc(sizeof(Prvek)); // vytvoříme nový prvek P->znak = z; if (F->odchod == NULL) // fronta je prázdná F->prichod = F->odchod = P; else F->prichod->dalsi = P; // zapojíme nový prvek F->prichod = P; // posuneme konec fronty volání v programu PridejPrvek(&F, znak); Jan Hnilica Počítačové modelování 11 23

Použití lineárních seznamů Fronta operace odebrání prvku funkce přebírá pointer na frontu, návratovou hodnotou je znak z počátku fronty char OdeberPrvek(Fronta * F) char z = F->odchod->znak; Prvek * P = F->odchod; if (F->odchod == F->prichod) // po odebrání bude fronta prázdná F->odchod = F->prichod = NULL; else F->odchod = F->odchod->dalsi; free((void*)p); return z; volání funkce v programu char odebiranyznak; if (F.odchod!= NULL) odebiranyznak = OdeberPrvek(&F); Jan Hnilica Počítačové modelování 11 24