Tfr2-23-02 Hlaðar Og Biðraðir 1

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 29

TÖL203G TÖLVUNARFRÆÐI 2

2. Hlaðar og biðraðir 1

Hjálmtýr Hafsteinsson
Vor 2023

Byggt á glærum eftir Sedgewick og Wayne


Í þessum fyrirlestri

▪ Huglæg gagnatög
▪ Hlaðar og biðraðir
▪ Hlaðar
▪ Útfærsla með tengdum lista 1.3
▪ Útfærsla með fylki af fastri stærð
▪ Útfærsla með fylki af breytilegri stærð

2
Hlaðar (stacks) og biðraðir (queues)

▪ Mikilvæg samsett gagnatög innsetning eyðing

▪ Gildi: safn af hlutum


▪ Aðgerðir: innsetning, eyðing, ítrun, athuga hvort tóm, ...
▪ Augljóst hvað gerist við innsetningu
▪ ... en hvaða stak á að taka út við eyðingu?

innsetning eyðing

biðröð
hlaði

▪ Hlaði: Eyða stakinu sem var síðast sett inn LIFO = "last in first out"

▪ Biðröð: Eyða stakinu sem fyrst var sett inn FIFO = "first in first out"

3
Fyrirlestraæfingar

1. Í hlaða er tekið út stakið sem síðast var sett inn. Í biðröð er það stakið sem
fyrst var sett inn. Nefnið þrjár aðrar leiðir til að velja það stak sem á að taka út.
Vísbending: Það má nota gildi staksins

2. Segjum að x sé hnútur í tengdum lista og ekki síðasti hnúturinn. Hvað gerist


þegar eftirfarandi skipun er framkvæmd?
x.next = x.next.next;

3. Forrit setur (push) gildin 1, 2, 3, 4, 5 á hlaða í þessari röð. Þegar gildi eru tekin
af hlaðanum (pop) þá eru þau prentuð út. Sýnið hvenær pop-aðgerðirnar eru
framkvæmdar til að fá eftirfarandi útprentaðar runur (ef það er mögulegt)?
a. 3, 5, 4, 2, 1
b. 5, 4, 3, 1, 2
4
Forritaskil, notendaforrit og útfærsla

▪ Aðskiljum notendaforrit og útfærslu með forritaskilum (API)


Notendaforrit Forritaskil Útfærsla
(client) (API) (implementation)

Forritaskil: Aðgerðir sem skilgreina hegðun gagnatagsins

Notendaforrit: Forrit sem notar aðgerðir forritaskilanna

Útfærsla: Kóði sem útfærir einstakar aðgerðir forritaskilanna

▪ Ávinningur:
▪ Hönnun: Býr til afmörkuð, endurnýtanleg forritasöfn (libraries)
▪ Afköst: Auðveldara að skipta inn hraðvirkari útfærslum

Dæmi: Hlaði, biðröð, poki, forgangsbiðröð, táknatafla, union-find, ...

5
Forritaskil fyrir hlaða

▪ Upphitunarútgáfa: Hlaði af strengjum


push pop
public class StackOfStrings

StackOfStrings() búa til tóman hlaða

void push(String item) setja nýjan streng efst á hlaða

eyða og skila strengnum sem


String pop()
síðast var settur inn

boolean isEmpty() er hlaðinn tómur?

int size() fjöldi strengja í hlaðanum

▪ Afkastamarkmið: Viljum að hver aðgerð taki aðeins fastan (þ.e. Θ(1)) tíma
▪ Notendaforrit: Snúa við runu af strengjum af staðalinntaki

6
Dæmi um notendaforrit

▪ Notendaforrit: Snúa við runu af strengjum af staðalinntaki


▪ Lesa alla strengi og setja á hlaða push pop

▪ Taka alla strengi af hlaða og prenta út

public class ReverseStrings


{
public static void main(String[] args)
{
StackOfStrings stack = new StackOfStrings();
while (!StdIn.isEmpty())
stack.push(StdIn.readString());
while (!stack.isEmpty()) % more tinyTale.txt
StdOut.println(stack.pop()); it was the best of times ...
}
} % java ReverseStrings < tinyTale.txt
... times of best the was it
7
Hvernig á að útfæra hlaða með tengdum lista?

A. Ekki hægt á hagkvæman hátt með eintengdum lista

