Metody programowania / Ćwiczenia 4

From Studia Informatyczne

To są zadania na kolejki i stosy.

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

Przyjmijmy następujące definicje, gdzie typ lista był zdefiniowany w Ćwiczeniach 2.

type stos = lista;
     kolejka = record 
                 pocz, kon : lista;
               end;

Dla stosów dostępne są następujące funkcje i procedury:

procedure Init(var s:stos);                 //tworzy pusty stos s
procedure Push(var s:stos; w:integer);      //wstawia w na wierzchołek stosu 
procedure Pop(var s:stos);                  //zdejmuje wierzchni element stosu
function Top(s:stos): integer;              //wyznacza wartość na wierzchołku stosu
function Empty(s:stos): boolean;            //sprawdza czy stos jest pusty
procedure Destroy(s:stos);                  //niszczy stos (zwalnia pamięć)

Dla kolejek dostępne są następujące funkcje i procedury:

procedure Init(var k:kolejka);                    //tworzy pustą kolejkę k
procedure PutLast(var k:kolejka; w:integer);      //wstawia w na koniec kolejki
procedure GetFirst(var k:kolejka; var w:integer); //usuwa pierwszy element kolejki i zapisuje na w
function Empty(k:kolejka): boolean;               //sprawdza czy kolejka jest pusta
procedure Destroy(k:kolejka);                     //niszczy kolejkę (zwalnia pamięć)


Spis treści

Zadanie 1 (Poprawność wyrażeń nawiasowych)

Napisz funkcję sprawdzającą czy zadane wyrażenie nawiasowe składające się z nawiasów okrągłych i kwadratowych jest poprawne. Zakładamy, że dana jest funkcja DajZnak:integer, która przyjmuje następujące wartości: -1 dla (, 1 dla ), -2 dla [, 2 dla ] i 0 na oznaczenie końca wyrażenia nawiasowego.

Rozwiązanie 1

function PoprawneNawiasy: boolean;
//Sprawdzamy czy wyrażenie nawiasowe otrzymane przez kolejne wartości funkcji DajZnak jest poprawne
var s: stos;
    ok: boolean;
begin
  Init(s);
  ok:=true;
  repeat
    z:=DajZnak;
    case z of
      -1: Push(s, -1);
      -2: Push(s, -2);
       1: if not Empty(s) then
            if Top(s) = -1 then Pop(s)
            else ok:=false
          else ok:=false;
       2: if not Empty(s) then
            if Top(s) = -2 then Pop(s)
            else ok:=false
          else ok:=false;
       0: if not Empty(s) then ok:=false;
    end {case}
  until (z = 0) or (not ok)
  PoprawneNawiasy:=ok;
  Destroy(s);
end;

Koszt czasowy: liniowy ze względu na długość wyrażenia nawiasowego

Koszt pamięciowy: liniowy ze względu na długość wyrażenia nawiasowego

Ćwiczenie 1

Czy dodanie następnych typów nawiasów istotnie zmieniłoby rozwiązanie ?

Ćwiczenie 2

Jak wygląda optymalne rozwiązanie dla jednego typu nawiasów ?

Zadanie 2 (Maksymalnie odległy porządek)

Napisz funkcję która dla danej tablicy A typu array[1..N] of integer obliczy maksymalne k, takie że k=j-i, 1 ≤ i ≤ j ≤ N, A[j] ≥ A[i].

Wskazówka 1

Jeśli po elemencie i indeksie j pojawi się element większy o indeksie j' większym od j, to element o indeksie j nie ma szans być rekordowym. Takim elementem nie ma się co przejmować, jako końcem potencjalnego rekordowego skoku.

Wskazówka 2

Przeglądając tablicę od tyłu wkładamy na stos kandydatów na bycie najlepszym j. Potem przeglądając tablicę od przodu wyliczamy k, zdejmując zużytych kandydatów na j ze stosu.

Rozwiązanie 1

function MaksOdległość(N:integer; A:array[1..N] of integer): integer;
//Obliczamy maksymalne k, takie że k=j-i, 1 <= i <= j <= N, A[j] >= A[i]
var s: stos;
    i,j,maks: integer;
begin
  Init(s);
  Push(s, A[N]);
  j:=N-1;
  while j >= 1 do  
   begin                        //wkładamy kandydatów na j na stos
    if A[j] > Top(s) then
      Push(s, A[j]);
      j:=j-1
   end;
  i:=1;
  maks:=0;
  while (i <= N) and (not Empty(s)) do     //dla każdego i szukamy najlepszego j; uaktualniamy maks
    if Top(s) >= A[i] then begin
      maks:=max(maks, Top(s)-i);
      Pop(s);
    end
    else 
      i:=i+1; 
  MaksOdległość:=maks;
  Destroy(s);
end;

Koszt czasowy: liniowy ze względu na długość tablicy

