Algoritmer og datastrukturer
Løsningsforslag

Eksamen  −  30. november 2010

Oppgave 1A

Et turneringstre for en utslagsturnering med n deltagere blir et komplett binærtre med 2n − 1 noder. I vårt tilfelle får treet 2·12 − 1 = 23 noder. Verdiene (eller deltagerne) legges inn nederst i treet. Vi tenker oss at nodene har nummer fra 1 til 23 i nivåorden. Det betyr at de 12 verdiene legges inn på de 12 siste nodene slik at første verdi legges i node 12, andre verdi i node 13, osv. Det svarer til flg. kode når dette implementeres:

  int[] deltagere = {6,5,8,7,9,4,9,5,4,10,5,8};
  int n = deltagere.length;
  int[] turnering = new int[2*n];  // posisjon 0 brukes ikke
  System.arraycopy(verdi,0,turnering,n,n);  // kopieres inn bakerst

  // Nå ser turneringstabellen slik ut:
  // {0,0,0,0,0,0,0,0,0,0,0,0,6,5,8,7,9,4,9,5,4,10,5,8}

  // Turneringen gjennomføres slik:
  for (int k = 2*n - 2; k > 1; k -= 2)
    turnering[k/2] = Math.max(turnering[k],turnering[k+1]);

  // Nå ser turneringstabellen slik ut (posisjon 0 brukes ikke):
  // {0,10,10,8,9,10,6,8,9,9,10,8,6,5,8,7,9,4,9,5,4,10,5,8}

OBS: Oppgaven gikk kun ut på å tegne turneringstreet og finne de verdiene som vinneren slo ut. Det skulle ikke lages noen kode.

Vinneren 10 har (fra første til siste runde) slått ut 4, 8, 9, 8.

Oppgave 1B

I setningen kø.leggInn(kø.taUt()) tas den første i køen ut og legges så inn i køen bakerst. Køen starter med 3, 1, 4, 2 og dermed blir det så 1, 4, 2, 3. Dette gjentas og tilsammen blir setningen utført 10 ganger. For hver 4. gang blir køen slik den opprinnelig var. Det ender med at køen til slutt inneholder 4, 2, 3, 1. Utskriften blir 4 2 3 1.

Oppgave 1C

Vi kan få kort kode hvis vi bruker en hjelpetabell og en metode fra klassen Arrays:

  public static <T> String toString(Kø<T> kø)
  {
    T[] a = (T[])new Object[kø.antall()];
    for (int i = 0; i < a.length; i++) a[i] = kø.taUt();
    for (T t : a) kø.leggInn(t);  // legger tilbake i køen
    return Arrays.toString(a);
  }

Det stod i oppgaven at det skulle brukes minst mulig ekstra «resurser». Metoden over vil derfor ikke gi fullt hus i en besvarelse siden det egentlig er helt unødvendig å bruke en hjelpetabell. Neste versjon er bedre:

  public static <T> String toString(Kø<T> kø)
  {
    StringBuilder s = new StringBuilder();
    s.append('[');

    int n = kø.antall();
    if (n > 0)
    {
      s.append(kø.kikk());
      kø.leggInn(kø.taUt());
    }

    for (int i = 1; i < n; i++)
    {
      s.append(',');
      s.append(' ');
      s.append(kø.kikk());
      kø.leggInn(kø.taUt());
    }

    s.append(']');

    return s.toString();
  }

Oppgave 2A

  public static int[] utenDuplikater(int[] a)
  {
    if (a.length < 2) return a;

    Arrays.sort(a);
    int antall = 1;

    for (int i = 1; i < a.length; i++)
    {
      if (a[i-1] < a[i]) a[antall++] = a[i];
    }

    return Arrays.copyOf(a,antall);
  }

Oppgave 2B


Oppgave 3A

Preorden: 7, 2, 5, 4, 6, 5, 11, 10, 8, 7, 9, 15, 12

Oppgave 3B

  public T rotverdi()
  {
    if (tom()) throw new NoSuchElementException("Treet er tomt!");
    return rot.verdi;
  }

Oppgave 3C

Her er en 1. versjon av dybde-metoden:

  private int dybde(Node<T> p)
  {
    int dybde = 0;
    Node<T> q = rot;

    while (p != q)
    {
      if (comp.compare(p.verdi,q.verdi) < 0) q = q.venstre;
      else q = q.høyre;

      dybde++;
    }

    return dybde;
  }

og en 2. versjon:

  private int dybde(Node<T> p)
  {
    Node<T> q = rot;
    int dybde = 0;

    while (true)
    {
      if (comp.compare(p.verdi,q.verdi) < 0)
        q = q.venstre;
      else if (p == q) return dybde;
      else q = q.høyre;

      dybde++;
    }
  }

Begge versjonene av dybde-metoden vil virke og vil gi fullt hus i en besvarelse. Men er de like gode? I den 1. versjonen testes det først om q er lik p. Den testen utføres hver gang. Men for et gjennomsnittlig binært søketre vil sannsynligheten være nærmere 50% for at p ligger til venstre for q. Det tas det hensyn til i den 2. versjonen. Der vil i nær 50% av tilfellene kun den første testen utføres. Det betyr i gjennomsnitt 1,5 sammenligninger per iterasjon, mens det i 1. versjon blir alltid 2 sammenligninger i hver iterasjon. Med andre ord utføres det færre sammenligninger i den 2. versjonen.

