Løsningsforslag - oppgaver i Avsnitt 11.2.5


Oppgave 1

Uansett hvilken startnode man velger, vil det minimale spenntreet man får alltid inneholde kantene A - B, B - D, C - D og D - F. Men i tilfellene det er valg siden det er to noder som begge ligger nærmest treet man til da har fått, vil treet være avhengig av hva man velger. Mulighetene er da C - F og F - G, D - F og F - G eller E - G og F - G.

Oppgave 2

Det vil bli kastet en NullPointerException. For-løkken i metoden spenntrePrim0() går til at alle nodene er innlemmet i treet. Men siden det ikke er noen kant til H, vil etterhvert alle kanter som er lagt inn, bli tatt ut av køen. Når køen er tom, returnerer metoden poll() null. Dermed kommer det en NullPointerException i neste setning. Det vi derfor kan gjøre er å legge inn flg. setning rett etter poll()-setningen:

  if (kant == null) throw new NullPointerException("Treet er usammenhengende!");

En annen mulighet hadde vært å bruke remove() istedenfor poll(). De gjør det samme. Men remove() kaster en NoSuchElementException hvis køen er tom. Men det er kanskje ingen god feilmelding å få. Den sier ikke hva som er årsaken, dvs. at grafen er usammenhengende.

Oppgave 3 a)

Grafer
Et minimalt spenntre for grafen i Figur 11.2.5 i)

Oppgave 3 b)

Klassen Kant inneholder kun noden som kanten går til. Men vi trenger også noden som kanten går fra. Men den mister vi når kanten legges inn i køen. En løsning er å bruke en lokal hjelpeklasse UtvidetKant som inneholder begge nodene og vekten. I tillegg skal den ha en «teller» som øker med 1 for hver innlegging. Dermed kan vi lage komparatoren for prioriteskøen slik at med lik vekt er det den med seneste tellerverdi som tas ut.

  public List<String> spenntrePrim0(String nodenavn)
  {
    Node node = noder.get(nodenavn); // henter startnoden
    if (node == null) throw new NoSuchElementException(nodenavn + " er ukjent!");

    record UtvidetKant(Node fra, Node til, int vekt, int nr)
    {
      public String toString()
      {
        return "(" + fra.navn + "," + til.navn + "," + vekt + ")";
      }
    }

    List<String> liste = new LinkedList<>();

    PriorityQueue<UtvidetKant> kø =
      new PriorityQueue<>((a,b) ->
        a.vekt - b.vekt != 0 ? a.vekt - b.vekt : b.nr - a.nr);

    int nr = 0;
    kø.offer(new UtvidetKant(node, node, 0, nr));  // 0-kant fra noden til seg selv

    int vekt = 0;  // hjelpevariabel for summering

    for (int antallnoder = 0; antallnoder < noder.size(); )
    {
      UtvidetKant kant = kø.poll();   // tar ut kanten med minst vekt
      if (kant == null) throw new NullPointerException("Treet er usammenhengende!");

      Node nynode = kant.til;        // noden som kanten går til

      if (!nynode.ferdig)            // en ferdig node ligger allerede i treet
      {
        liste.add(kant.toString());  // legger kanten i listen

        vekt += kant.vekt;           // øker vekten
        nynode.ferdig = true;        // innlemmes i treet
        antallnoder++;               // en node til i treet

        for (Kant k : nynode.kanter)
          if (!k.til.ferdig)
            kø.offer(new UtvidetKant(nynode, k.til, k.vekt, nr++));
      }
    }
    liste.add("Vekt: " + vekt);

    liste.remove(0);  // fjerner den første 0-kanten
    return liste;     // returnerer en liste med spenntreets kanter
  }

Flg. program tester metoden:

  String url = "https://www.cs.hioa.no/~ulfu/appolonius/kap11/2/vgraf2.txt";
  VGraf vgraf = new VGraf(url);
  System.out.println(vgraf.penntrePrim0("C"));
  // Utskrift: [(C,D,2), (D,E,2), (D,B,2), (B,A,2), (E,G,3), (G,F,2), Vekt: 13]

Oppgave 4

  public boolean erUrettet()
  {
    for (Node node : noder.values())
    {
      for (Kant kant : node.kanter)
      {
        Node til = kant.til;

        boolean funnet = false;

        for (Kant k : til.kanter)
        {
          if (k.til == node && k.vekt == kant.vekt)
          {
            funnet = true;
            break;
          }
        }
        if (!funnet) return false;
      }
    }
    return true;
  }

Oppgave 5

Hvis Programkode 11.2.5 c) brukes på en ikke sammenhengende graf, vil vi få returnert vekten til et minimalt spenntre for den delen av grafen som nås fra startnoden. Det kommer ingen feilmeldinger.

Oppgave 6

Legg f.eks.

  if (noder.size() == 1)
  {
    StringJoiner sj = new StringJoiner(",", "(", ")");
    String node = nodenavn()[0];
    sj.add(node).add(node).add("0");
    spenntre.add(sj.toString());
    return spenntre;
  }

mellom setningene

  List<String> spenntre = new ArrayList<>();  // for kantene i spenntreet