efst á hlaða

B. it was the best of null

efst á hlaða
C.
of best the was it null

8
Hlaði: útfærsla með tengdum lista

▪ Viðhalda bendi first á fyrsta hnút (node) í eintengdum lista (singly-linked list)
▪ Setja nýtt stak (push) fyrir framan first
▪ Taka stak (pop) frá first

efst á hlaða

of best the was it null

first

9
Hlaði: útfærsla með tengdum lista

first
▪ Viðhöldum bendi á fyrsta hnút í tengdum lista
▪ Innsetning og eyðing að framan push("to")

push("be")

innsetning fremst í push("or")


tengda listann
push("not")

eyðing fremst úr push("to")


tengda listann
pop()

push("be")

pop()

Ath: Þurfum aðeins einn bendi til að


pop()
gera þetta á hagkvæman hátt

10
pop-aðgerð á hlaða: útfærsla með tengdum lista

innri klasi
geyma stak til að skila private class Node
String item = first.item; {
String item;
Node next;
}

eyða fyrsta hnúti


first = first.next;

skila geymda stakinu


return item;

11
push-aðgerð á hlaða: útfærsla með tengdum lista

innri klasi
geyma bendi á listann
private class Node
Node oldfirst = first;
{
String item;
Node next;
}

búa til nýjan hnút fyrir nýja stakið


first = new Node();

setja rétt gildi í tilviksbreytur nýja hnútarins


first.item = "not";
first.next = oldfirst;

12
Hlaði: Útfærsla með tengdum lista í Java
public class LinkedStackOfStrings
{
private Node first = null;

private class Node


{ falinn (private) innri klasi
String item;
Node next;
}

public boolean isEmpty()


{ return first == null; }

public void push(String item)


{
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}

public String pop()


{
String item = first.item;
first = first.next;
return item;
} Java Tutor
}
13
Fyrirlestraæfingar

1. Í hlaða er tekið út stakið sem síðast var sett inn. Í biðröð er það stakið sem
fyrst var sett inn. Nefnið þrjár aðrar leiðir til að velja það stak sem á að taka út.
Vísbending: Það má nota gildi staksins

2. Segjum að x sé hnútur í tengdum lista og ekki síðasti hnúturinn. Hvað gerist


þegar eftirfarandi skipun er framkvæmd?
x.next = x.next.next;

3. Forrit setur (push) gildin 1, 2, 3, 4, 5 á hlaða í þessari röð. Þegar gildi eru tekin
af hlaðanum (pop) þá eru þau prentuð út. Sýnið hvenær pop-aðgerðirnar eru
framkvæmdar til að fá eftirfarandi útprentaðar runur (ef það er mögulegt)?
a. 3, 5, 4, 2, 1
b. 5, 4, 3, 1, 2
14
Hlaði: Afköst útfærslu með tengdum lista

▪ Staðhæfing: Sérhver aðgerð tekur fastan tíma í versta tilfelli


▪ Staðhæfing: Hlaði með N stökum notar ~40N bæti

innri klasi
16 bæti (aukakostnaður við hluti)
private class Node
{
8 bæti (aukakostnaður við innri klasa)
String item;
Node next; 8 bæti (tilvísun á String)
}
8 bæti (tilvísun á Node)

40 bæti á hnút í hlaða

Þetta telur aðeins minni fyrir hlaðann (en ekki


minni fyrir strengina sjálfa, sem notendaforritið býr til)

15
Hvernig á að útfæra hlaða af fastri stærð með fylki?

A. Ekki hægt að gera á hagkvæman hátt með fylki.

efst á hlaða

it was the best of times null null null null


B. 0 1 2 3 4 5 6 7 8 9

efst á hlaða

C. times of best the was it null null null null

0 1 2 3 4 5 6 7 8 9

16
Hlaði af fastri stærð: útfærsla með fylki

▪ Nota fylkið s[] til að geyma N stök á hlaða


▪ push: setja inn nýtt stak í s[N] Efsta stak hlaðans er alltaf í sæti s[N-1]
(ef hlaðinn er ekki tómur)
▪ pop: eyða staki í s[N-1]

stærð = 10

s[] it was the best of times null null null null

0 1 2 3 4 5 6 7 8 9

Galli: Yfirflæði í hlaða þegar N verður of stórt [Munum leysa þetta!]

