Lineárny zoznam: Rozdiel medzi revíziami

Z Kiwiki
Skočit na navigaci Skočit na vyhledávání
 
(6 medziľahlých úprav od 2 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
[[Kategória:Študijné materiály]]
 
[[Kategória:Programovanie]]
 
[[Kategória:Informatika]]
 
 
{{Draft}}
 
{{Draft}}
 
{{Skripta programovanie}}
 
{{Skripta programovanie}}
Riadok 10: Riadok 7:
 
Lineárny zoznam poznáme jednosmerný a obojsmerný. V jednosmernom zozname odkazuje každá položka na nasledujúcu položku a v obojsmernom zozname odkazuje aktuálna položka na nasledujúcu ale aj predchádzajúcu položku. Definuje sa tu smerník alebo odkaz na určitý prvok zoznamu. Na konci (a začiatku) musí byť definovaná zarážka, ktorá označuje koniec zoznamu. Ak vytvoríme taký cyklus, že posledný prvok zoznamu odkazuje na prvý prvok zoznamu, dostaneme kruhový zoznam.
 
Lineárny zoznam poznáme jednosmerný a obojsmerný. V jednosmernom zozname odkazuje každá položka na nasledujúcu položku a v obojsmernom zozname odkazuje aktuálna položka na nasledujúcu ale aj predchádzajúcu položku. Definuje sa tu smerník alebo odkaz na určitý prvok zoznamu. Na konci (a začiatku) musí byť definovaná zarážka, ktorá označuje koniec zoznamu. Ak vytvoríme taký cyklus, že posledný prvok zoznamu odkazuje na prvý prvok zoznamu, dostaneme kruhový zoznam.
 
<gallery widths=250px>
 
<gallery widths=250px>
Súbor:ll1.svg|Jednosmerný lineárny zoznam.Každý prvok zoznamu okrem svojej hodnoty obsahuje i odkaz na nasledujúci prvok v seznamu. Posledný prvok odkazuje „nikam“.
+
Súbor:ll1.svg|Jednosmerný lineárny zoznam.Každý prvok zoznamu okrem svojej hodnoty obsahuje i odkaz na nasledujúci prvok v zozname. Posledný prvok odkazuje „nikam“.
 
Súbor:ll2.svg|Jednosmerný kruhový zoznam. Posledný prvok zoznamu odkazuje opäť na začiatok.
 
Súbor:ll2.svg|Jednosmerný kruhový zoznam. Posledný prvok zoznamu odkazuje opäť na začiatok.
 
Súbor:ll3.svg|Obojsmerný lineárny zoznam. Každý prvok zoznamu obsahuje, okrem svojej hodnoty, odkaz na nasledujúci i predchádzajúci prvok zoznamu
 
Súbor:ll3.svg|Obojsmerný lineárny zoznam. Každý prvok zoznamu obsahuje, okrem svojej hodnoty, odkaz na nasledujúci i predchádzajúci prvok zoznamu
Riadok 89: Riadok 86:
 
**Smerník ''dalsi'' (TPrvok) prvého prvku ukazuje na druhy prvok zoznamu a ''dalsi'' druhého prvku ukazuje na NULL.
 
**Smerník ''dalsi'' (TPrvok) prvého prvku ukazuje na druhy prvok zoznamu a ''dalsi'' druhého prvku ukazuje na NULL.
 
<gallery widths=500px>
 
<gallery widths=500px>
Súbor:lz4.png|Prázdny lineárny zoznam
+
Súbor:lz6.png|Prázdny lineárny zoznam
Súbor:lz5.png|Lineárny zoznam s jedným prvkom
+
Súbor:lz4.png|Lineárny zoznam s jedným prvkom
 
</gallery>
 
</gallery>
 
====Prechod cez lineárny zoznam====
 
====Prechod cez lineárny zoznam====
Riadok 120: Riadok 117:
 
*Na riadku 9 je posunutie sa o jeden prvok ďalej.
 
*Na riadku 9 je posunutie sa o jeden prvok ďalej.
 
*Cyklus ukončíme vtedy ak bude prvok pom zhodný z posledným prvkom zoznamu (smerní na posledný prvok zoznamu obsahuje premenná LinZ.
 
*Cyklus ukončíme vtedy ak bude prvok pom zhodný z posledným prvkom zoznamu (smerní na posledný prvok zoznamu obsahuje premenná LinZ.
 +
 
==Vkladanie záznamov zo lineárneho zoznamu==
 
==Vkladanie záznamov zo lineárneho zoznamu==
 
Prvok môžeme do zoznamu vkladať dvoma spôsobmi:
 
Prvok môžeme do zoznamu vkladať dvoma spôsobmi:
Riadok 136: Riadok 134:
 
Súbor:lz8.png|Do zoznamu sme vložili nový prvok s hodnotou "20". Tento nový prvok bude posledným prvkom v zozname. Pred pridaním mal prvok "-5" smerník ''dalsi'' nastavený na hodnou NULL (pretože bol posledný). Teraz smetník ''dalsi'' prvku "-5" ukazuje na prvok "20", ktorého smerník ''dalsi'' ma hodnotu NULL.
 
Súbor:lz8.png|Do zoznamu sme vložili nový prvok s hodnotou "20". Tento nový prvok bude posledným prvkom v zozname. Pred pridaním mal prvok "-5" smerník ''dalsi'' nastavený na hodnou NULL (pretože bol posledný). Teraz smetník ''dalsi'' prvku "-5" ukazuje na prvok "20", ktorého smerník ''dalsi'' ma hodnotu NULL.
 
</gallery>
 
</gallery>
 +
===Vkladanie nového prvku do usporiadaného zoznamu===
 
Pri vkladaní prvku do usporiadaného lineárneho zoznamu musíme rozlíšiť nasledujúce situácie
 
Pri vkladaní prvku do usporiadaného lineárneho zoznamu musíme rozlíšiť nasledujúce situácie
 
#Zoznam je prázdny
 
#Zoznam je prázdny
Riadok 148: Riadok 147:
 
[[Súbor:lz9.png|framed|center|Nerázdny usporiadaný lineárny zoznam]]
 
[[Súbor:lz9.png|framed|center|Nerázdny usporiadaný lineárny zoznam]]
  
Do zoznamu vkladáme nová prvok s hodnotou "-5". Zistili sme že tento prvok je menší ako prvý prvok, preto ho umiestnime na začiatok zoznamu. Po vložení to bude nový prvý prvok zoznamu.
+
Do zoznamu vkladáme nový prvok s hodnotou "-5". Zistili sme že tento prvok je menší ako prvý prvok, preto ho umiestnime na začiatok zoznamu. Po vložení to bude nový prvý prvok zoznamu.
  
 
[[Súbor:lz10.png|framed|center|Vloženie nového prvku na začiatok zoznamu]]
 
[[Súbor:lz10.png|framed|center|Vloženie nového prvku na začiatok zoznamu]]
  
Do zoznamu vkladáme nová prvok s hodnotou "20". Zistili sme že tento prvok je väčší ako posledný prvok, preto ho umiestnime na koniec zoznamu. Po vložení to bude nový posledný prvok zoznamu.
+
Do zoznamu vkladáme nový prvok s hodnotou "20". Zistili sme že tento prvok je väčší ako posledný prvok, preto ho umiestnime na koniec zoznamu. Po vložení to bude nový posledný prvok zoznamu.
  
 
[[Súbor:lz11.png|framed|center|Vloženie nového prvku na koniec zoznamu]]
 
[[Súbor:lz11.png|framed|center|Vloženie nového prvku na koniec zoznamu]]
Riadok 163: Riadok 162:
 
[[Súbor:lz12.png|framed|center|]]
 
[[Súbor:lz12.png|framed|center|]]
  
 
<source lang="c">
 
</source>
 
 
==Implementácia lineárneho zoznamu v jazyku C==
 
==Implementácia lineárneho zoznamu v jazyku C==
 +
===Dátová štruktúra opisujúca lineárny zoznam===
 
<source lang="c">
 
<source lang="c">
 
struct TPrvok  
 
struct TPrvok  
Riadok 179: Riadok 176:
 
} ;
 
} ;
 
</source>
 
</source>
 +
===Vloženie prvku na koniec zoznamu===
 +
Vo funkcii Vloz_k budeme do zoznamu vkladať nový prvok na koniec zoznamu.
 +
 +
Parametre funkcie:
 +
*TZoznam &z - odkaz na lineárny zoznam, do ktorého vkladáme nový údaj,
 +
* int x - hodnota, ktorú budeme do zoznamu ''z'' vkladať.
 +
 +
Návratová hodnota: žiadna.
 +
 +
Do zoznamu budeme vkladať nový prvok, ktorého hodnota je definované vo vstupnom parametri x. Keďže sa zoznam skladá iba zo smerníkov na štruktúru TPrvok, je potrebné si vytvoriť (a alokovať miesto) smerník (riadok 3) na štruktúru TPrvok. Do tejto štruktúry je potom treba nastaviť hodnotu x (riadok 4). Premenná ''novy'' reprezentuje prvok, ktorý budeme do zoznamu vkladať.
 +
Ak je zoznam ''z'' prázdny, tak novo vložený prvok bude zároveň prvým aj posledným prvkom zoznamu (riadok 8 a 11). Ak v zozname ''z'' existuje nejaký prvok, tak teba nový prvok vložiť na koniec zoznamu. Poradie operácií, ktoré treba urobiť je:
 +
#Hodnota smerníku ''dalsi'' posledného prvku zoznamu je NULL. Po pridaní nového prvku bude mať tento smerník <nowiki>z.posledny->dalsi</nowiki> hodnotu adresy nového prvku (riadok 10)
 +
#Po pridaní nového prvku na koniec treba upraviť smerník ''posledny'' (''z.posledny'') zoznamu z: posledný prvok je v skutočnosti novo vložený prvok (riadok 11).
 +
<source lang="c" line>
 +
void Vloz_k(TZoznam &z, int x)  // vlozi prvok na koniec
 +
{ // vlozime novy prvok, ktory ma hodnotu x
 +
  TPrvok *novy = new TPrvok;
 +
  novy->data = x;
 +
  novy->dalsi = NULL;
 +
 +
  if (z.prvy == NULL)  // prazdny zoznam
 +
      z.prvy = novy;
 +
  else
 +
      z.posledny->dalsi = novy;
 +
  z.posledny = novy;
 +
}
 +
</source>
 +
===Vloženie prvku na začiatok zoznamu===
 +
Vo funkcii Vloz_z budeme do zoznamu vkladať nový prvok na začiatok zoznamu.
 +
 +
Parametre funkcie:
 +
*TZoznam &z - odkaz na lineárny zoznam, do ktorého vkladáme nový údaj,
 +
* int x - hodnota, ktorú budeme do zoznamu ''z'' vkladať.
 +
 +
Návratová hodnota: žiadna.
 +
 +
Význam riadkov 3 až 5 je rovnaký ako v predchádzajúcej funkcii.
 +
Ak je zoznam ''z'' prázdny, tak novo vložený prvok bude zároveň prvým aj posledným prvkom zoznamu (riadok 8 a 11). Ak v zozname ''z'' existuje nejaký prvok, tak teba nový prvok vložiť na začiatok zoznamu. Poradie operácií, ktoré treba urobiť je:
 +
#Hodnota smerníku ''dalsi'' vkladaného prvku zoznamu je NULL (riadok 5). Po pridaní nového prvku bude hodnota jeho smerníka ''dalsi'' obsahovať adresu prvku, ktorý bol pred vložením prvý (riadok 10)
 +
#Po pridaní nového prvku na začiatok treba upraviť smerník ''prvy'' (''z.prvy'') zoznamu z: prvý prvok je v skutočnosti novo vložený prvok (riadok 11).
 +
<source lang="c" line>
 +
void Vloz_z(TZoznam &z, int x) 
 +
{ // vlozime novy prvok, ktory ma hodnotu x
 +
  TPrvok *novy = new TPrvok;
 +
  novy->data = x;
 +
  novy->dalsi = NULL;
 +
 +
  if (z.prvy == NULL)  // prazdny zoznam
 +
    z.posledny = novy;
 +
  else
 +
    novy->dalsi=z.prvy
 +
  z.prvy = novy;
 +
}
 +
</source>
 +
===Vloženie prvku do usporiadaného zoznamu===
 +
Vo funkcii Vloz budeme do zoznamu vkladať nový prvok do zoznamu na také miesto, aby bol zoznam stále usporiadaný.
 +
 +
Parametre funkcie:
 +
*TZoznam &z - odkaz na lineárny zoznam, do ktorého vkladáme nový údaj,
 +
* int x - hodnota, ktorú budeme do zoznamu ''z'' vkladať.
 +
 +
Návratová hodnota: žiadna.
 +
 +
Princíp vkladania vkladania nového prvku je opísaný v kapitole [[#Vkladanie nového prvku do usporiadaného zoznamu]]. V nasledujúcom texte bude vysvetlená len implementácia v jazyku C.
 +
*riadok 7-8 : vkladanie do prázdneho zoznamu
 +
*riadok 9-30 : vkladanie do neprázdneho zoznamu
 +
**riadok 10: hodnota x <nowiki><</nowiki> hodnota prvého prvku zoznamu. Nový prvok bude vložený na začiatok zoznamu.
 +
**riadok 16-20: hodnota x <nowiki>></nowiki> hodnota posledného prvku zoznamu. Nový prvok bude vložený na koniec zoznamu.
 +
**riadok 21-30: hodnota x menšia hodnota prvého prvku a zároveň menšia ako hodnota posledného prvku
 +
***riadok 23: 2 pomocné smerníky p1 a p2. p1 ukazuje na prvý prvok (''p1=z.prvy''), p2 ukazuje na druhý prvok zoznamu (''p2=z.prvy-<nowiki>></nowiki>dalsi'')
 +
***riadok 24: v cykle while hľadáme také miesto kde neplatí, že hodnota x je väčšia ako hodnota prvku p2. V prípade, ak platí že <nowiki>x>p2->data</nowiki>, tak smerníky p1 a p2 posunieme o jedno miesto z zozname ďalej (riadok 25 a 26).
 +
*** Po skončení cyklu while platí že hodnota prvku x je menšia ako hodnota dátovej časti prvku p2. Našli sme teda miesto, kde budeme prvok ''novy'' vkladať. Nový prvok vložíme medzi prvky p1 a p2.
 +
*** riadok 28-29: vloženie nového prvku medzi prvky p1 a p2.
 +
<source lang="c" line>
 +
void Vloz(TZoznam &z, int x)  // vlozi prvok zotriedene
 +
{
 +
  TPrvok *novy = new TPrvok;
 +
  novy->data = x;
 +
  novy->dalsi = NULL;
 +
 +
  if (z.prvy == NULL)  // prazdny zoznam
 +
      z.prvy = z.posledny = novy;
 +
  else // v zozname je nejaky prvok
 +
      if (x < z.prvy->data)  // ak je x < ako hodnota prveho  
 +
      { // prvku vlozim novy prvok na zaciatok
 +
        novy->dalsi = z.prvy;
 +
        z.prvy = novy;
 +
      }
 +
      else
 +
        if (x > z.posledny->data)  // x > ako posledny prvok zoznamu
 +
        {
 +
            z.posledny->dalsi = novy;
 +
            z.posledny = novy;
 +
        }
 +
        else  // vlozenie niekde do stredu zoznamu
 +
        {
 +
            TPrvok *p1 = z.prvy, *p2 = z.prvy->dalsi;
 +
            while (p2->data < x) // hladame miesto, kde vlozime prvok
 +
            {  p1 = p2;
 +
                p2 = p2->dalsi; // posun o jeden prvok
 +
            }
 +
            p1->dalsi = novy; // vlozenie noveho zaznamu    
 +
            novy->dalsi = p2; // na spravne miesto
 +
        }
 +
}
 +
</source>
 +
===Vyhľadanie prvku v lineárnom zozname===
 +
Vo funkcii ''hladaj'' budeme v zozname hľadať prvok s danou hodnotou.
 +
 +
Parametre funkcie:
 +
* TZoznam z - lineárny zoznam, v ktorom budeme vyhľadávať
 +
* int x - hodnota, ktorú budeme v zozname ''z'' hľadať.
 +
 +
Návratová hodnota: V prípade úspechu smerník na nájdený prvok. V prípade neúspechu NULL.
 +
 +
Princíp funkcie o opísaný v kapitole [[#Prechod cez lineárny zoznam|Prechod cez lineárny zoznam]].
 
<source lang="c">
 
<source lang="c">
 +
TPrvok* hladaj(TZoznam z, int x)
 +
{
 +
  TPrvok *p = z.prvy;  // pomocny prvok typu TPrvok
 +
  while (p != NULL) // opakuj, pokial neprides na koniec zoznamu
 +
  {
 +
    if (p->data == x) return p;   // ak sa prvok nasiel
 +
    p = p->dalsi; // chod na dalsi prvok zoznamu
 +
  }
 +
  return NULL; // ak si nic nenasiel, vrat 0
 +
}
 
</source>
 
</source>
 +
===Výpis prvkov lineárneho zoznamu===
 +
Vo funkcii ''vypis'' budeme vypisovať postupne všetky prvku lineárneho zoznamu.
  
 +
Parametre funkcie:
 +
* TZoznam z - lineárny zoznam, ktorého prvky budeme vypisovať
 +
 +
Návratová hodnota: žiadna
 +
 +
Princíp funkcie o opísaný v kapitole [[#Prechod cez lineárny zoznam|Prechod cez lineárny zoznam]].
 
<source lang="c">
 
<source lang="c">
 +
int JePrazdny(TZoznam z)
 +
{ // ak je zoznam prazdny, funkcia vrati 1, inak 0
 +
  return z.prvy == NULL;
 +
}
 +
void vypis(TZoznam z)
 +
{
 +
  if (JePrazdny(z)) return;  // ak je zoznam prazdny, skonci
 +
  TPrvok *p = z.prvy;  // pomocny prvok typu TPrvok
 +
  while (p->dalsi != NULL) // prechádzaj cez vsetky prvky
 +
  {
 +
    cout << p->data << ", ";  // vypis datovu cast prvku
 +
    p = p->dalsi; // chod na dalsi prvok
 +
  }
 +
  cout << p->data << endl; // vypis posledny prvok
 +
}
 
</source>
 
</source>
 +
===Zmazanie prvého prvku zoznamu===
 +
Vo funkcii ''ZmazPrvy'' budeme mazať prvý prvok zoznamu. Po zmazaní musia mať smerníky ''prvy'' a ''posledny'' zoznamu ''z'' aktuálne hodnoty.
  
<source lang="c">
+
Parametre funkcie:
 +
* TZoznam &z - odkaz na lineárny zoznam, v ktorom budeme mazať prvý prvok.
 +
 
 +
Návratová hodnota: žiadna
 +
 
 +
Na riadku 4 je vytvorený pomocný smerník, ktorý bude mať hodnotu prvého prvku zoznamu. Ako prvé je potrebné nastaviť smerník ''prvy'' zoznamu ''z'' (''z.prvy'') na prvok, ktorý bude po zmazaní prvého prvku zoznamu nový prvý prvok. Toto je vlastne druhý prvok zoznamu (''<nowiki>p->dalsi</nowiki>''). Na koniec (riadok 6) treba zmazať prvý prvok.
 +
<source lang="c" line>
 +
void ZmazPrvy(TZoznam &z)
 +
{  if (z.prvy == NULL)  // prazdny zoznam
 +
    return;
 +
  TPrvok *p=z.prvy;
 +
  z.prvy=p->dalsi;
 +
  delete p;
 +
}
 +
</source>
 +
 
 +
===Zmazanie posledného prvku zoznamu===
 +
Vo funkcii ''ZmazPosledny'' budeme mazať posledný prvok zoznamu. Po zmazaní musia mať smerníky ''prvy'' a ''posledny'' zoznamu ''z'' aktuálne hodnoty.
 +
 
 +
Parametre funkcie:
 +
* TZoznam &z - odkaz na lineárny zoznam, v ktorom budeme mazať prvý prvok.
 +
 
 +
Návratová hodnota: žiadna
 +
 
 +
*riadok 2: zoznam je prázdny. Funkcia skončí
 +
*riadok 5: v zozname je len jeden prvok. Po jeho zmazaní bude zoznam prázdny.
 +
*riadok 11: v zozname je viac ako 1 prvok. Po zmazaní posledného prvku bude nový posledný prvok ten, ktorý bol v pôvodnom zozname predposledný.
 +
** Hľadanie predposledného prvku: riadok 11 a 12. Predposledný prvok je taký prvok p, pre ktorý platí:
 +
***''<nowiki>p->dalsi</nowiki>'' je posledný prvok zoznamu. Vieme, že posledný prvok zoznamu má hodnotu smerníka dalsi NULL. Preto ak platí výraz <nowiki>p->dalsi->dalsi == NULL</nowiki>, tak potom je prvok p predposledný.
 +
***Ak sme našli predposledný prvok, tak môžeme zmazať posledný prvok (riadok 13)
 +
***Prvok ''p'' sa stane posledným prvkom. Posledný prvok má hodnotu smerníka ''dalsi'' NULL (riadok 14)
 +
***Prvok ''p'' nastavíme ako posledný prvok zoznamu ''z'' (riadok 15).
 +
<source lang="c" line>
 +
void ZmazPosledny(TZoznam &z)
 +
{  if (z.prvy == NULL)  // prazdny zoznam
 +
    return;
 +
  TPrvok *p = z.prvy;
 +
  if (p->dalsi == NULL)  // je len jeden prvok
 +
  { delete p;
 +
    z.prvy = z.posledny= NULL;  // upravime strukturu z
 +
    return;
 +
  }
 +
  // najdeme predposledny prvok
 +
  while (p->dalsi->dalsi != NULL)
 +
    p = p->dalsi;
 +
  delete p->dalsi; // zmazeme posledny
 +
  p->dalsi = NULL;  // a nastavime NULL na aktualny posledny prvok
 +
  z.posledny = p;  //upravime zoznam tak, aby ukazoval spravne na
 +
}   // posledny prvok
 
</source>
 
</source>
 +
===Zmazanie lineárneho zoznamu===
 +
Vo funkcii ''Zmaz'' budeme mazať všetky prvky zoznamu.
 +
 +
Parametre funkcie:
 +
* TZoznam &z - odkaz na lineárny zoznam, ktorého prvky budeme mazať.
  
 +
Návratová hodnota: žiadna
 +
 +
Je jedno, či budeme mazať zoznam od začiatku, alebo od konca. Implementácia mazania Zmaz2 je ale efektívnejšia pretože zložitosť funkcie ''ZmazPrvy'' je O(1) a zložitosť funkcie ''ZmazPosledny'' je O(n).
 
<source lang="c">
 
<source lang="c">
 +
void Zmaz(TZoznam &z)
 +
{
 +
  while (!JePrazdny(z)) // pokial je v zozname nejaky prvok
 +
    ZmazPosledny(z); // zmaze posledny prvok
 +
}
 +
 +
void Zmaz2(TZoznam &z)
 +
{
 +
  while (!JePrazdny(z)) // pokial je v zozname nejaky prvok
 +
    ZmazPrvy(z); // zmaze prvy prvok
 +
}
 
</source>
 
</source>
 
+
===Použitie lineárneho zoznamu v programe===
 
<source lang="c">
 
<source lang="c">
 +
void main()
 +
{ TZoznam zoznam;
 +
  zoznam.prvy =NULL;
 +
  zoznam.posledny = NULL;
 +
  int udaj,n=4; //pocet zaznamov
 +
  for(int i=0;i<n;i++)
 +
  {  cin>>udaj;
 +
Vloz_z(zoznam, udaj);
 +
  }
 +
  for(int i=0;i<n;i++)
 +
  {  cin>>udaj;
 +
Vloz_k(zoznam, udaj);
 +
  }
 +
  vypis(zoznam);
 +
  Zmaz(zoznam);
 +
 
 +
  for(int i=0;i<n;i++)
 +
  {  cin>>udaj;
 +
Vloz(zoznam, udaj);
 +
  }
 +
  vypis(zoznam);
 +
  Zmaz(zoznam);
 
</source>
 
</source>
 +
 
==Odkazy==
 
==Odkazy==
 
<references/>
 
<references/>

Aktuálna revízia z 22:18, 16. august 2010

Imbox draft.png
Toto je projekt, na ktorom sa ešte stále pracuje!!

Aj keď sú v tomto dokumente použiteľné informácie, ešte nie je dokončený. Svoje návrhy môžete vyjadriť v diskusii o tejto stránke.

Practice.png
Praktické príklady

Riešené príklady k tejto téme sú na stránke Lineárny zoznam (riešené príklady)

Lineárny zoznam [1](lineárny spojový zoznam) je dynamická dátová štruktúra, navonok podobná poľu (umožňuje uložiť viacero hodnôt, ale iným spôsobom), obsahujúca jednu alebo viacero dátových položiek (štruktúr) rovnakého typu, ktoré sú navzájom lineárne previazané vzájomnými odkazmi pomocou smerníkov alebo referencií. Aby bol zoznam lineárny, nesmú v ňom existovať cykly so vzájomnými odkazmi.

Lineárny zoznam poznáme jednosmerný a obojsmerný. V jednosmernom zozname odkazuje každá položka na nasledujúcu položku a v obojsmernom zozname odkazuje aktuálna položka na nasledujúcu ale aj predchádzajúcu položku. Definuje sa tu smerník alebo odkaz na určitý prvok zoznamu. Na konci (a začiatku) musí byť definovaná zarážka, ktorá označuje koniec zoznamu. Ak vytvoríme taký cyklus, že posledný prvok zoznamu odkazuje na prvý prvok zoznamu, dostaneme kruhový zoznam.

V nasledujúcom texte bude opisovaný jednosmerný lineárny zoznam.

Vlastnosti lineárneho zoznamu

  • Jednosmerný lineárny zoznam je pamäťovo nenáročná dátová štruktúra
  • Dovoľuje zoskupiť ľubovoľný počet prvkov. Počet prvkov je obmedzený len dostupnou pamäťovou kapacitou počítača.
  • Dokáže jednoducho meniť svoju veľkosť počas behu programu.
  • Prvky zoznamu, na rozdiel od polí neležia vedľa seba, ale v pamäti sú alokované podľa toho, kde je dostatok miesta.

Nasledujúci obrázok znázorňuje spôsob uloženia prvkov v pamäti pri použití poľa a lineárneho zoznamu.

Rozdiel medzi vnútornou štruktúrou poľa a lineárneho zoznamu

Štruktúra lineárneho zozamu

Lineárny zoznam sa skladá z určitých prvkov (alebo uzlov), ktoré sú navzájom previazané vzájomnými odkazmi. Inak povedané stavebný prvok lineárneho zoznamuje samotnú hodnotu prvku (číslo, reťazec, štruktúra, ...) a odkaz na ďalší prvok. Ešte pripomeňme, že všetky prvky lineárneho zoznamu sú rovnakého typu.

Definujme štruktúru TPrvok (záznam pracovnej povahy), ktorý bude obsahovať:

  • dátovú časť (ľubovolná položka, v našom prípade celé číslo)
  • ukazovateľ na na ďalší záznam typu TPrvok

Definícia v jazyku C:

struct TPrvok 
   {	
      int data;	
      TPrvok *dalsi; 
   };

Vysvetlenie: Štruktúra TPrvok obsahuje dátovú časť označenú ako data. Odkaz na ďalší prvok je označený ako dalsi, a je to smetník na štruktúru TPrvok.

V nasledujúcom príklade budú ukážky definície prvkov lineárneho zoznamu, ktorý namiesto hodnoty celého čísla obsahuje inú dátovú časť.

 1 struct TPrvok 
 2    {	
 3       char retazec[64];	
 4       TPrvok *dalsi; 
 5    };
 6 //------------------------------------
 7 struct TPrvok 
 8    {	
 9       TZlomok data;	
10       TPrvok *dalsi; 
11    };
12 //------------------------------------
13 struct TPrvok 
14    {	
15       double data[10];	
16       TPrvok *dalsi; 
17    };
  • riadok 1: prvok lineárneho zoznamu je reťazec o dĺžke 63 znakov
  • riadok 7: prvok lineárneho zoznamu je zlomok
  • riadok 13: prvok lineárneho zoznamu je pole reálnych čísel o veľkosti 10.
Vizualizácia dátovej štruktúry TPrvok

Pre uloženie informácie o tom, kde má zoznam začiatok a kde končí si definujme ďalšiu štruktúru, ktorú nazveme TZoznam, ktorá bude obsahovať 2 smerníky na štruktúru TPrvok. Jeden smerník ukazuje na začiatok zoznamu a druhý smerník ukazuje na koniec zoznamu.

struct TZoznam { 
   TPrvok *prvy;
   TPrvok *posledny;
} ;
Vizualizácia dátovej štruktúry TZoznam

Práca s lineárnym zoznamom

Pre lineárny zoznam platí nasledovné:

  • Prvky prvy, posledny, dalsi sú ukazovatele na TPrvok.
  • Časť štruktúry TPrvok označené ako data môže byť ľubovoľný dátový typ: reťazec, štruktúra, číslo...
  • Ak je zoznam prázdny informačná štruktúra TZoznam má dva ukazovatele a oba majú hodnotu NULL. Teda ukazujú "nikam".
  • Ak je v zozname jeden prvok tak ukazovatele prvy a posledny (TZoznam) ukazujú ne tento prvok a dalsi (TPrvok) má hodnotu NULL.
  • Ak je v zozname dva a viac prvkov tak
    • smerník prvy (TZoznam) ukazuje na prvý prvok v lineárnom zozname,
    • smerník posledny (TZoznam) ukazuje na posledný prvok v lineárnom zozname,
    • Smerník dalsi (TPrvok) prvého prvku ukazuje na druhy prvok zoznamu a dalsi druhého prvku ukazuje na NULL.

Prechod cez lineárny zoznam

Uvedieme psseudokód, ktorý ukazuje princíp prechodu cez všetky prvky lineárneho zoznamu.

 1 TZoznam LinZ;
 2 // naplnenie zoznamu datami
 3 begin
 4 TPrvok *pom;
 5 pom=LinZ.prvy;
 6 rob
 7    begin
 8       vypis pom->data
 9       pom=pom->dalsi;
10       pokial( pom != LinZ.posledny )
11    end
12 end

Vysvetlenie uvedeného pseudokódu:

  • Na riadku 1 si vytvoríme premennú typu TZoznam (Lineárny zoznam, ktorý udržuje informáciu o prvom a posledom prvku zoznamu).
  • Naplnenie zoznamu je symbolicky naznačené na riadku 2.
  • Zoznam pozostáva zo štruktúr typu smerník na TPrvok. Preto si na riadku 4 vytvárame premennú pom typu smerník na TPrvok.
    • Pre túto premennú netreba alokovať miesto v pamäti, pretože pomocou tejto premennej budeme prechádzať po existujúcich prvkoch zoznamu.
    • Na začiatok nastavíme premennú pom na prvý prvok zoznamu.
  • V cykle (riadok 6) vypíšeme obsah prvku (riadok 8).
  • Na riadku 9 je posunutie sa o jeden prvok ďalej.
  • Cyklus ukončíme vtedy ak bude prvok pom zhodný z posledným prvkom zoznamu (smerní na posledný prvok zoznamu obsahuje premenná LinZ.

Vkladanie záznamov zo lineárneho zoznamu

Prvok môžeme do zoznamu vkladať dvoma spôsobmi:

  1. Vloženie prvku na koniec (na začiatok). Vytváraný zoznam bude neusporiadaný.
  2. Vloženie prvku na správne miesto. V tomto prípade sa snažíme aby bol zoznam stále usporiadaný od najmenšieho (najväčšieho) prvku po najväčší (najmenší).

Vkladanie nového prvku na koniec zoznamu

Pri vkladaní prvku do lineárneho zoznamu musíme rozlíšiť 2 rôzne situácie

  1. Zoznam je prázdny
    • Vložený prvok bude jediným prvkom zoznamu. Bude prvým a zároveň posledným prvkom zoznamu
  2. Zoznam nie je prázdny.
    • Vložený prvok umiestnime na koniec zoznamu. Tento nový prvok bude zároveň posledným prvkom zoznamu.

Na nasledujúcich obrázkoch bude znázornená situácia vloženia nového prvku do prázdneho a neprázdneho zoznamu na koniec.

Vkladanie nového prvku do usporiadaného zoznamu

Pri vkladaní prvku do usporiadaného lineárneho zoznamu musíme rozlíšiť nasledujúce situácie

  1. Zoznam je prázdny
    • Vložený prvok bude jediným prvkom zoznamu. Bude prvým a zároveň posledným prvkom zoznamu. Toto riešenie je rovnaké ako v predchádzajúcom príklade.
  2. Zoznam nie je prázdny.
    1. Nový prvok je menší ako prvý prvok.
    2. Nový prvok je väčší ako posledný prvok.
    3. Nový prvok treba umiestniť na správne miesto v zozname.

Na nasledujúcich obrázkoch bude znázornené jednotlivé prípady. Budeme vychádzať z neprázdneho zoznamu z nasledujúceho obrázka.

Nerázdny usporiadaný lineárny zoznam

Do zoznamu vkladáme nový prvok s hodnotou "-5". Zistili sme že tento prvok je menší ako prvý prvok, preto ho umiestnime na začiatok zoznamu. Po vložení to bude nový prvý prvok zoznamu.

Vloženie nového prvku na začiatok zoznamu

Do zoznamu vkladáme nový prvok s hodnotou "20". Zistili sme že tento prvok je väčší ako posledný prvok, preto ho umiestnime na koniec zoznamu. Po vložení to bude nový posledný prvok zoznamu.

Vloženie nového prvku na koniec zoznamu

Do zoznamu vkladáme nová prvok s hodnotou "5". Zistili sme že tento prvok treba umiestniť na správne miesto v zozname. Po nájdení správneho miesta (medzi prvky 0 a 10) ho medzi tieto prvky vložíme. Prvky medzi ktoré budeme nový prvok vkladať si označme ako lavy a pravy. Postupnosť operácií, ktoré musíme urobiť je na nasledujúcom obrázku:

  1. Smerník dalsi nového prvku nastavíme na prvok pravy
  2. Zmažeme smerník dalsi, ktorý ukazuje z prvku lavy na pravy.
  3. Smerník dalsi prvku lavy nastavíme tak, aby ukazoval na nový prvok.
Lz12.png

Implementácia lineárneho zoznamu v jazyku C

Dátová štruktúra opisujúca lineárny zoznam

struct TPrvok 
   {	
      int data;	
      TPrvok *dalsi; 
   };

struct TZoznam { 
   TPrvok *prvy;
   TPrvok *posledny;
} ;

Vloženie prvku na koniec zoznamu

Vo funkcii Vloz_k budeme do zoznamu vkladať nový prvok na koniec zoznamu.

Parametre funkcie:

  • TZoznam &z - odkaz na lineárny zoznam, do ktorého vkladáme nový údaj,
  • int x - hodnota, ktorú budeme do zoznamu z vkladať.

Návratová hodnota: žiadna.

Do zoznamu budeme vkladať nový prvok, ktorého hodnota je definované vo vstupnom parametri x. Keďže sa zoznam skladá iba zo smerníkov na štruktúru TPrvok, je potrebné si vytvoriť (a alokovať miesto) smerník (riadok 3) na štruktúru TPrvok. Do tejto štruktúry je potom treba nastaviť hodnotu x (riadok 4). Premenná novy reprezentuje prvok, ktorý budeme do zoznamu vkladať. Ak je zoznam z prázdny, tak novo vložený prvok bude zároveň prvým aj posledným prvkom zoznamu (riadok 8 a 11). Ak v zozname z existuje nejaký prvok, tak teba nový prvok vložiť na koniec zoznamu. Poradie operácií, ktoré treba urobiť je:

  1. Hodnota smerníku dalsi posledného prvku zoznamu je NULL. Po pridaní nového prvku bude mať tento smerník z.posledny->dalsi hodnotu adresy nového prvku (riadok 10)
  2. Po pridaní nového prvku na koniec treba upraviť smerník posledny (z.posledny) zoznamu z: posledný prvok je v skutočnosti novo vložený prvok (riadok 11).
 1 void Vloz_k(TZoznam &z, int x)  // vlozi prvok na koniec
 2 { // vlozime novy prvok, ktory ma hodnotu x
 3   TPrvok *novy = new TPrvok;
 4   novy->data = x;
 5   novy->dalsi = NULL;
 6 
 7   if (z.prvy == NULL)  // prazdny zoznam
 8        z.prvy = novy;
 9   else
10        z.posledny->dalsi = novy;
11   z.posledny = novy;
12 }

Vloženie prvku na začiatok zoznamu

Vo funkcii Vloz_z budeme do zoznamu vkladať nový prvok na začiatok zoznamu.

Parametre funkcie:

  • TZoznam &z - odkaz na lineárny zoznam, do ktorého vkladáme nový údaj,
  • int x - hodnota, ktorú budeme do zoznamu z vkladať.

Návratová hodnota: žiadna.

Význam riadkov 3 až 5 je rovnaký ako v predchádzajúcej funkcii. Ak je zoznam z prázdny, tak novo vložený prvok bude zároveň prvým aj posledným prvkom zoznamu (riadok 8 a 11). Ak v zozname z existuje nejaký prvok, tak teba nový prvok vložiť na začiatok zoznamu. Poradie operácií, ktoré treba urobiť je:

  1. Hodnota smerníku dalsi vkladaného prvku zoznamu je NULL (riadok 5). Po pridaní nového prvku bude hodnota jeho smerníka dalsi obsahovať adresu prvku, ktorý bol pred vložením prvý (riadok 10)
  2. Po pridaní nového prvku na začiatok treba upraviť smerník prvy (z.prvy) zoznamu z: prvý prvok je v skutočnosti novo vložený prvok (riadok 11).
 1 void Vloz_z(TZoznam &z, int x)  
 2 { // vlozime novy prvok, ktory ma hodnotu x
 3   TPrvok *novy = new TPrvok;
 4   novy->data = x;
 5   novy->dalsi = NULL;
 6 
 7   if (z.prvy == NULL)  // prazdny zoznam
 8      z.posledny = novy;
 9   else
10      novy->dalsi=z.prvy
11   z.prvy = novy;
12 }

Vloženie prvku do usporiadaného zoznamu

Vo funkcii Vloz budeme do zoznamu vkladať nový prvok do zoznamu na také miesto, aby bol zoznam stále usporiadaný.

Parametre funkcie:

  • TZoznam &z - odkaz na lineárny zoznam, do ktorého vkladáme nový údaj,
  • int x - hodnota, ktorú budeme do zoznamu z vkladať.

Návratová hodnota: žiadna.

Princíp vkladania vkladania nového prvku je opísaný v kapitole #Vkladanie nového prvku do usporiadaného zoznamu. V nasledujúcom texte bude vysvetlená len implementácia v jazyku C.

  • riadok 7-8 : vkladanie do prázdneho zoznamu
  • riadok 9-30 : vkladanie do neprázdneho zoznamu
    • riadok 10: hodnota x < hodnota prvého prvku zoznamu. Nový prvok bude vložený na začiatok zoznamu.
    • riadok 16-20: hodnota x > hodnota posledného prvku zoznamu. Nový prvok bude vložený na koniec zoznamu.
    • riadok 21-30: hodnota x menšia hodnota prvého prvku a zároveň menšia ako hodnota posledného prvku
      • riadok 23: 2 pomocné smerníky p1 a p2. p1 ukazuje na prvý prvok (p1=z.prvy), p2 ukazuje na druhý prvok zoznamu (p2=z.prvy->dalsi)
      • riadok 24: v cykle while hľadáme také miesto kde neplatí, že hodnota x je väčšia ako hodnota prvku p2. V prípade, ak platí že x>p2->data, tak smerníky p1 a p2 posunieme o jedno miesto z zozname ďalej (riadok 25 a 26).
      • Po skončení cyklu while platí že hodnota prvku x je menšia ako hodnota dátovej časti prvku p2. Našli sme teda miesto, kde budeme prvok novy vkladať. Nový prvok vložíme medzi prvky p1 a p2.
      • riadok 28-29: vloženie nového prvku medzi prvky p1 a p2.
 1 void Vloz(TZoznam &z, int x)  // vlozi prvok zotriedene
 2 {
 3    TPrvok *novy = new TPrvok;
 4    novy->data = x;
 5    novy->dalsi = NULL;
 6 
 7    if (z.prvy == NULL)  // prazdny zoznam
 8       z.prvy = z.posledny = novy;
 9    else 	// v zozname je nejaky prvok
10       if (x < z.prvy->data)  // ak je x < ako hodnota prveho 	  
11       {		// prvku vlozim novy prvok na zaciatok
12          novy->dalsi = z.prvy;
13          z.prvy = novy;
14       }
15       else 
16          if (x > z.posledny->data)  // x > ako posledny prvok zoznamu
17          {
18             z.posledny->dalsi = novy;
19             z.posledny = novy;
20          }
21          else  // vlozenie niekde do stredu zoznamu
22          {
23             TPrvok *p1 = z.prvy, *p2 = z.prvy->dalsi;
24             while (p2->data < x) // hladame miesto, kde vlozime prvok
25             {   p1 = p2; 
26                 p2 = p2->dalsi; // posun o jeden prvok 
27             }
28             p1->dalsi = novy; // vlozenie noveho zaznamu 		    
29             novy->dalsi = p2; // na spravne miesto
30          }
31 }

Vyhľadanie prvku v lineárnom zozname

Vo funkcii hladaj budeme v zozname hľadať prvok s danou hodnotou.

Parametre funkcie:

  • TZoznam z - lineárny zoznam, v ktorom budeme vyhľadávať
  • int x - hodnota, ktorú budeme v zozname z hľadať.

Návratová hodnota: V prípade úspechu smerník na nájdený prvok. V prípade neúspechu NULL.

Princíp funkcie o opísaný v kapitole Prechod cez lineárny zoznam.

TPrvok* hladaj(TZoznam z, int x)
{
  TPrvok *p = z.prvy;  // pomocny prvok typu TPrvok
  while (p != NULL) // opakuj, pokial neprides na koniec zoznamu
  {
    if (p->data == x) return p;	  // ak sa prvok nasiel
    p = p->dalsi;	// chod na dalsi prvok zoznamu
  }
  return NULL; // ak si nic nenasiel, vrat 0
}

Výpis prvkov lineárneho zoznamu

Vo funkcii vypis budeme vypisovať postupne všetky prvku lineárneho zoznamu.

Parametre funkcie:

  • TZoznam z - lineárny zoznam, ktorého prvky budeme vypisovať

Návratová hodnota: žiadna

Princíp funkcie o opísaný v kapitole Prechod cez lineárny zoznam.

int JePrazdny(TZoznam z)
{ // ak je zoznam prazdny, funkcia vrati 1, inak 0
  return z.prvy == NULL;
}
void vypis(TZoznam z)
{
  if (JePrazdny(z)) return;  // ak je zoznam prazdny, skonci
  TPrvok *p = z.prvy;  // pomocny prvok typu TPrvok
  while (p->dalsi != NULL) // prechádzaj cez vsetky prvky
  {
    cout << p->data << ", ";  // vypis datovu cast prvku
    p = p->dalsi;	// chod na dalsi prvok
  }
  cout << p->data << endl; // vypis posledny prvok
}

Zmazanie prvého prvku zoznamu

Vo funkcii ZmazPrvy budeme mazať prvý prvok zoznamu. Po zmazaní musia mať smerníky prvy a posledny zoznamu z aktuálne hodnoty.

Parametre funkcie:

  • TZoznam &z - odkaz na lineárny zoznam, v ktorom budeme mazať prvý prvok.

Návratová hodnota: žiadna

Na riadku 4 je vytvorený pomocný smerník, ktorý bude mať hodnotu prvého prvku zoznamu. Ako prvé je potrebné nastaviť smerník prvy zoznamu z (z.prvy) na prvok, ktorý bude po zmazaní prvého prvku zoznamu nový prvý prvok. Toto je vlastne druhý prvok zoznamu (p->dalsi). Na koniec (riadok 6) treba zmazať prvý prvok.

1 void ZmazPrvy(TZoznam &z)
2 {  if (z.prvy == NULL)  // prazdny zoznam
3     return;
4    TPrvok *p=z.prvy;
5    z.prvy=p->dalsi;
6    delete p;
7 }

Zmazanie posledného prvku zoznamu

Vo funkcii ZmazPosledny budeme mazať posledný prvok zoznamu. Po zmazaní musia mať smerníky prvy a posledny zoznamu z aktuálne hodnoty.

Parametre funkcie:

  • TZoznam &z - odkaz na lineárny zoznam, v ktorom budeme mazať prvý prvok.

Návratová hodnota: žiadna

  • riadok 2: zoznam je prázdny. Funkcia skončí
  • riadok 5: v zozname je len jeden prvok. Po jeho zmazaní bude zoznam prázdny.
  • riadok 11: v zozname je viac ako 1 prvok. Po zmazaní posledného prvku bude nový posledný prvok ten, ktorý bol v pôvodnom zozname predposledný.
    • Hľadanie predposledného prvku: riadok 11 a 12. Predposledný prvok je taký prvok p, pre ktorý platí:
      • p->dalsi je posledný prvok zoznamu. Vieme, že posledný prvok zoznamu má hodnotu smerníka dalsi NULL. Preto ak platí výraz p->dalsi->dalsi == NULL, tak potom je prvok p predposledný.
      • Ak sme našli predposledný prvok, tak môžeme zmazať posledný prvok (riadok 13)
      • Prvok p sa stane posledným prvkom. Posledný prvok má hodnotu smerníka dalsi NULL (riadok 14)
      • Prvok p nastavíme ako posledný prvok zoznamu z (riadok 15).
 1 void ZmazPosledny(TZoznam &z)
 2 {  if (z.prvy == NULL)  // prazdny zoznam
 3     return;
 4   TPrvok *p = z.prvy;
 5   if (p->dalsi == NULL)  // je len jeden prvok
 6   { delete p;
 7     z.prvy = z.posledny= NULL;  // upravime strukturu z
 8     return;
 9   }
10   // najdeme predposledny prvok
11   while (p->dalsi->dalsi != NULL)
12     p = p->dalsi;
13   delete p->dalsi;	// zmazeme posledny
14   p->dalsi = NULL;  // a nastavime NULL na aktualny posledny prvok
15   z.posledny = p;  //upravime zoznam tak, aby ukazoval spravne na
16 }			  // posledny prvok

Zmazanie lineárneho zoznamu

Vo funkcii Zmaz budeme mazať všetky prvky zoznamu.

Parametre funkcie:

  • TZoznam &z - odkaz na lineárny zoznam, ktorého prvky budeme mazať.

Návratová hodnota: žiadna

Je jedno, či budeme mazať zoznam od začiatku, alebo od konca. Implementácia mazania Zmaz2 je ale efektívnejšia pretože zložitosť funkcie ZmazPrvy je O(1) a zložitosť funkcie ZmazPosledny je O(n).

void Zmaz(TZoznam &z)
{ 
  while (!JePrazdny(z)) // pokial je v zozname nejaky prvok
    ZmazPosledny(z); // zmaze posledny prvok
}

void Zmaz2(TZoznam &z)
{ 
  while (!JePrazdny(z)) // pokial je v zozname nejaky prvok
    ZmazPrvy(z); // zmaze prvy prvok
}

Použitie lineárneho zoznamu v programe

void main()
{ TZoznam zoznam;
  zoznam.prvy =NULL;
  zoznam.posledny = NULL;
  int udaj,n=4; //pocet zaznamov
  for(int i=0;i<n;i++)
  {   cin>>udaj;
	 Vloz_z(zoznam, udaj);
  }
  for(int i=0;i<n;i++)
  {   cin>>udaj;
	 Vloz_k(zoznam, udaj);
  }
  vypis(zoznam);
  Zmaz(zoznam);
  
  for(int i=0;i<n;i++)
  {   cin>>udaj;
	 Vloz(zoznam, udaj);
  }
  vypis(zoznam);
  Zmaz(zoznam);

Odkazy