Noen lager kode der en ikke tar hensyn til at treet kan inneholde dulikater. Det blir det trukket en del for!

Oppgave 3D

Noen lager kode som om hver node har en forelderpeker. En slik peker er det ikke i denne oppgaven og slik kode er derfor verdiløs!

  private Node<T> nestePreorden(Node<T> p)
  {
    if (p.venstre != null) return p.venstre;
    else if (p.høyre != null) return p.høyre;
    else // p er en bladnode
    {
      // Må finne den nærmeste noden q oppover slik at p ligger
      // i det venstre subtreet til q samtidig som q.høyre er
      // forskjellig fra null. Da vil q.høyre være den neste i
      // preorden. Hvis ikke er den neste lik null. Letingen
      // må starte i roten:

      Node<T> q = rot;   // starter i roten
      Node<T> r = null;  // den neste i preorden

      while (true)
      {
        if (comp.compare(p.verdi,q.verdi) < 0)
        {
          if (q.høyre != null) r = q.høyre;
          q = q.venstre;
        }
        else if (p == q) return r;
        else q = q.høyre;
      }
    }
  }

Det står i oppgaveteksten at metoden traverserPreorden skal kodes ved hjelp av metoden nestePreorden. Derfor vil det ikke bli gitt poeng for den vanlige rekursive metoden som traverserer i preorden.

  public void traverserPreorden(Oppgave<? super T> oppgave)
  {
    Node<T> p = rot;
    while (p != null)
    {
      oppgave.utførOppgave(p.verdi);
      p = nestePreorden(p);
    }
  }

Oppgave 3E

i) Noden med verdien 2 er en forgjenger til noden med verdien 6. Dermed er avstanden mellom dem lik lengden på veien fra den første til den andre, dvs. at avstanden er 2. Noden med verdien 9 og noden med verdien 12 hra noden med verdien 11 som felles nærmeste forgjenger. Avstanden mellom dem blir derfor lik 3 + 2 = 5.

ii) En mulig løsning er å gå fra roten og ned til nodene x og y og samtidig registrere veien. Det kan gjøres ved å notere hvilke noder som ble passert eller f.eks. ved hjelp av 0 (for venstre) og 1 (for høyre). Siden dette må gjøres for hver av nodene, kan det kanskje være lurt med en hjelpemetode.

Flg. metode legger nodene på veien fra roten og ned til noden p inn i en liste:

  private Liste<Node<T>> vei(Node p)
  {
    Liste<Node<T>> v = new TabellListe<Node<T>>();
    Node<T> q = rot;

    while (true)
    {
      v.leggInn(q);
      if (comp.compare(p.verdi,q.verdi) < 0) q = q.venstre;
      else if (p == q) return v;
      else q = q.høyre;
    }
  }

Nærmeste forgjenger til nodene x og y finner vi der veiene skilles:

 private int avstand(Node x, Node y)
  {
    Liste<Node<T>> xvei = vei(x);
    Liste<Node<T>> yvei = vei(y);

    int n = 0;
    while (n < xvei.antall() && n < yvei.antall())
    {
      if (xvei.hent(n) != yvei.hent(n)) break;
      n++;
    }

    return xvei.antall() + yvei.antall() - 2*n;
  }

I løsningen over starter vi i roten to ganger. En mulig forbedring kunne være å gå fra roten og ned til nodenes nærmeste forgjenger z ved hjelp av den vanlige ordningen i et binært søketre. Deretter kunne vi finne avstanden fra z til x og deretter avstanden fra z til y. Her må vi imidlertid være ekstra nøyaktige siden duplikater er tillatt. Det kan med andre ord være flere noder som har samme verdi som x og flere som har samme verdi som y.

Først, hvis verdien til x ikke er mindre enn eller lik verdien til y, lar vi de to bytte plass. La så z være rotnoden. Hvis verdien til y nå er mindre enn verdien til z, må både x og y ligge til venstre for z. Hvis ikke, dvs. hvis verdien til y er større enn eller lik verdien til z, så er enten z nærmeste felles forgjenger for x og y eller så må både x og y ligge til høyre for z. Her må vi passe på spesialtilfellene at x er lik z eller at y er lik z:

  private int avstand(Node<T> x, Node<T> y)
  {
    if (comp.compare(y.verdi,x.verdi) < 0)
    {
      Node<T> temp = x; x = y; y = temp;
    }

    // Vet nå at verdien til x er <= verdien til y

    Node<T> z = rot;

    while (true)
    {
      if (comp.compare(y.verdi,z.verdi) < 0) z = z.venstre;
      else if (comp.compare(x.verdi,z.verdi) < 0 || x == z || y == z) break;
      else z = z.høyre;
    }

    // Nå er z nærmeste felles forgjenger for x og y. Spesielt har vi at
    // hvis x er en forgjenger til y, så er z lik x og omvendt hvis y er
    // en forgjenger til x, så er z lik y.

    int avstand = 0;

    // går først fra z til x

    Node<T> p = z;
    while (true)
    {
      if (comp.compare(x.verdi,p.verdi) < 0) p = p.venstre;
      else if (p == x) break;
      else p = p.høyre;

      avstand++;
    }

    // går så fra z til y

    p = z;
    while (true)
    {
      if (comp.compare(y.verdi,p.verdi) < 0) p = p.venstre;
      else if (p == y) break;
      else p = p.høyre;

      avstand++;
    }

    return avstand;
  }