bild
Skolan för
elektroteknik
och datavetenskap

Föreläsning 8: Prioritetskö, trappa (heap)

  • Prioritetskö
  • Heap (trappa)
  • Trappsortering (Heapsort)
  • Bästaförstsökning
  • Siluettproblemet

Prioritetskö

När man poppar en stack får man ut det senast inpushade. När man tar ut något ur en vanlig kö får man tvärtom ut det som legat längst tid i kön. Man skulle kunna se det som att det som stoppas in tidsstämplas och att det påstämplade talet ger prioriteten för uthämtning.

I en prioritetskö stämplas en prioritet på varje objekt som stoppas in och vid uthämtning får man objektet med högst prioritet.

En abstrakt prioritetskö kan ha föjande anrop.

put(p,x)
Stoppa in x med påstämplad prioritet p (oftast ett heltal).
x=get()
Hämta det som har högst prioritet.
isempty()
Undersök om prioritetskön är tom.
Om det man vill stoppar in i prioritetskön är ett tal kan man använda talet självt som prioritet och bara skriva put(x). Hur den då skiljer sej från en stack och från en vanlig kö ser man av följande exempel.
      pq.put(1)
      pq.put(3)
      pq.put(2)
      x = pq.get() # x blir 3 
En kö hade skickat tillbaka det först instoppade talet 1; en stack hade skickat tillbaka det senast instoppade talet, 2; prioritetskön skickar tillbaka det bästa talet, 3. I denna prioritetskö betraktar vi största talet som bäst - vi har en så kallad maxprioritetskö. Det finns förstås också minprioritetsköer, där det minsta talet betraktas som bäst.

Prioritetsköer har många användningar. Man kan tänka sej en auktion där budgivarna stoppar in sina bud i en maxprioritetskö och auktionsförrättaren efter "första, andra, tredje" gör pq.get() för att få reda på det vinnande budet. För att han ska veta vem som lagt detta bud behövs förstås båda parametrarna i pq.put(p,x).

      pq.put(bud,person)   #person är ett objekt med budgivarens namn och bud
      winner = pq.get()    #budgivaren  med högst bud 











Trappa

Den bästa implementationen av en prioritetskö är en trappa, (eng heap), som är en array tab tolkad som binärträd. HEAP Roten är tab[1], dess båda barn är tab[2] och tab[3] osv. Allmänt gäller att tab[i] har barnen tab[2*i] och tab[2*i+1]. Trappvillkoret är att föräldern är bäst, dvs varje tal ligger på två sämre tal.

Ett nytt tal läggs alltid in sist i trappan. Om trappvillkoret inte blir uppfyllt, dvs om det är större än sin förälder, byter förälder och barn plats och så fortgår det tills villkoret uppfyllts. Det här kallas upptrappning och kan i värsta fall föra det nya talet hela vägen upp till toppen, alltså tab[1].

Man plockar alltid ut det översta talet ur trappan och fyller igen tomrummet med det sista talet i trappan. Då är inte trappvillkoret uppfyllt, så man får byta talet och dess största barn. Denna nedtrappning upprepas till villkoret åter gäller.

Både put och get har komplexitet log N om trappan har N element. Nackdelen med trappan är man måste bestämma arrayens storlek från början.

Här följer en rudimentär implementation av en max-heap:


class HeapNode:

    def __init__(self, value, priority):
        self.value=value
        self.priority=priority

