Metody programowania / Ćwiczenia 3

From Studia Informatyczne

To są zadania na drzewa.

Oglądaj wskazówki i rozwiązania
Ukryj wskazówki i rozwiązania

Przyjmujemy następujące definicje:

type el_drzewa = record
                   w: integer;
                   lsyn, psyn: ^el_drzewa;
                 end;
        drzewo = ^el_drzewa;


Spis treści

Zadanie 1 (Liczba liści w drzewie)

Napisz funkcję która oblicza liczbę liści w drzewie.

Rozwiązanie 1

function LiczbaLiści(d: drzewo): integer;
//Liczymy liczbe liści w drzewie
begin
  if d = nil then LiczbaLiści:=0
  else 
    if d^.lsyn=nil and d^.psyn=nil then LiczbaLiści:=1
    else  
      LiczbaLiści:=LiczbaLiści(d^.lsyn) + LiczbaLiści(d^.psyn); 
end;     

Koszt czasowy: liniowy ze względu na wielkość drzewa

Koszt pamięciowy: liniowy ze względu na wysokość drzewa

Omówienie: Koszt pamięciowy jest oczywiście związany z wielkością stosu wywołań rekurencyjnych

Wizualizacja

Zadanie 2 (Odbicie lustrzane)

Napisz procedurę przekształcającą zadane drzewo w jego odbicie lustrzane.

Rozwiązanie 1

procedure Lustro(d: drzewo);
//Przekształcamy d na swoje odbicie lustrzane
var pom: drzewo;
begin
  if d <> nil then begin
    Lustro(d^.lsyn);
    Lustro(d^.psyn);
    pom:=d^.lsyn;
    d^.lsyn:=d^.psyn;
    d^.psyn:=pom;
  end;
end;

Koszt czasowy: liniowy ze względu na wielkość drzewa

Koszt pamięciowy: liniowy ze względu na wysokość drzewa (rekurencja)

Wizualizacja

Zadanie 3 (Izomorfizm drzew)

Napisz funkcję sprawdzającą czy dwa zadane drzewa są izomorficzne (mają taki sam kształt).

Rozwiązanie 1

function Izomorficzne(d1,d2: drzewo): boolean;
//Sprawdzamy czy d1 i d2 mają taki sam kształt
var i: boolean;
begin
  if d1 = nil and d2 = nil then i:=true
  else 
    if d1 <> nil and d2 <> nil then begin
      i:=Izomorficzne(d1^.lsyn, d2^.lsyn);
      if i then i:=Izomorficzne(d1^.psyn, d2^.psyn);
    end
    else i:=false;
  Izomorficzne:=i;
end;

Koszt czasowy: liniowy ze względu na sumę wielkości drzew

Koszt pamięciowy: liniowy ze względu na minimum z wysokości drzew

Wizualizacja

Zadanie 4 (Lista liści)

Napisz funkcję wiążącą liście drzewa w listę (od lewej do prawej). Wynikiem funkcji ma być wskaźnik do utworzonej listy. Zakładamy, że każdy węzeł ma dodatkowe pole będące wskaźnikiem do węzła, czyli używamy typów

type węzeł = record
               w: integer;
               lsyn, psyn, nast: ^węzeł;
             end;
   drzewoL = ^węzeł;

Wskazówka 1

Najwygodniej zrobić to obchodząc drzewo od prawej strony w lewo.

Rozwiązanie 1

function ListaLiści(d: drzewoL): drzewoL;
//Wynikiem funkcji jest wskaźnik do listy liści (od prawej do lewej) w drzewie d
var l: drzewoL; //dotychczas obliczona lista liści
procedure Lista(d: drzewoL);   
//Jeśli węzeł d jest liściem to dopisujemy go z przodu listy l   
//l jest zmienną globalną dla procedury Lista
begin
  if d <> nil then 
    if d^.lsyn = nil and d^.psyn = nil then begin
      d^.nast:=l;
      l:=d;
    end
    else begin
      Lista(d^.psyn);
      Lista(d^.lsyn);
    end;
end;
begin //ListaLiści
  l:=nil;
  Lista(d);
  ListaLiści:=l;
end;

Koszt czasowy: liniowy ze względu na wielkość drzewa

Koszt pamięciowy: liniowy ze względu na wysokość drzewa (rekurencja)

Wizualizacja

Zadanie 5 (Szerokość drzewa)

Napisz funkcję obliczającą szerokość drzewa, czyli maksymalną liczbę wierzchołków na jednym poziomie.

Wskazówka 1

Najłatwiej to zrobić obchodząc drzewo wszerz; ale taniej jest gdy dla każdego poziomu będziemy pamiętać dotychczas odwiedzoną liczbę wierzchołków na tym poziomie. Do tego celu potrzebna jest lista.

Rozwiązanie 1

function Szerokość(d: drzewo): integer;
//Oblicza maksymalną liczbę wierzchołków na jednym poziomie drzewa d
var l, pom: lista;
    maks: integer;     
procedure Obejdź(d: drzewo; var l:lista);  
//Wskaźnik l odpowiada poziomowi na którym jest d; jeśli l = nil to trzeba listę poziomów wydłużyć  
begin
  if d <> nil then
    if l = nil then begin    
      new(l);
      l^.w:=0;
      l^.nast:=nil;
    end;
    l^.w:=l^.w+1;                                     //zwiększamy licznik liści na tym poziomie
    Obejdź(d^.lsyn, l^.nast);                         //obchodzimy lewe i prawe poddrzewa
    Obejdź(d^.psyn, l^.nast);