og

  for (Node n : noder.values())  // ser på alle nodene

Oppgave 7

  public VGraf spenntrePrimGraf(String nodenavn)
  {
    spenntrePrim(nodenavn);  // finner et minimalt spenntre

    VGraf spenntre = new VGraf();  // ny og tom graf

    for (Node node : noder.values())
    {
      spenntre.leggInnNode(node.navn);  // legger alle nodenavnene inn i ny graf
    }

    if (noder.size() < 2) return spenntre;

    for (Node node : noder.values())
    {
      if (node.forrige != null)
      {
        // legger inn kantene i spenntreet i den nye grafen
        spenntre.leggInnKant(node.navn, node.forrige.navn, node.avstand);
        spenntre.leggInnKant(node.forrige.navn, node.navn, node.avstand);
      }
    }

    nullstill();  // nullstiller variablene forrige og avstand

    return spenntre;  // returnerer grafen
  }

Her er et eksempel på hvordan metoden kan brukes:

  String url = "https://www.cs.hioa.no/~ulfu/appolonius/kap11/2/vgraf2.txt";
  VGraf vgraf = new VGraf(url);

  VGraf tre = vgraf.spenntrePrimGraf("C");

  for (String node : tre)
  {
    System.out.println(node + " -> " + tre.kanterFra(node) + " ");
  }

  // Utskrift
  // A -> (B,2) 
  // B -> (A,2), (D,2) 
  // C -> (D,2) 
  // D -> (B,2), (C,2), (E,2) 
  // E -> (D,2), (G,3) 
  // F -> (G,2) 
  // G -> (F,2), (E,3) 

Oppgave 8

  public int spenntrePrim(String nodenavn)  // Prims algoritme
  {
    int i = finn(nodenavn);  // indeks i den sorterte navnetabellen
    if (i < 0) throw new NoSuchElementException(nodenavn + " er ukjent!");
    i = indeks[i];           // indeks i den usorterte navnetabellen   

    class Avstand  // lokal hjelpeklasse
    {
      int node, avstand;

      Avstand(int node, int avstand)
      {
        this.node = node; this.avstand = avstand;
      }
    }

    Queue<Avstand> kø = new PriorityQueue<>((a,b) -> a.avstand - b.avstand);

    avstand = new int[antall];           // oppretter tabellen
    Arrays.fill(avstand, 0x7fffffff);    // setter verdiene til "uendelig"

    forrige = new int[antall];           // oppretter tabellen
    Arrays.fill(forrige, -1);            // setter verdiene til -1

    boolean[] ferdig = new boolean[antall];  // lokal hjelpetabell

    avstand[i] = 0;                      // avstand i startnoden settes til 0
    kø.offer(new Avstand(i, 0));         // startnoden legges i køen

    int vektsum = 0;                     // hjelpevariabel for treets vekt

    while (!kø.isEmpty())                // så lenge køen ikke er tom
    {
      Avstand minst = kø.poll();         // den "minste" noden
      int denne = minst.node;            // hjelpevariabel

      if (ferdig[denne]) continue;       // denne er vi allerede ferdig med

      for (int neste = 0; neste < antall; neste++)  // går gjennom matriseraden
      {
        if (graf[denne][neste] != IKKE_KANT) // en kant fra denne til neste
        {
          if (ferdig[neste]) continue;   // denne er vi allerede ferdig med

          int vekt = graf[denne][neste]; // hjelpevariabel

          if (vekt < avstand[neste])     // sammenligner
          {
            avstand[neste] =  vekt;
            kø.offer(new Avstand(neste, avstand[neste]));
            forrige[neste] = denne;      // veien går fra denne til neste
          }
        }
      } // for

      ferdig[denne] = true;           // nå er denne er ferdig
      vektsum += avstand[denne];      // legger til kantvekten

    } // while

    return vektsum;                   // spenntreets vekt
  }

  public VMGraf spenntrePrimGraf()
  {
    VMGraf spenntre = new VMGraf();

    for (int i = 0; i < antall; i++)
    {
      spenntre.leggInnNode(snavn[i]);
    }

    if (antall < 2) return spenntre;

    for (int i = 0; i < antall; i++)
    {
      if (forrige[i] != -1)
      {
      spenntre.leggInnKant(navn[i], navn[forrige[i]], avstand[i]);
      spenntre.leggInnKant(navn[forrige[i]], navn[i], avstand[i]);
      }
    }

    return spenntre;
  }

  public List<String> spenntrePrim()
  {
    List<String> tre = new LinkedList<>();

    if (antall == 1)
    {
      StringJoiner sj = new StringJoiner(",", "(", ")");
      sj.add(navn[0]).add(navn[0]).add("0");
      tre.add(sj.toString());
      return tre;
    }

    for (int i = 0; i < antall; i++)
    {
      if (forrige[i] != -1)
      {
        StringJoiner sj = new StringJoiner(",", "(", ")");
        sj.add(navn[i]).add(navn[forrige[i]]).add(Integer.toString(avstand[i]));
        tre.add(sj.toString());
      }
    }

    return tre;
  }