class Heap:
    # En max-heap

    def __init__(self, maxsize):
        self.maxsize=maxsize
        self.tab=maxsize*[None]
        self.size=0

    def isempty(self):
        return self.size==0

    def isfull(self):
        return self.size==self.maxsize

    def put(self, value, priority):
        if not self.isfull():
            self.size+=1
            self.tab[self.size]=HeapNode(value, priority)
            i=self.size
            while i>1 and self.tab[i//2].priority < self.tab[i].priority:
                self.swap(i//2, i)
                i=i//2






    def get(self):
        if not self.isempty():
            data=self.tab[1]
            self.tab[1]=self.tab[self.size]
            self.size-=1
            i=1
            while i<=self.size//2:
                mx=self.maxindex(i)
                if self.tab[i].priority < self.tab[mx].priority:
                    self.swap(i, mx)
                i=mx
            return data.value
        else:
            return None

    def maxindex(self, i):
        if 2*i+1>self.size: return 2*i
        if self.tab[2*i] > self.tab[2*i+1]: return 2*i
        else: return 2*i+1

    def swap(self, a, b):
        temp=self.tab[a]
        self.tab[a]=self.tab[b]
        self.tab[b]=temp


Heapsort

Om man stoppar in N tal i en trappa och sedan hämtar ut dom ett efter ett får man dom sorterade. Komplexiteten för denna heapsort blir O(N log N), alltså av lika god storleksordning som quicksort. Visserligen är quicksort lite snabbare, men heapsort har inte quicksorts dåliga värstafallsbeteende. och så kan ju en heap användas till andra saker än sortering också.
"Heapsort av orden på en fil"

  heap = Heap(16)               
  for ord in open("bibel.txt").read().split():
      heap.put(ord, ord)
  while not heap.isempty():
      print heap.get()   

Bästaförstsökning

Labb 4 behandlar problemet att finna kortaste vägen från FAN till GUD. Man har då ett problemträd med FAN som stamfar/urmoder, på nivån därunder barnen MAN, FIN, FAT osv, på nästa nivå fans barnbarn osv. Om man lägger barnen i en kö kommer man att gå igenom problemträdet nivå för nivå, alltså breddenförst. Om man byter kön mot en stack blir sökningen djupetförst. Med en prioritetskö får man bästaförstsökning, dvs det mest lovande barnet prioriteras och får föda barn.

Det här är exempel på en girig algoritm, där man i varje steg väljer den väg som verkar bäst för stunden, utan att reflektera över vad konsekvenserna blir i längden. Ofta ger giriga algoritmer inte den bästa lösningen, men i just det här fallet fungerar det faktiskt! Algoritmen kallas Dijkstra's algoritm (efter den holländske datalogen Edsger W. Dijkstra) och att den fungerar bevisas i fortsättningskursen 2D1352 Algoritmer, datastrukturer och komplexitet.

Exempel 1: Sök billigaste transport från Teknis till Honolulu. All världens resprislistor finns tillgängliga.
Problemträdets poster innehåller en plats, ett pris och en förälderpekare. Överst i trädet står Teknis med priset noll. Barnen är alla platser man kan komma till med en transport och priset, till exempel T-centralen, 20.00. Man söker en Honolulupost i problemträdet. Med breddenförstsökning får man den resa som har så få transportsteg som möjligt. Med bästaförstsökning får man den billigaste resan. Detta påstående är inte helt självklart utan man får tänka en del för att inse det.



Exempel 2: Sök effektivaste processen för att framställa en önskad substans från en given substans. All världens kemiska reaktioner finns tillgängliga med uppgift om utbytet i procent.
Problemträdets poster innehåller substansnamn och procenttal. Överst i trädet står utgångssubstansen med procenttalet 100. Barnen är alla substanser man kan framställa med en reaktion och utbytet, till exempel C2H5OH, 96%. Med en max-prioritetskö får man fram den effektivaste process som leder till målet.


Siluettproblemet

Om man på håll betraktar en stad i skymningen ser man inte dom enskilda byggnaderna utan bara siluetten, den yttersta konturen, avteckna sig mot himlen. Hitta på en algoritm som givet varje byggnads kontur beräknar stadssiluetten.

Anta att alla byggnader står på x-axeln, är rektangulära och beskrivs av tripplar (left, height, right) där left är vänsterväggens x-koordinat, right är högerväggens x-koordinat och height är höjden (y-koordinat).

Inmatningen består av n stycken tripplar, ordnade i stigande värden på vänsterväggar, och utmatningen ska vara en rad med x,y-koordinater som från vänster till höger beskriver siluetten. x-värdena anger var på x-axeln siluetten går vertikalt och y anger på vilken höjd siluetten fortsätter efter x-värdet. Den sista y-koordinaten är alltid noll eftersom alla byggnader står på x-axeln.

Exempel: Om n=8 och inmatningen är
(1,11,5) (2,6,7) (3,13,9) (12,7,16) (14,3,25) (19,18,22) (23,13,29) (24,4,28)
så blir utmatningen

     x= 1  y= 11
     x= 3  y= 13
     x= 9  y= 0
     x= 12 y= 7
     x= 16 y= 3
     x= 19 y= 18
     x= 22 y= 3
     x= 23 y= 13
     x= 29 y= 0
Det här knepiga problemet kan vi lösa med hjälp av två heapar. Den ena är en min-heap, som ser till att vi får ut husväggarnas koordinater i ordning från minsta (vänstraste) till största (högraste) x-värde. Den andra är en max-heap som hjälper oss att hitta det just nu högsta huset.

Algoritm:

  • Låt x och y vara aktuell x-koordinat resp y-koordinat. Båda är noll från början.
  • Lägg in alla hus, med vänstervägg som nyckel, i en min-heap som vi kallar xheap.
  • Skapa också en tom max-heap, som vi kallar för yheap, där vi (så småningom) ska sortera husen efter höjd.
  • Så länge det finns hus kvar i xheap,
    • Ta ut första huset ur xheap (vi får det hus som har den vänstraste vänsterväggen).
    • Om vänsterväggen ligger efter aktuell x-koordinat...
      • ...så blir den ny x-koordinat.
      • Om dessutom huset höjd är högre än aktuell y-koordinat skriver vi ut x och y.
      • Stoppa in huset i xheap igen, men nu med högerväggen som nyckel.
      • Stoppa in samma hus i yheap med höjden som nyckel.
    • men om vänsterväggen inte ligger efter aktuell x-koordinat har vi fått ut ett hus med högervägg som nyckel, och då...
      • ...letar vi igenom yheap efter det högsta huset vars högervägg inte ligger efter aktuell x-koordinat.
      • Om yheap tar slut medan vi letar är höjden noll.
      • Sen skriver vi ut x och y.







Programkod

from maxheap import MaxHeap
from minheap import MinHeap

class House:
    left=None
    height=None
    right=None

    def __init__(self, left, height, right):
        self.left=left
        self.height=height
        self.right=right

def readHousesTo(xheap):
    ins=raw_input("Vilka hus finns? ").split()
    for h in ins:
        h=h.strip("()")
        hs=h.split(",")
        house=House(int(hs[0]), int(hs[1]), int(hs[2]))
        print house
        xheap.put(house, house.left)

xheap = MinHeap(20)
yheap = MaxHeap(20)

readHousesTo(xheap) # Läs in husen

x=0
y=0
while not xheap.isempty():
    house = xheap.get()
    if house.left > x:  # Hus med vänstervägg utplockat
        x=house.left
        if house.height > y:
            y=house.height
            print "x=", x, "y=", y
        xheap.put(House(house.left, house.height, house.right), house.right)
        yheap.put(House(house.left, house.height, house.right), house.height)

    else: # Hus med högervägg utplockat
        if house.height == y: # Det var det just nu högsta huset
            # Sök efter det hus som blir högst nu
            x=house.right
            while not yheap.isempty() and yheap.top().right <= x:
                yheap.get()     # Ta bort hus som ligger...
                                # ...till vänster om x
            if yheap.isempty():
                y=0
            else:
                y=yheap.top().height
            print "x=", x, "y=", y


Sidansvarig: Magnus Rosell <rosell@csc.kth.se>
Uppdaterad 2006-09-28