17
Hlaði af fastri stærð: útfærsla með fylki

public class FixedCapacityStackOfStrings


{ smá "svindl"
private String[] s; (verður lagað)
private int N = 0;

public FixedCapacityStackOfStrings(int capacity)


{ s = new String[capacity]; }

public boolean isEmpty()


Setja stak í s[N] og { return N == 0; }
hækka síðan N
(post-increment) public void push(String item)
{ s[N++] = item; }

public String pop()


{ return s[--N]; }
}

Lækka fyrst N og
nota það síðan til að ná í s[N]
(pre-decrement)
18
Álitamál með hlaða

▪ Undirflæði (underflow): kalla á frávik (throw exception) ef pop reynt á tómum


hlaða
▪ Yfirflæði (overflow): auka stærð fylkis í fylkisútgáfu
▪ Null stök: Við leyfum innsetningu á null stökum

▪ Hangs (loitering): Vera með tilvísun á hlut þegar ekki er lengur þörf á því

public String pop()


public String pop() {
{ return s[--N]; } String item = s[--N];
s[N] = null;
hangs return item;
}

Sæti s[N] inniheldur ennþá ekkert hangs


stakið (eða tilvísun á það) ruslasafnari getur aðeins endurnýtt
minni ef það eru engar tilvísanir á það
19
Hlaði: útfærsla með fylki af breytilegri stærð

▪ Vandamál: Ekki samkvæmt forritunarskilum að þurfa að gefa upp stærð!


▪ Spurning: Hvernig á að stækka og minnka fylkið?
▪ Fyrsta tilraun:
▪ push: auka lengd fylkisins s[] um 1
▪ pop: minnka lengd fylkisins s[] um 1
óhagkvæmt fyrir stór N

▪ Of dýrt:
▪ Þurfum að afrita öll stökin yfir í nýja fylkið, fyrir hverja einustu aðgerð
▪ Fylkjaaðgangar við að setja inn fyrstu N stökin = N + (2 + 4 + … + 2(N – 1)) ~ N 2

1 aðgangur 2(k−1) aðgangar við að stækka upp í k


fyrir hvert push (án kostnaðar við að búa til nýja fylkið)

Áskorun: Tryggja að stærðarbreytingin (og afritun staka) gerist sjaldan


20
Hlaði: stækkun fylkisins

▪ Ef fylkið er fyllist, smíða tvöfalt stærra fylki og afrita stökin yfir

public ResizingArrayStackOfStrings() gætum líka byrjað með stærra upphafsfylki, t.d. 8 stök
{ s = new String[1]; }

public void push(String item)


{
if (N == s.length) resize(2 * s.length); Fylkisaðgangar til að setja inn fyrstu N = 2i stökin:
s[N++] = item;
} N + (2 + 4 + 8 + … + N) ~ 3N

private void resize(int capacity)


{ 1 aðgangur k aðgangar við að stækka upp í k
fyrir hvert push (án kostnaðar við að búa til nýja fylkið)
String[] copy = new String[capacity];
for (int i = 0; i < N; i++)
copy[i] = s[i];
s = copy;
}

21
Hlaði: jafnaðarkostnaður við innsetningu

▪ Flestar innsetningar eru mjög ódýrar, en örfáar eru dýrar


▪ Ef við dreifum kostnaði dýru aðgerðanna á allar hinar þá er hver aðgerð að jafnaði
(amortized) frekar ódýr

Fylkisaðgangar til að setja inn fyrstu N = 2i stökin:


einn grár punktur

fjöldi fylkisaðganga
fyrir hvern aðgang
N + (2 + 4 + 8 + … + N) ~ 3N

1 aðgangur k aðgangar við að stækka upp í k rauðu punktarnir gefa


fyrir hvert push (án kostnaðar við að búa til nýja fylkið) uppsafnað meðaltal

fjöldi push() aðgerða

22
Hlaði: minnkun fylkisins

▪ Hvernig minnkum við fylkið?


▪ Fyrsta tilraun:
▪ push: tvöfalda stærð fylkisins s[] þegar fylkið er fullt
▪ pop: helminga stærð fylkisins s[] þegar fylkið er hálf fullt

▪ Of dýr í versta tilfelli:


▪ Skoðum runu af aðgerðunum push - pop - push - pop-… þegar fylkið er fullt
▪ Hver einasta aðgerð tekur Θ(N) tíma!

fullt to be or not

push("to") to be or not to null null null [afritum 4 stök]

pop() to be or not [afritum 4 stök]

push("to") to be or not to null null null [afritum 4 stök]

23
Hlaði: minnkun fylkisins

▪ Hvernig minnkum við fylkið?


▪ Hagkvæm lausn:
▪ push: tvöfalda stærð fylkisins s[] þegar fylkið er fullt
▪ pop: helminga stærð fylkisins s[] þegar fylkið er fjórðungs fullt

public String pop()


{
Þurfum (N > 0) til að passa að
String item = s[--N];
stærð fylkisins verði aldrei 0
s[N] = null;
if (N > 0 && N == s.length/4) resize(s.length/2);
return item;
}

Fastayrðing:
Athugið: Hlaði með engum stökum hefur fylki af stærð 2 Fylkið alltaf á milli 25% og 100% fullt

24
Hlaði: rakning á fylki með breytilegri stærð

stækkun
stækkun

stækkun

minnkun
minnkun

Rakning á stærðarbreytingum fylkis í gegnum runu af push- og pop-aðgerðum

25
Afköst hlaða með fylki af breytilegri stærð

▪ Jafnaðargreining (amortized analysis): Meðal keyrslutími per aðgerð yfir versta tilfellis runu
aðgerða á gagnagrind sem byrjar tóm
▪ Staðhæfing: Sérhver runa af M push- og pop-aðgerðum sem byrjar með tóman hlaða tekur
Θ(M) heildartíma

besta versta jafnaðar


(amortized)

smíði 1 1 1
tvöföldunar og
helmingunar aðgerðir
push 1 N 1

pop 1 N 1

stærð 1 1 1 Athugið:
Langflest sem gert er í Java hefur
vaxtargráða keyrslutíma á N-staka hlaða jafnaðartíma (ekki versta tilfellis
útfærðum með fylki af breytilegri stærð tíma) vegna ruslasöfnunar

26
Minnisnotkun hlaða með fylki af breytilegri stærð

▪ Staðhæfing: Hlaðinn notar á milli ~8N og ~32N bæti til að geyma N stök
▪ ~8N þegar fylkið er fullt
▪ ~32N þegar fylkið er fjórðungsfullt

public class ResizingArrayStackOfStrings


{
8 bæti x stærð fylkisins
private String[] s;
private int N = 0;

}

Athugið:
Þetta telur aðeins minnið sem hlaðinn notar
Minnið fyrir strengina sjálfa getur verið mjög breytilegt
og er "í eigu" forritsins sem notar hlaðann
27
Hlaði: tengdur listi eða fylki af breytilegri stærð

▪ Við getum útfært hlaða með tengdum lista eða fylki af breytilegri stærð. Hvor
aðferðin er betri?
▪ Útfærsla með tengdum lista:
▪ Hver aðgerð tekur fastan tíma í versta tilfelli
▪ Notar aukatíma og aukaminni til að vinna með hlekkina

▪ Útfærsla með fylki af breytilegri stærð: Hvorug aðferðin er augljóslega


▪ Hver aðgerð tekur fastan jafnaðartíma betri. Báðar hafa kosti og galla.
▪ Ekki eins mikið af aukaminni

N=4 to be or not null null null null

28
Fyrirlestraæfingar

1. Í hlaða er tekið út stakið sem síðast var sett inn. Í biðröð er það stakið sem
fyrst var sett inn. Nefnið þrjár aðrar leiðir til að velja það stak sem á að taka út.
Vísbending: Það má nota gildi staksins

2. Segjum að x sé hnútur í tengdum lista og ekki síðasti hnúturinn. Hvað gerist


þegar eftirfarandi skipun er framkvæmd?
x.next = x.next.next;

3. Forrit setur (push) gildin 1, 2, 3, 4, 5 á hlaða í þessari röð. Þegar gildi eru tekin
af hlaðanum (pop) þá eru þau prentuð út. Sýnið hvenær pop-aðgerðirnar eru
framkvæmdar til að fá eftirfarandi útprentaðar runur (ef það er mögulegt)?
a. 3, 5, 4, 2, 1
b. 5, 4, 3, 1, 2
29

You might also like