end;
begin //Szerokość
  l:=nil;
  Obejdź(d, l);
  maks:=0;                                             //szukamy maksimum na l i jednocześnie ją niszczymy
  while l <> nil do begin
    maks:=max(maks, l^.w);
    pom:=l;
    l:=l^.nast;
    dispose(pom);
  end;
  Szerokość:=maks;
end;

Koszt czasowy: liniowy ze względu na wielkość drzewa

Koszt pamięciowy: liniowy ze względu na wysokość drzewa

Zadanie 6 (Średnica drzewa)

Napisz funkcję obliczającą średnicę drzewa. Średnica to maksymalna odległość między dwoma wierzchołkami.

Wskazówka 1

Niech d to wierzchołek średnicy położony najbliżej korzenia. Wtedy średnica składa się z najdłuższej ścieżki w lewym i w prawym poddrzewie d.

Rozwiązanie 1

function Średnica(d: drzewo): integer;
//Obliczamy maksymalną odległość między dwoma wierzchołkami w d
var dl, maks: integer;
function Ścieżki(d:drzewo; var dl, maks:integer);
//Na dl i maks są obliczane odpowiednio długość najdłuższej ścieżki w d i maksymalna średnica w d
//Dla d = nil ustalamy dl na -1 żeby dobrze obliczać dl dla liści  
var dl1,dl2,m1,m2, pom: integer;
begin
  if d=nil then begin
    dl:=-1; 
    maks:= 0;
  end
  else begin
    Ścieżki(d^.lsyn, dl1, m1);
    Ścieżki(d^.psyn, dl2, m2);
    dl:=max(dl1,dl2)+1;
    pom:=max(m1,m2);
    maks:=max(pom, dl1+2+dl2);
  end; 
end;
begin   //Średnica
  Ścieżki(d, dl, maks);
  Średnica:=maks;
end;

Koszt czasowy: liniowy ze względu na wielkość drzewa

Koszt pamięciowy: liniowy ze względu na wysokość drzewa (rekurencja)

Zadanie 7 (Sprawdzanie czy drzewo jest BST)

Napisz funkcję sprawdzającą czy drzewo jest drzewem BST.

Wskazówka 1

Sprawdzenie, że w każdym wierzchołku d^.lsyn^.w < d^.w < d^.psyn^.w nie wystarcza. Trzeba dla każdego poddrzewa wiedzieć jaka jest maksymalna i minimalna wartość w tym poddrzewie.

Rozwiązanie 1

function CzyBST1(d: drzewo): boolean;
//Sprawdzamy czy d jest drzewem BST
var mala, duza: integer;
function Czy(d: drzewo; var mala,duza :integer): boolean;  
//Zakładamy, że d <> nil; Czy stwierdza czy d jest drzewem BST i jeśli tak to przy wyjściu z procedury mala i duza to najmniejsza i największa wartość w drzewie d
var m1,d1,m2,d2: integer;
    c1,c2, lewy, prawy: boolean;
begin
  if d^.lsyn <> nil then begin   //sprawdzenie lewego poddrzewa
    c1:=Czy(d^.lsyn, m1, d1);
    if c1 then begin
      lewy:=(d1 < d^.w);
      mala:=m1;
    end 
    else lewy:=false;
  end
  else begin
    lewy:=true;
    mala:=d^.w;
  end;
  if d^.psyn <> nil then begin   //analogiczne sprawdzenie prawego poddrzewa
    c2:=Czy(d^.psyn, m2, d2);
    if c2 then begin
      prawy:=(d^.w < m2);
      duza:=d2; 
    end
    else prawy:=false;
  end
  else begin
    prawy:=true;
    duza:=d^.w;
  end
  CzyBST1:=lewy and prawy;
end;
begin //CzyBST1
  if d = nil then CzyBST:=true;
  else CzyBST:=Czy(d, mala, duza);
end;

Koszt czasowy: liniowy ze względu na wielkość drzewa

Koszt pamięciowy: liniowy ze względu na wysokość drzewa


Wskazówka 2

Można też skorzystać z następującej własności drzew BST: w obiegu infiksowym wartości tworzą ciąg rosnący.

Rozwiązanie 2

function CzyBST2(d: drzewo): boolean;
//Sprawdzamy czy d jest drzewem BST
var ost: integer;
    pierwsza, dobrze: boolean;
procedure Obejdź(d: drzewo):   
//Obejdź obchodzi d infiksowo; dla kazdego węzła d, poównujemy d^.w z ostatnio odwiedzoną wartością
//obiegu infiksowego, którą mamy zapamiętaną w zmiennej globalnej ost.
//Wyjątkiem jest sytuacja gdy pierwsza=true, czyli gdy nie mamy jeszcze zapamiętanej żadnej wartości
//dobrze oznacza że dotychczas odwiedzone węzły tworzą ciąg rosnący
begin
  if dobrze and (d <> nil) then
   begin
       Obejdź(d^.lsyn);
       if pierwsza then 
        begin ost:=d^.w //pierwszy element może być dowolny
            pierwsza:=false
        end
       else dobrze:=dobrze and (ost < d^.w);
       if dobrze then begin //ucinamy rekursję tak wcześnie, jak się da! 
         ost:=d^.w;
         Obejdź(d^.psyn); 
       end;    
   end;
end;
begin //CzyBST2
  pierwsza:=true;
  dobrze:=true;
  Obejdź(d);
  CzyBST2:=dobrze;
end;

Koszt czasowy: liniowy ze względu na wielkość drzewa

Koszt pamięciowy: liniowy ze względu na wysokość drzewa