Koszt pamięciowy: liniowy ze względu na długość tablicy

Zadanie 3 (Najpłytszy liść w drzewie)

Napisz funkcję która dla danego drzewa (definicja typu w MP Ćwiczenia2) obliczy głębokość najpłytszego (najbliższego korzeniowi) liścia. Dla drzewa pustego wynikiem powinno być zero, a dla jednoelementowego jeden.

Wskazówka 1

Trzeba użyć przeszukiwania wszerz i kolejki w której trzymane będą pary składające się ze wskaźnika do węzła i głębokości węzła.

Rozwiązanie 1

type para = record
       drz: drzewo;
       poz: integer;
     end;

Typ kolejkaPar odpowiada kolejce w której trzymane są wartości typu para; poniżej jest on używany bez uprzedniej definicji.

function NajpłytszyLiść(d:drzewo): integer;
//Obliczamy głębokość najpłytszego liścia w drzewie d
var k:kolejkaPar;
    liść: integer;
    e,w: para;
begin
  if d = nil then NajpłytszyLiść:=0
  else begin
    Init(k);
    e.drz:=d;
    e.poz:=1;
    PutLast(k, e);
    liść:=0; 
    while (liść = 0) do begin                     //dopóki nie znaleźliśmy liścia kolejka nie może byc pusta
      GetFirst(k, w);
      l:=w.drz^.lsyn;
      p:=w.drz^.psyn;
      if l = nil and p = nil then liść:=w.poz
      else begin
        if l <> nil then begin                    //wstawiamy lewego syna do kolejki
          e.drz:=l
          e.poz:=w.poz+1;
          PutLast(k, e); 
        end;
        if p <> nil then begin                    //wstawiamy prawego syna do kolejki
          e.drz:=p
          e.poz:=w.poz+1;
          PutLast(k, e); 
        end;
    end;
    NajpłytszyLiść:=liść;
    Destroy(k);
  end;
end;

Koszt czasowy: liniowy ze względu na N

Koszt pamięciowy: liniowy ze względu na N

Zadanie 4 (Sortowanie liczb k-cyfrowych)

Zaimplementuj algorytm sortowania tablicy A typu array[1..N] of liczba dziesiętnych liczb k-cyfrowych przy użyciu tablicy dziesięciu kolejek. Typ liczba to array[1..k] of integer, najbardziej znaczący bit jest pod indeksem 1.

Wskazówka 1

Zastosujmy k-krotne sortowanie kubełkowe: przeglądamy ciąg liczb patrząc na i-tą (i=k..1) cyfrę i wkładamy liczbę do właściwej kolejki. Po przejrzeniu ciągu kontynuujemy ze zmniejszonym o jeden i.

Rozwiązanie 1

procedure Sortuj(N:integer; var A: array[1..N] of liczba);
//Sortujemy tablicę liczb k-cyfrowych 
var T: array[0..9] of kolejka;
    B: array[1..N] of liczba;
    i,j,maks: integer;
function i_taCyfra(x,i:Integer):Integer;
//wyznacza i-tą cyfrę liczby x
var j:Integer;
begin
 for j:=1 to i-1 do x:=x div 10; 
 //Tu zamiast pętli można by użyć dzielnika pobranego z tablicy potęg 10 raz ustawionej na początku działania programu
 i_taCyfra:=x mod 10
end; 
 
begin
  for c:=0 to 9 do Init(T[c]);
  for j:=1 to N do PutLast(T[i_taCyfra(A[j],1)], j);
  for c:=0 to 9 do PutLast(T[c], -1);
  //Liczby z A (a raczej ich indeksy w A) są umieszczone w kolejkach tablicy T względem najbardziej znaczącej cyfry
  //Na końcu każdej kolejki jest -1 żeby wiedzieć kiedy zmniejszyć i
  for i:=2 to k do 
    for c:=0 to 9 do begin
      repeat
        GetFirst(T[c], l);
        if l <> -1 then PutLast(T[i_taCyfra(l,i)], l);
      until l = -1;
      for c:=0 to 9 do PutLast(T[c], -1);
    end; 
  //W kolejkach w tablicy T liczby są już posortowane
  //Trzeba je w tej kolejności umieścić w A; używamy pomocniczej tablicy B   
  j:=1;
  for c:=0 to 9 do 
    repeat
      GetFirst(T[c], l);
      if l <> -1 then begin
        B[j]:=A[l];
        j:=j+1;
      end;
    until l = -1;
  for j:=1 to N do A[j]:=B[j];
  for c:=0 to 9 do Destroy(T[c]);   
end;

Koszt czasowy: rzędu N*k

Koszt pamięciowy: liniowy względem N

Uwaga: Koszt pamięciowy związany jest z zawartościa kolejek w T i z pomocniczą tablicą B (obie rzędu N).