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
Napravimo najprej osnovne komponente in metode.
Komponento
Naslednja slika prikazuje seznam s tremi elementi, nizi
"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
Vectorz 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.


