Seznami
V prejšnji lekciji smo spoznali razred
Vector
kot eno
od splošno uporabnih podatkovnih struktur, ki so namenjene
shranjevanju in iskanju podatkov. Sklicevali smo se na podobnost
s tabelami in zares je vektor implementiran kot tabela.
Vector
je le ena od tovrstnih podatkovnih struktur,
ostale bomo spoznali pri predmetu Računalništvo 2.
Za radovedne povejmo, da lahko dober nabor tovrstnih struktur
najdemo kot razrede, ki implemetirajo vmesnika
Collection
in Map
.
Radovedneže napotimo tudi na izvorno kodo razreda
Vector
, ki je na voljo (kot tudi velika večina ostalih
razredov) v (veliki) datoteki src.zip
, ki je običajno
instalirana v vrhnji mapi J2SDK (to je na šolskih računalnikih
običajno C:\programs\java\src.zip
).
Tokrat si bomo ogledali nekaj splošnih lastnosti teh razredov in
kako so narejeni. Ker je razred
Vector
narejen s tabelo
in ga (načeloma) že znamo napisati sami (zagotovo bi se ob tem
dolgočasili), bomo pokukali v nov svet rekurzivnih podatkovnih
struktur in si ogledali najpreprostejšo med njimi:
seznam (angl. list).
Seznam
Razmišljajmo splošno: kaj si želimo od podatkovne strukture, ki
naj služi za shranjevanje in iskanje podatkov. Odgovor na to
seveda ni preprost, saj se približno polovica zgodovine
računalništva vrti okrog tega vprašanja (druga polovica
zgodovine pa se ukvarja s tem, kako shranjene podatke obdelati).
Pojavi se seveda tudi kopica zapletenih podpvrašanj: kaj je
podatek, kaj je podatkovna struktura, kaj je shranjevanje, kaj
je iskanje, ... Zato bomo v našem razmisleku skromni in si
zamislili minimalistično rešitev:
- Podatek je poljuben objekt
- Seznam je bodisi prazen, bodisi vsebuje (1) prvi podatek in (2) seznam ostalih podatkov
- Struktura seznam naj ima naslednje metode:
Osnovne metode za delo s seznami seznam = seznam.dodaj(Object obj)
dodaj podatek seznam.prvi()
vrni prvi podatek seznam.ostanek()
vrni ostanek seznama (vse razen prvega podatka) seznam.prazen()
Ali je seznam prazen?
Na sliki si poglejmo, kaj smo določili z gornjimi izhodišči:
Naslednja slika prikazuje seznam s tremi elementi, nizi
Komponento
"Veni"
, "vidi"
, "vici"
:
Napravimo najprej osnovne komponente in metode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class Seznam { private boolean prazen; // == true, ce je seznam prazen private Object element; // prvi element private Seznam ostali; // ostanek seznama public Seznam(Object ee, Seznam oo) { element = ee; ostali = oo; prazen = false; } public Seznam() { element = null; ostali = null; prazen = true; } public Seznam dodaj(Object obj) { return new Seznam(obj, this); } public Object prvi() { return element; } public Seznam ostanek() { return ostali; } public boolean prazen() { return prazen; } } // class |
prazen
smo morali dodati zato, da je prazen
seznam še vedno objekt. Če bi uporabljali le funkcije (statične
metode), bi lahko imeli vrednost null
za prazen seznam.
Naloga 1
Odgovori na naslednja vprašanja:
- Zakaj smo rekli, da je Seznam rekurzivna podatkovna struktura?
- Kaj je prazen seznam?
- Koliko elementov lahko hrani seznam?
- Kako lahko ugotovimo, koliko elementov je v seznamu?
- Kako se primerjajo operacije v strukturi
Vector
z operacijami v strukturiSeznam
? - Ali je vstavljanje ali brisanje elementa na sredi seznama draga operacija?
Za objekte se spodobi, da definirajo metode
Metoda
equals
in
toString
, zato jih dodajmo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public boolean equals(Seznam s) { if (this.prazen() && s.prazen()) return true; if (this.prazen() != s.prazen()) return false; return element.equals(s.prvi()) && ostali.equals(s.ostali); } public String toString() { String s = "("; Seznam l = this; while (l != null && !l.prazen()) { if (l.element != null) s += l.element.toString(); else s += "()"; l = l.ostali; if (l != null && !l.prazen()) s += " "; } s += ")"; return s; } |
equals
je rekurzivna metoda, kar je najbolj
naraven način za obravnavo rekurzivnih podatkovnih struktur.
Metoda toString
pa je iterativna (ena redkih v tej
lekciji).
Preden napišemo testni program, si napravimo še metodo, ki
tabelo predela v seznam.
Zdaj pa si lahko ogledamo delovanje:
Kot vidimo, metoda
in poglejmo kako deluje
1 2 3 4 5 6 7 8 | public static Seznam napolni(Object[] tbl, int index) { if (index >= tbl.length) return new Seznam(); return new Seznam(tbl[index], napolni(tbl, index+1)); } public static Seznam napolni(Object[] tbl) { return napolni(tbl, 0); } |
SeznamTest1.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public class SeznamTest1 { public static void main(String[] aa) { String[] ss = {"Veni", "vidi", "vici"}; String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"}; Seznam ll = Seznam.napolni(ss); Seznam mm = Seznam.napolni(ss); Seznam nn = Seznam.napolni(tt); System.out.println(ll); System.out.println(mm); System.out.println(nn); System.out.println(ll.equals(mm)); System.out.println(ll == mm); System.out.println(ll.equals(nn)); mm = mm.dodaj(null); mm = mm.dodaj(new Seznam()); System.out.println(mm); Seznam kk = new Seznam(); kk = kk.dodaj("GHI"); kk = kk.dodaj("DEF"); kk = kk.dodaj("ABC"); System.out.println(kk); ll = ll.dodaj(nn); System.out.println(ll); ll = Seznam.napolni(ss); ll = ll.dodaj(ll); System.out.println(ll); } // main } // class |
$ java SeznamTest1 (Veni, vidi, vici) (Veni, vidi, vici) (Ceterum, censeo, Carthaginem, esse, delendam) true false false ((), (), Veni, vidi, vici) (ABC, DEF, GHI) ((Ceterum, censeo, Carthaginem, esse, delendam), Veni, vidi, vici) ((Veni, vidi, vici), Veni, vidi, vici) |
dodaj()
dodaja elemente na začetek
seznama. Napišimo še metodo, ki dodaja elemente na konec
seznama.
1 2 3 4 | public void dodajNaKonec(Seznam l) { if (ostali == null || ostali.prazen()) ostali = l; else ostali.dodajNaKonec(l); } |
SeznamTest2.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | public class SeznamTest2 { public static void main(String[] aa) { String[] ss = {"Veni", "vidi", "vici"}; String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"}; Seznam ll = Seznam.napolni(ss); Seznam nn = Seznam.napolni(tt); System.out.println(ll); System.out.println(nn); Seznam mm = ll.dodaj(nn); System.out.println(mm); ll.dodajNaKonec(nn); System.out.println(ll); ll = new Seznam(); ll.dodajNaKonec(nn); System.out.println(ll); ll = new Seznam("???",new Seznam()); ll.dodajNaKonec(nn); System.out.println(ll); ll = Seznam.napolni(ss); ll = ll.dodaj(ll); ll = ll.dodaj("!!!"); ll.dodajNaKonec(new Seznam("???",null)); System.out.println(ll); } // main } // class |
$ java SeznamTest2 (Veni, vidi, vici) (Ceterum, censeo, Carthaginem, esse, delendam) ((Ceterum, censeo, Carthaginem, esse, delendam), Veni, vidi, vici) (Veni, vidi, vici, Ceterum, censeo, Carthaginem, esse, delendam) (Ceterum, censeo, Carthaginem, esse, delendam) (???, Ceterum, censeo, Carthaginem, esse, delendam) (!!!, (Veni, vidi, vici, ???), Veni, vidi, vici, ???) |
Naloga 2
Zakaj se je v zadnjem primeru z enim klicem metode
dodajNaKonec
spremenil seznam na dveh
mestih?
Metoda
dodajNaKonec()
spremeni objekt,
kjer jo uporabimo. Morda bi bila intuitivno bolj smiselna
spodnja statična metoda zlepi(Seznam aa, Seznam bb)
.
Dodali bomo še enostavno metodo za kopiranje seznamov in metodo
za obračanje seznama.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public Seznam kopija() { if (prazen()) return new Seznam(); return new Seznam(element, ostali.kopija()); } public static Seznam zlepi(Seznam a, Seznam b) { if (a == null || a.prazen()) return b; return new Seznam(a.prvi(), zlepi(a.ostanek(), b)); } public Seznam obrni() { if (prazen()) return new Seznam(); return zlepi(ostali.obrni(), new Seznam(element, new Seznam())); } // obrni |
Testni program:
Napišimo zdaj metodo, ki vrne število elementov v seznamu, in metodo, ki
vrne
Testni program:
SeznamTest3.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class SeznamTest3 { public static void main(String[] aa) { String[] ss = {"Veni", "vidi", "vici"}; String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"}; Seznam ll = Seznam.napolni(ss); Seznam mm = Seznam.napolni(tt); Seznam nn = ll.kopija(); System.out.println(ll); System.out.println(mm); System.out.println(nn); System.out.println(nn == ll); System.out.println(nn.equals(ll)); nn = Seznam.zlepi(ll,mm); System.out.println(nn); nn = ll.obrni(); System.out.println(nn); } // main } // class |
$ java SeznamTest3 (Veni, vidi, vici) (Ceterum, censeo, Carthaginem, esse, delendam) (Veni, vidi, vici) false true (Veni, vidi, vici, Ceterum, censeo, Carthaginem, esse, delendam) (vici, vidi, Veni) |
k
-ti element.
1 2 3 4 5 6 7 8 9 10 11 | public int steviloElementov() { if (prazen()) return 0; return 1 + ostanek().steviloElementov(); } public Object elementNa(int k) { if (k < 0) { return null; } else if (prazen()) { return null; } else if (k == 0) { return prvi(); } else { return ostanek().elementNa(k-1); } } |
SeznamTest4.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class SeznamTest4 { public static void main(String[] aa) { String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"}; Seznam ll = Seznam.napolni(tt); System.out.println(ll); int max = ll.steviloElementov(); System.out.println(max); for (int ii = -1; ii <= max; ii++) { System.out.println(ii+" "+ll.elementNa(ii)); } } // main } // class |
$ java SeznamTest4 (Ceterum, censeo, Carthaginem, esse, delendam) 5 -1 null 0 Ceterum 1 censeo 2 Carthaginem 3 esse 4 delendam 5 null |
Skoraj vse dosedanje metode so rekurzivne.
Napravili bi jih lahko tudi iterativno (z zankami),
podobno kot smo napravili
toString()
(poskusi!).
Za zaključek ukvarjanja s seznami napravimo še metodi za
vstavljanje elementa na poljubno mesto v seznamu in metodo za
brisanje elementa iz seznama. Metoda
Dogajanje pri vstavljanju se loči na dva do tri primere:
vstavljanje na začetek seznama (ki je lahko prazen ali neprazen)
in vstavljanje na sredini seznama. Dogajanje opisujejo slike:
vstavi(Objekt obj, int
k)
bo vstavila element obj
na k
-to mesto
v seznamu. Metoda elementNa(k)
vrne element na
k
-tem mestu. Metoda zbrisi(int k)
zbriše
element na k
-tem mestu. Vrednost metode zbrisi
na bo objekt, ki ga je pobrisala. Metoda vstavi
pa na
vrne kot vrednost tisti del seznama, ki se začne z novo
vstavljenim elementom. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public Seznam vstavi(Object oo, int index) { if (index < 0) return null; // premajhen index if (index == 0) { // vstavljanje na začetku Seznam nn = prazen ? new Seznam() : new Seznam(element,ostali); element = oo; ostali = nn; prazen = false; return this; } Seznam ll = this; // trenutni element seznama Seznam pp = null; // prejšnji element seznama while (index > 0 && !ll.prazen()) { index--; pp = ll; ll = ll.ostanek(); } if (index > 0) return null; // smo na koncu, prevelik indeks Seznam nn = new Seznam(oo,ll); pp.ostali = nn; // vstavljanje na sredi return nn; } // vstavi public Object zbrisi(int index) { Object zbrisan; // zapomnimo si zbrisan objekt if (index < 0) return null; // premajhen index if (prazen) return null; // iz praznega seznama ne moremo brisati if (index == 0) { // brisanje na zaeetku zbrisan = element; // zapomnimo si zbrisan objekt if (ostali.prazen()) { // zbrisati moramo edini element element = null; ostali = null; prazen = true; } else { // brišemo prvi element, naslednik ni prazen element = ostali.element; ostali = ostali.ostali; } return zbrisan; } Seznam ll = this; // trenutni element seznama Seznam pp = null; // prejšnji element seznama while (index > 0 && !ll.prazen()) { index--; pp = ll; ll = ll.ostanek(); } if (ll.prazen()) return null; // smo na koncu, prevelik indeks zbrisan = ll.element; // zapomnimo si zbrisan objekt pp.ostali = ll.ostali; // brisanje na sredini return zbrisan; } // zbrisi |
Vstavljanje na sredi seznama:
Vstavljanje na začetek nepraznega seznama:
Vstavljanje na začetek praznega seznama:
Slike delovanja metode
zbrisi
so pri
Testni program:
SeznamTest5.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import java.io.*; public class SeznamTest5 { public static void main(String[] aa) throws IOException { String vrsta; BufferedReader vhod; vhod = new BufferedReader(new InputStreamReader(System.in)); Seznam ll = Seznam.napolni(aa); System.out.println("VSTAVLJANJE"); int ii = 1; while ((vrsta = vhod.readLine()) != null) { if (vrsta.length() == 0) break; int index = Integer.parseInt(vrsta); System.out.println(ll.vstavi("NOV"+ii, index)); System.out.println(ll); ii++; } System.out.println("BRISANJE"); while ((vrsta = vhod.readLine()) != null) { if (vrsta.length() == 0) break; int index = Integer.parseInt(vrsta); System.out.println(ll.zbrisi(index)); System.out.println(ll); } } // main } // class |
$ java SeznamTest5 veni vidi vici 2 VSTAVLJANJE 0 (NOV1, veni, vidi, vici) (NOV1, veni, vidi, vici) 2 (NOV2, vidi, vici) (NOV1, veni, NOV2, vidi, vici) 4 (NOV3, vici) (NOV1, veni, NOV2, vidi, NOV3, vici) 6 (NOV4) (NOV1, veni, NOV2, vidi, NOV3, vici, NOV4) 0 (NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4) (NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4) 8 (NOV6) (NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4, NOV6) -1 null (NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4, NOV6) 10 null (NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4, NOV6) BRISANJE 0 NOV5 (NOV1, veni, NOV2, vidi, NOV3, vici, NOV4, NOV6) 1 veni (NOV1, NOV2, vidi, NOV3, vici, NOV4, NOV6) 2 vidi (NOV1, NOV2, NOV3, vici, NOV4, NOV6) 3 vici (NOV1, NOV2, NOV3, NOV4, NOV6) 4 NOV6 (NOV1, NOV2, NOV3, NOV4) 10 null (NOV1, NOV2, NOV3, NOV4) -1 null (NOV1, NOV2, NOV3, NOV4) |
Na koncu podajmo še celoten razred
Za radovedne podajmo še implementacijo razreda seznam, ki bi
bila narejena klasično, torej tako, da je prazen seznam vrednost
Kaj je komu bolj všeč (objektne ali statične metode), je stvar
okusa. V Javi, ki je objektni programski jezik, je verjetno bolj
smiselno programirati z objekti.
Seznam
skupaj z
nekaj več komentarji.
Seznam.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | public class Seznam { // komponente: // prazen označuje, da je to prazen seznam; vrednosti element in // ostali sta v tem primeru nepomembni (običajno sta null). // To potrebujemo zato, da je tudi prazen seznam objekt, da lahko // na njem kličemo objektne metode. Lahko bi napisali podoben // razred, ki bi za prazen seznam uporabljal vrednost null (kar bi // bilo običajno) in bi zato imel predvsem statične metode. // Če nimamo objekta, ki je prazen seznam, npr. ne moremo poklicati // metode dodaj na praznem seznamu. boolean prazen; // element vsebuje objekt, ki je (prvi) element seznama Object element; // ostali je seznam ostalih elementov Seznam ostali; // konstruktor praznega seznama public Seznam(Object ee, Seznam oo) { element = ee; ostali = oo; prazen = false; } // Seznam // konstruktor elementa seznama public Seznam() { element = null; ostali = null; prazen = true; } // Seznam // na začetek seznama this dodamo nov element ee in nov seznam // vrnemo kot vrednost funkcije public Seznam dodaj(Object ee) { return new Seznam(ee,this); } // dodaj // vrnemo prvi element seznama this public Object prvi() { return element; } // prvi // vrne ostanek seznama this (brez prvega elementa) public Seznam ostanek() { return ostali; } // ostanek // vrne true natanko tedaj, ko je seznam this prazen public boolean prazen() { return prazen; } // prazen // metoda, ki ugotavlja strukturno enakost dveh seznamov: this in oo // nadomešča metodo Object.equals public boolean equals(Object oo) { Seznam ss = (Seznam)oo; // prazna seznama sta enaka if (this.prazen() && ss.prazen()) return true; // če je eden prazen in drugi ni, nista enaka if (this.prazen() != ss.prazen()) return false; // enaka sta natanko tedaj, ko sta enaka prva elementa in ko sta // enaka ostanka return element.equals(ss.prvi()) && ostali.equals(ss.ostali); } // equals // metoda, ki seznam pretvori v String; nadomešča Object.toString // Elementi seznama so ločeni s presledki, cel seznam pa je v // oklepajih. Prazen seznam je "()". // Elementi seznama so lahko poljubni objekti, vključno s seznami // in tudi vrednostjo null. // V tej metodi dovolimo, da se seznam konča z vrednostjo null in // ne le s pravim praznim seznamom. public String toString() { String ss = "("; Seznam ll = this; while (ll != null && !ll.prazen()) { if (ll.element != null) ss += ll.element.toString(); else ss += "()"; ll = ll.ostali; if (ll != null && !ll.prazen()) ss += ", "; } ss += ")"; return ss; } // toString // elemente iz tabele tbl od indeksa index naprej pretvorimo v // seznam, ki ga vrnemo kot vrednost metode. public static Seznam napolni(Object[] tbl, int index) { if (index >= tbl.length) return new Seznam(); return new Seznam(tbl[index], napolni(tbl,index+1)); } // napolni // Vse elemente tabele tbl pretvorimo v seznam, ki ga vrnemo kot // vrednost metode. public static Seznam napolni(Object[] tbl) { return napolni(tbl,0); } // napolni // Elemente seznama ll dodamo na konec seznama this. // Seznam this pri tem destruktivno spremenimo, prav tako // NE napravimo kopije seznama ll. public void dodajNaKonec(Seznam ll) { if (prazen()) { // this je prazen, postane kar prvi element ll prazen = ll.prazen; element = ll.element; ostali = ll.ostali; } // če smo na koncu seznama this, prilepimo seznam ll else if (ostali == null || ostali.prazen()) ostali = ll; // sicer dodamo ll na konec ostanka seznama this else ostali.dodajNaKonec(ll); } // dodajNaKonec // vrnemo kopijo seznama this public Seznam kopija() { // če je seznam prazen, vrnemo nov prazen seznam if (prazen()) return new Seznam(); // sicer pa vrnemo nov seznam, ki se sestoji iz prvega elementa // in kopije ostanka return new Seznam(element,ostali.kopija()); } // kopija // vrnemo seznam, ki ima za elemente najprej vse elemente seznama // aa in nato vse elemente seznama bb public static Seznam zlepi(Seznam aa, Seznam bb) { // če je seznam aa prazen, vrnemo kar seznam bb if (aa == null || aa.prazen()) return bb; // sicer pa vrnemmo seznam iz prvega elementa in zlepljenega // ostanka in bb return new Seznam(aa.prvi(), zlepi(aa.ostanek(),bb)); } // zlepi // obrnemo vrstni red elementov v seznamu public Seznam obrni() { // obrnjen prazen seznam je spet prazen seznam if (prazen()) return new Seznam(); // sicer pa na obrnjen ostanek seznama prilepimo seznam s samo // prvim elementom return zlepi(ostali.obrni(),new Seznam(element,new Seznam())); } // obrni // vrnemo število elementov v sezamu public int steviloElementov() { // število elementov praznega seznama je 0 if (prazen()) return 0; // sicer pa je za 1 večje od števila elementov ostanka return 1 + ostanek().steviloElementov(); } // steviloElementov // vrnemo index-ti element seznama; če je index zunaj območja, // vrnemo null (nikoli ne povzročimo napake) public Object elementNa(int index) { if (index < 0) return null; // index premajhen if (prazen()) return null; // index prevelik if (index == 0) return prvi(); // prvi element ima index 0 return ostanek().elementNa(index-1); // (index-1)-ti elt ostanka } // elementNa // na index-to mesto vstavimo nov element oo; po vstavljanju bo // elementNa(index) == oo // vrnemo podseznam, ki se začne z novim objektom, ali null, če // smo dobili neumen index public Seznam vstavi(Object oo, int index) { if (index < 0) return null; // premajhen index if (index == 0) { // vstavljanje na začetku // ker vrednosti this ne moremo popraviti, bomo najprej this // skopirali v nov objekt in nato v this popravili njegove // komponente tako, da bo to postal nov element seznama Seznam nn = prazen ? new Seznam() : new Seznam(element,ostali); element = oo; ostali = nn; prazen = false; return this; } Seznam ll = this; // trenutni element seznama Seznam pp = null; // prejšnji element seznama // po seznamu se peljemo dokler ne pridemo do konca ali pa do // index-tega elementa while (index > 0 && !ll.prazen()) { index--; pp = ll; ll = ll.ostanek(); } if (index > 0) return null; // smo na koncu, prevelik indeks // pp ne more biti null, ker smo primer index==0 obdelali posebej // naredimo nov objekt, ki vsebuje nov element in kaže naprej // na ll. nato prejšnji element seznama nastavimo tako, da mu // sledi nov element Seznam nn = new Seznam(oo,ll); pp.ostali = nn; return nn; } // vstavi // iz seznama zbrišemo element na index-tem mestu // vrnemo zbrisan objekt ali null, če smo dobili neumen index public Object zbrisi(int index) { Object zbrisan; // zapomnimo si zbrisan objekt if (index < 0) return null; // premajhen index if (prazen) return null; // iz praznega seznama ne moremo brisati if (index == 0) { // brisanje na začetku // ker vrednosti this ne moremo popraviti, iz prvega objekta // naredimo drugega zbrisan = element; // zapomnimo si zbrisan objekt if (ostali.prazen()) { // zbrisati moramo edini element element = null; ostali = null; prazen = true; } else { // brišemo prvi element, naslednik ni prazen element = ostali.element; ostali = ostali.ostali; } return zbrisan; } Seznam ll = this; // trenutni element seznama Seznam pp = null; // prejšnji element seznama // po seznamu se peljemo dokler ne pridemo do konca ali pa do // index-tega elementa while (index > 0 && !ll.prazen()) { index--; pp = ll; ll = ll.ostanek(); } if (ll.prazen()) return null; // smo na koncu, prevelik indeks zbrisan = ll.element; // zapomnimo si zbrisan objekt // ker smo index==0 obdelali prej, pp ne more biti null pp.ostali = ll.ostali; // brisanje na sredini return zbrisan; } // zbrisi } // class |
null
. Ker potem ne moremo uporabljati objektnih metod
na praznem seznamu so v tej implementaciji skoraj vse metode
statične.
SSeznam.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | public class SSeznam { Object element; SSeznam ostali; public SSeznam(Object ee, SSeznam oo) { element = ee; ostali = oo; } // SSeznam public static SSeznam dodaj(Object ee, SSeznam oo) { return new SSeznam(ee,oo); } // dodaj public static Object prvi(SSeznam ss) { return ss.element; } // prvi public static SSeznam ostanek(SSeznam ss) { return ss.ostali; } // ostanek public static boolean prazen(SSeznam ss) { return ss == null; } // prazen public boolean equals(Object oo) { return equals(this,(SSeznam)oo); } // equals public static boolean equals(SSeznam aa, SSeznam bb) { boolean ok; if (prazen(aa) != prazen(bb)) return false; if (prazen(aa) && prazen(bb)) return true; if ((prvi(aa)==null) != (prvi(bb)==null)) return false; return ( ((prvi(aa) == null) && (prvi(bb) == null)) || (prvi(aa).equals(prvi(bb))) ) && equals(ostanek(aa),ostanek(bb)); } // equals public String toString() { return toString(this); } // toString public static String toString(SSeznam ll) { String ss = "("; while (!prazen(ll)) { Object pp = prvi(ll); if (pp == null) ss += "()"; else ss += prvi(ll).toString(); ll = ostanek(ll); if (!prazen(ll)) ss += ", "; } ss += ")"; return ss; } // toString public static SSeznam napolni(Object[] tbl, int index) { if (index >= tbl.length) return null; return dodaj(tbl[index], napolni(tbl,index+1)); } // napolni public static SSeznam napolni(Object[] tbl) { return napolni(tbl,0); } // napolni public static SSeznam kopija(SSeznam aa) { if (prazen(aa)) return null; return dodaj(prvi(aa),kopija(ostanek(aa))); } // kopija public static SSeznam dodajNaKonec(SSeznam aa, SSeznam bb) { if (prazen(aa)) return bb; if (prazen(ostanek(aa))) aa.ostali = bb; else dodajNaKonec(ostanek(aa),bb); return aa; } // dodajNaKonec public static SSeznam zlepi(SSeznam aa, SSeznam bb) { if (prazen(aa)) return bb; return dodaj(prvi(aa), zlepi(ostanek(aa),bb)); } // dodajNaKonec public static void main(String[] aa) { String[] ss = {"Veni", "vidi", "vici"}; String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"}; SSeznam ll = SSeznam.napolni(ss); SSeznam mm = SSeznam.napolni(ss); SSeznam nn = SSeznam.napolni(tt); System.out.println(ll); System.out.println(mm); System.out.println(nn); System.out.println(ll.equals(mm)); System.out.println(ll == mm); System.out.println(ll.equals(nn)); mm = dodaj(null,mm); System.out.println(mm); SSeznam kk = null; kk = dodaj("GHI",kk); kk = dodaj("DEF",kk); kk = dodaj("ABC",kk); System.out.println(kk); ll = dodaj(nn,ll); System.out.println(ll); ll = SSeznam.napolni(ss); ll = dodaj(ll,ll); System.out.println(ll); ll = napolni(ss); nn = napolni(tt); System.out.println(ll); System.out.println(nn); mm = dodaj(nn,ll); System.out.println(mm); ll = dodajNaKonec(ll,nn); System.out.println(ll); ll = napolni(ss); ll = dodaj(ll,ll); ll = dodaj("!!!",ll); ll = dodajNaKonec(ll,dodaj("???",null)); System.out.println(ll); ll = napolni(ss); ll = dodaj(ll,kopija(ll)); ll = dodaj("!!!",ll); ll = zlepi(ll,dodaj("???",null)); System.out.println(ll); } // main } // class |
Zelo radovednega študenta napotimo še k ogledovanju razreda
java.util.LinkedList
, ki predstavlja implementacijo
seznamov v javanski knjižnici. Tam gre sicer za dvojne sezname
(v objektu je poleg kazalca naprej tudi kazalec nazaj), vendar
je ogled razreda v src.zip
prav poučen.