Clasa a 7-a lecția 14 - 10 dec 2015

From Algopedia
Jump to navigationJump to search

Tema - rezolvări

Rezolvări aici [1]

Tema - rezolvări

Cuburi

Problema cuburi. Această problemă este relativ ușoară pentru clasa a 8-a. Necesită calcule triviale pentru aflarea numărului de piramide și a numărului de numere de afișat. Singurul punct care merită discutat este transformarea numerelor. Această problemă este una clasică în informatică: dat un număr cu n cifre să se elimine k dintre ele astfel încît numărul rămas să fie minim (sau maxim). Soluția, tot clasică, pentru n și k mari folosește un algoritm amortizat liniar și o coadă dublă. În cazul nostru, deoarece k este 3, putem implementa ceva mai simplu, tot liniar. Să vedem variantele posibile:

  1. Combinări: putem încerca exhaustiv să eliminăm toate combinările de 3 cifre. O soluție ineficientă, dar care funcționează pentru că numerele sînt mici. Puteţi implementa recursiv, sau folosind trei bucle for. Atenție la implementarea recursivă! Este bine să învățați pe din-afară implementarea combinărilor, nu este ceva ce vă iese din prima în timpul unui concurs.
  2. Numărul rămas trebuie să înceapă cu o cifră. Opțiunile sînt însă foarte limitate. El trebuie să înceapă cu una din primele patru cifre. Dacă ar începe cu a cincea, sau altă cifră mai departe, ar trebui să eliminăm minim patru cifre de la început. Aceasta ne sugerează algoritmul: alegem cifra minimă din prime patru cifre. Cifrele din-naintea ei se elimină. Știm acum cîte cifre mai avem de eliminat. Continuăm cu numărul care începe după cifra selectată ca prima cifră. Să presupunem că am eliminat o cifră, acum mai avem de eliminat două. Ne vom uita, deci, la următoarele trei cifre și vom selecta minima. Ne oprim dacă am eliminat trei cire, sau dacă numărul de cifre rămase este egal cu numărul de cifre care mai sînt de eliminat. Atenție la prima alegere de minim: prima oară nu avem voie să alegem o cifră zero.
  3. A treia variantă este algoritmul clasic, care depune în coadă minimele locale. Nu-l vom discuta aici, a fost discutat pe algopedia (problema maxim de la varena.ro).

Iată o implementare bazată pe a doua variantă:

#include <stdio.h>

// dat un numar nr returneaza numarul minim rezultat in urma taierii
// a trei cifre din nr; numarul minim nu are voie sa inceapa cu zero;
int minim( int nr ) {
  int n, pmin, i, rez, detaiat;
  int cifre[10];

  n = 0;
  while ( nr > 0 ) {
    cifre[n++] = nr % 10;
    nr /= 10;
  }
  // alegem prima cifra, este minimul din primele 4, dar nu zero
  pmin = n-1;
  for ( i = n-2; i >= n-4; i-- )
    if ( cifre[i] > 0 && cifre[i] < cifre[pmin] )
      pmin = i;
  rez = cifre[pmin];
  detaiat = 3 - ((n-1) - pmin);
  // continuam sa alegem minimul (inclusiv zero) din urmatoarele
  // detaiat+1 cifre
  while( detaiat > 0 && detaiat < pmin ) {
    pmin--;
    n = pmin;
    for ( i = n - 1; i >= n - detaiat; i-- )
      if ( cifre[i] < cifre[pmin] )
        pmin = i;
    rez = rez * 10 + cifre[pmin];
    detaiat -= n - pmin;
  }
  // daca am taiat toate cifrele pur si simplu copiem cifrele ramase
  if ( detaiat == 0 )
    for ( i = pmin - 1; i >= 0; i-- )
      rez = rez * 10 + cifre[i];

  return rez;
}

int main() {
  FILE *fin, *fout;
  int n, k, ncub, npir, sum, nr, nn, i;

  fout = fopen( "cuburi.out", "w" );
  fin = fopen( "cuburi.in", "r" );
  fscanf( fin, "%d%d", &n, &k );
  // calcul numar de piramide complete construibile cu n numere -> npir
  ncub = n / 6;
  npir = sum = 1;
  while ( sum <= ncub ) {
    npir++;
    sum += npir * npir;
  }
  npir--;
  fprintf( fout, "%d\n", npir );

  nn = k * (k + 1) * (2 * k + 1); // calcul numar de numere de afisat
  // afisare a primelor nn numere transformate
  for ( i = 0; i < nn; i++ ) {
    fscanf( fin, "%d", &nr );
    fprintf( fout, "%d ", minim( nr ) );
  }
  fprintf( fout, "\n" );
  fclose( fin );
  fclose( fout );

  return 0;
}

Maxim

Problema maxim se rezolvă folosind algoritmul maxim în fereastră glisantă.

  • Vom folosi o parcurgere în numărul nostru imaginar obţinut prin alipirea tuturor numerelor între a şi b.
  • Conform algoritmului maxim în fereastră glisantă vom memora o coadă cu maximele găsite în fereastra curentă.
  • Vom adăuga la coadă următoarea cifră din numărul x.
  • De scos vom scoate întotdeauna prima cifră din coadă, pe care o vom şi afişa.
  • Fereastra se va micşora pe măsură ce avansăm.
  • Cifrele din coadă reprezintă o comprimare a ferestrei curente, conţinînd doar elementele ce au şanse să fie maxime pe măsură ce fereastra glisează.
  • Să observăm că toate cifrele din această fereastră vor fi "aruncate" dacă fereastra a ajuns la capătul numărului. De aceea algoritmul rulează pînă ce am terminat cifrele din numărul x şi nu pînă ce
  • Pentru memorarea cozii în O(1) memorie vom observa faptul că ea este formată din cifre în ordine descrescătoare. Putem, deci, memora perechi (cifră, număr de apariţii. Deoarece cifrele sînt descrescătoare vom avea cel mult zece perechi.

Iată o implementare posibilă:

#include <stdio.h>

#define LCOADA 11

int a, b; // vom genera numerele intre a si b
int nrcf, cf[5]; // de aici extragem pe rind cifre, apoi trecem la urmatorul nr
int prim, ultim, // primul si ultimul element in coada, ultim e in afara cozii
  cifra[LCOADA], ncifra[LCOADA]; // coada de cifre, perechi (cifra, nr ori)

// extrage urmatoarea cifra a numarului a(a+1)(a+2)...(b-1)b
int urmatoareaCifra() {
  int ac; // avem nevoie ca a sa ramina intact, il tinem intr-o copie

  if ( nrcf == 0 ) { // nu mai avem cifre, reumplem cu cifrele lui a+1
    a++; // trecem la urmatorul numar
    ac = a;
    while ( ac > 0 ) { // descompunem numarul in cifre in cf[]
      cf[nrcf] = ac % 10;
      ac /= 10;
      nrcf++;
    }
  }
  if ( a > b ) // daca a a depasit b inseamna ca s-a terminat sirul
    return -1; // returnam -1 prin conventie, sa se stie ca nu mai avem cifre
  nrcf--; // la acest moment stim sigur ca avem cifre in vectorul de cifre
  return cf[nrcf]; // returnam prima cifra si decrementam lungimea vectorului
}

// adauga cifra c intr-o coada circulara de perechi (cifra, nr aparitii)
void adauga( int c ) {
  // aruncam elementele din coada mai mici ca cifra c
  while ( ultim != prim && c > cifra[(ultim - 1 + LCOADA) % LCOADA] )
    ultim = (ultim - 1 + LCOADA) % LCOADA;
  // daca ultima cifra din coada este egala cu c
  if ( ultim != prim && c == cifra[(ultim - 1 + LCOADA) % LCOADA] ) {
    ncifra[(ultim - 1 + LCOADA) % LCOADA]++; // inseram deasupra
  } else { // daca ultima cifra din coada este mai mare ca c
    cifra[ultim] = c; // inseram dupa ultima cifra
    ncifra[ultim] = 1;
    ultim = (ultim + 1) % LCOADA;
  }
}

// scoate prima cifra dintr-o coada circulara de perechi (cifra, nr aparitii)
int scoate() {
  int c;

  c = cifra[prim]; // vom returna prima cifra din coada
  ncifra[prim]--;  // reducem numarul ei de aparitii cu unu
  if ( ncifra[prim] == 0 ) // daca avem zero aparitii
    prim = (prim + 1) % LCOADA; // stergem pozitia avansind prim
  return c;
}

int main() {
  FILE *fin, *fout;
  int c, cif;

  fin = fopen( "maxim.in", "r" );
  fscanf( fin, "%d%d%d", &a, &b, &c );
  fclose( fin );

  a--;      // pornim cu a-1: cf[] este gol si variabila a va fi incrementata
  nrcf = 0; // lungimea cozii de cifre este initial 0
  prim = ultim = 0; // coada de maxime este initial goala
  // in prima faza adauga c cifre din numarul nostru compus x
  for ( ; c > 0; c-- )
    adauga( urmatoareaCifra() );
  // in faza doi extragem maximul din fereastra si adaugam inca o cifra
  fout = fopen( "maxim.out", "w" );
  while ( (cif = urmatoareaCifra()) >= 0 ) { // daca mai avem cifre de adaugat
    adauga( cif );         // adaugam acea cifra
    fputc( scoate() + '0', fout ); // eliminam si afisam prima cifra din coada
  }
  fputc( '\n', fout );
  fclose( fout );

  return 0;
}

Lecţie

Introducere în analiza sintactică

În această lecţie vom discuta un subiect care a fost mascota ONIgim 2014: analiza sintactică, denumită şi parsing. Ea face parte din domeniul mai larg al teoriei compilatoarelor. Teoria din spatele analizei sintactice se studiază în facultate, dar bazele ei pot fi ințelese odată ce stăpîniți recursivitatea.

Dacă se studiază abia în facultate, de întîlnim subiecte de gimnaziu care necesită parsing? Răspunsul este unul foarte interesant, în opinia mea. Este vorba de un fenomen întîlnit şi la olimpiadele de matematică şi de fizică. Unele subiecte de olimpiadă sînt fabricate în modul următor: creatorul problemei preia o problemă existentă de la clase mai mari, care foloseşte cunoştinţe de nivel superior. Apoi o modifică uşor şi, cunoscînd metoda avansată de rezolvare, găseşte o metodă "băbească" de rezolvare cu cunoştinţe de nivel mic. Apoi declară problema de clasa a 7a. Creatorului problemei îi este uşor să "găsească" acea demonstraţie, deoarece el sau ea este ghidat de soluţia de nivel superior, care este, de obicei, banală. Dar vouă, celor mici, nu vă este deloc uşor, căci problema este în fapt aproape imposibilă fără cunoştinţe avansate. De aceea consider că aceste probleme nu sînt corecte. Viaţa nu este întotdeauna corectă, se pare că nici măcar la vîrsta voastră.

Mai departe: de ce se întîmplă acest lucru? Dacă aceste probleme sînt de fapt banale odată ce ai cunoştinţe de nivel superior şi aproape imposibile dacă nu ai acele cunoştinţe, de ce creatorii de probleme simt nevoia să creeze astfel de probleme? Răspunsul, cred, constă în faptul că este greu să creezi probleme frumoase, bune, grele şi inedite folosind doar cunoştinţe de nivelul dorit. Este mult mai uşor să iei ceva clasic, pe care copilul nu îl ştie, şi să i-l pui în faţă.

Concluzia? Olimpicii de vîrf sînt siliţi să înveţe teorie în avans, iar eu sînt silit să o predau. Şi iată-mă predînd această lecţie :-)

Pentru a implementa orice problemă de parsing avem nevoie să știm două lucruri: gramatici și implementarea analizorului recursiv cu proceduri pe baza acestor gramatici. Să le luăm pe rînd.

Gramatici

Aşa cum gramatica limbii române descrie structura, organizarea limbii, o gramatică din teoria compilatoarelor descrie structura unui limbaj. Acel limbaj poate fi limbajul C, sau Pascal, dar poate fi şi mult mai simplu, ca limbajul parantezelor corect închise, sau limbajul tuturor expresiilor aritmetice. Analogia funcţionează mai departe: gramatica limbii române identifică părţile de propoziţie: subiect, predicat, atribut, complement. În informatică propoziţia este un şir din limbaj, de exemplu o expresie corectă, iar gramatica identifică structuri în acea expresie, cum ar fi operatori, operanzi, paranteze.

O gramatică constă din producţii de forma:

S ⇾ aAb

cu semnificaţia că S poate fi înlocuit cu grupul aAb.

Să luăm un exemplu:

Exemplul 1

Să considerăm limbajul:

Lab = { ab, aabb, aaabbb, aaaabbbb, ... }

respectiv limbajul tuturor şirurilor care au litere a la început şi litere b la final, în număr egal. O gramatică care descrie acest limbaj este:

S ⇾ aSb
S ⇾ λ

cu semnificaţia că λ este şirul vid. Cum putem produce un şir din limbaj folosind această gramatică? Prin înlocuiri succesive ale lui S cu una din părţile din dreapta ale producţiilor pînă ce obţinem şirul dorit. Să presupunem că vrem să generăm şirul aabb. Vom porni cu S şi vom înlocui succesiv astfel:

S ⇾ aSb ⇾ aaSbb ⇾ aabb

Literele mari din gramatică se numesc simboli neterminali, deoarece ele nu fac parte din şirurile generate. Înlocuirile se fac pînă ce şirul nu mai conţine nici un simbol neterminal. Să luăm un exemplu similar:

Exemplul 2

Fie limbajul şirurilor care conţin întîi litere a, apoi litere b în număr egal cu a, apoi litere c, apoi litere d în număr egal cu c:

Labcd = { ab, cd, abcd, aabb, aabbcd, abccdd, aaabbb, cccddd, ... }

O gramatică posibilă este:

S ⇾ XY
X ⇾ aXb | λ
Y ⇾ cYd | λ

Am introdus aici o prescurtare în notaţie: dacă X are două producţii, le vom grupa separînd părţile din dreapta cu bare verticale. Să exemplificăm producţia lui abccdd:

S ⇾ XY ⇾ aXbY ⇾ abY ⇾ abcYd ⇾ abccYdd ⇾ abccdd

Deoarece avem mai multe simboluri neterminale trebuie să specificăm cu ce simbol pornim în înlocuiri: simbolul S va fi denumit simbol de start.

Analizorul recursiv cu proceduri

Ne propunem, în primă fază, să răspundem la întrebarea "este şirul nostru corect?". Cu alte cuvinte, şirul de la intrare face parte din limbaj? Sau, mai concret, este o expresie corectă? Bazat pe gramatici putem implementa un analizor recursiv. Fiecare simbol neterminal (care se mai poate expanda, cele care încep cu litere mari) se înlocuiește cu o funcție C care "înghite" de la intrare o expresie. Ea verifică în același timp corectitudinea și se oprește la caracterul incorect, dacă găsește o greșeală.

Exemplul 1

Să luăm ca exemplu gramatica din Exemplul 1. Ne propunem să scriem o funcţie S() care verifică dacă şirul de la intrare se potriveşte cu ceea ce poate produce simbolul de start S. După verificare, cursorul din fişier va rămîne la primul caracter după şirul verificat. Funcţia va arăta astfel:

char first, eroare;

void S() {
  if ( first == 'a' ) {
    first = fgetc( fin );
    S();
    if ( first == 'b' )
      first = fgetc( fin );
    else
      eroare = 1;
  }
}

Funcţia S() decide care din cele două producţii trebuie urmate analizînd primul caracter de la intrare, memorat în variabila globală first. Dacă are caracterul a, îl consumă, apoi reapelează S(), urmărind producţia din gramatică. În final verifică existenţa literei b, caz în care o consumă. Dacă litera b nu apare, se setează variabila globală de eroare.

Iată programul complet, care afişează 1 dacă şirul este corect, sau 0 în caz contrar:

#include <stdio.h>

FILE *fin, *fout;
int first, eroare;

void S() {
  if ( first == 'a' ) {
    first = fgetc( fin );
    S();
    if ( first == 'b' )
      first = fgetc( fin );
    else
      eroare = 1;
  }
}

int main() {
  fin = fopen( "ab.in", "r" );
  first = fgetc( fin );
  S();
  fclose( fin );

  if ( first != '\n' )
    eroare = 1;
  fout = fopen( "ab.out", "w" );
  fputc( '1'-eroare, stdout );
  fclose( fout );

  return 0;
}

Exemplul 2

Să considerăm gramatica din Exemplul 2. Ne propunem să scriem o funcţiile S(), X() şi Y() care verifică dacă şirul de la intrare se potriveşte cu ceea ce poate produce simbolul de start S. După verificare, cursorul din fişier va rămîne la primul caracter după şirul verificat. Programul arăta astfel:

#include <stdio.h>

FILE *fin, *fout;
int first, eroare;

void X();
void Y();

void S() {
  X();
  Y();
}

void X() {
  if ( first == 'a' ) {
    first = fgetc( fin );
    X();
    if ( first == 'b' )
      first = fgetc( fin );
    else
      eroare = 1;
  }
}

void Y() {
  if ( first == 'c' ) {
    first = fgetc( fin );
    Y();
    if ( first == 'd' )
      first = fgetc( fin );
    else
      eroare = 1;
  }
}

int main() {
  fin = fopen( "abcd.in", "r" );
  first = fgetc( fin );
  S();
  fclose( fin );

  if ( first != '\n' )
    eroare = 1;
  fout = fopen( "abcd.out", "w" );
  fputc( '1'-eroare, stdout );
  fclose( fout );

  return 0;
}

Primul caracter este special

Deoarece vom avea mereu de luat decizii ce "înghițim" mai departe bazat pe primul caracter de la intrare, îl vom ține separat în variabila numită first. Astfel, șirul de la intrare este reprezentat de primul caracter ținut în first și de fișierul de la intrare, ce urmează logic în secvență. De fiecare dată cînd citim un caracter din fișier îl trecem mai întîi în variabila first. Astfel putem "trage cu ochiul" la primul caracter din fișier fără a avea probleme cu faptul că l-am citit deja și că o altă funcție vrea să îl recitească. Toate funcțiile știu că primul caracter este în first.

Dincolo de corectitudine

Pînă acum am văzut cum scriem un analizor recursiv cu proceduri care ne spune dacă un şir la intrare este corect. Dar dacă am dori să aflăm mai multe? Pentru gramaticile anterioare ne-am putea dori să aflăm factorul maxim de imbricare (numărul maxim de litere 'a' sau 'c'), de exemplu. Sau, pentru o expresie aritmetică, ne-am dori să o evaluăm. De obicei programul analizorului se poate adapta uşor pentru aceste calcule extra. De exemplu, pentru factorul maxim de imbricare am putea să modificăm funcţiile X() şi Y() să returneze acest factor, iar funcţia S() să calculeze maximul:

#include <stdio.h>

FILE *fin, *fout;
int first, eroare;

int X();
int Y();

int S() {
  int x, y;
  x = X();
  y = Y();
  return x > y ? x : y;
}

int X() {
  int x = 0;
  if ( first == 'a' ) {
    first = fgetc( fin );
    x = X() + 1;
    if ( first == 'b' )
      first = fgetc( fin );
    else
      eroare = 1;
  }
  return x;
}

int Y() {
  int y = 0;
  if ( first == 'c' ) {
    first = fgetc( fin );
    y = Y() + 1;
    if ( first == 'd' )
      first = fgetc( fin );
    else
      eroare = 1;
  }
  return y;
}

int main() {
  int max;

  fin = fopen( "abcd.in", "r" );
  first = fgetc( fin );
  max = S();
  fclose( fin );

  if ( first != '\n' )
    eroare = 1;
  fout = fopen( "abcd.out", "w" );
  if ( eroare == 1 )
    fprintf( fout, "-1\n" );
  else
    fprintf( fout, "%d\n", max );
  fclose( fout );

  return 0;
}

Exemplul 3

ParantezeUn sir corect de paranteze rotunde este un sir in care toate parantezele pot fi grupate dupa urmatoarea regula: fiecarei paranteze inchise ii este asociata cea mai apropiata paranteza deschisa aflata in dreapta sa, care nu a fost anterior asociata altei paranteze inchise.

#include <stdio.h>

const int MAX_N = 100;

int poz;
char sir[MAX_N + 1];

int S() {
  int raspuns;
  if (sir[poz] == '(') { // S -> (S)S
    poz++; // '('
    raspuns = S();
    if (raspuns) {
      if (sir[poz] == ')') {
        poz++; // ')'
        raspuns = S();
      } else {
        raspuns = 0;
      }
    }
  } else { // S -> lambda
    raspuns = 1;
  }
  return raspuns;
}

int corect() {
  return S() && sir[poz] == 0;
}

int main() {
  scanf("%s", sir);
  poz = 0;
  if (corect()) {
    printf("Bine\n");
  } else {
    printf("Gresit la pozitia %d: %s\n", poz + 1, sir + poz);
  }
  return 0;
}

Exemplul 4

agenda

Gramatica

  • Linie = Data- DataCeva
  • Data = ZiLunaOra.Minut

Studiu de caz: paranteze 3

Problema paranteze 3 este o modificare a exemplului 2. Ea cere să se spună dacă o expresie de paranteze deschise şi închise este corectă şi să se calculeze gradul de imbricare. Parantezele pot fi rotunde, sau acolade. Ele se pot închide şi acolade în interiorul parantezelor, nu ca la matematică.

Vom folosi următoarea gramatică:

E ⇾ ('('E')'|'{'E'}')*

Remarcaţi că am pus parantezele şi acoladele între apostroafe, pentru a le distinge de parantezele din descrierea gramaticii. Steluţa de la final înseamnă că expresia dintre paranteze poate să apară de oricîte ori, inclusiv de zero ori. Nu uitaţi că bara verticală înseamna sau, putînd alege oricare din producţii.

Această gramatică spune că o expresie este formată dintr-un şir de oricîte subexpresii, inclusiv niciuna, ceea ce înseamnă că expresia vidă este corectă. O subexpresie este formată dintr-o pereche de paranteze rotunde sau acolade între care se află o expresie. Precum vedeţi gramaticile sînt definiţii recursive.

Studiu de caz: expr

Problema expr cere să evaluăm o expresie. Pentru aceasta să găsim mai întîi gramatica care descrie limbajul expresiilor aritmetice din problemă. Pentru aceasta să definim următorii termeni:

  • Termen: Un termen este o subexpresie care nu conţine adunări sau scăderi. Astfel, o expresie este formată dintr-o înşiruire de termeni care sînt despărţiţi de operatori plus sau minus. Un termen va fi format din factori care se înmulţesc sau împart.
  • Factor: Un factor este o subexpresie care nu conţine adunări, scăderi, înmulţiri sau împărţiri. El este, în principiu, un număr, în faţa căruia putem avea semne, operatori unari plus sau minus. Tot un factor este şi o expresie între paranteze.

Putem acum introduce gramatica:

Expr -> Term (('+'|'-')Term)*
Term -> Fact (('*'|'/')Fact)*
Fact -> Int | ('+'|'-')Fact|'('Expr')' 
Int -> ('0'|'1'|'2'|...|'9')+

Să generăm de exemplu expresia 2+3*4:

Expr -> Term + Term -> Fact + Term -> Int + Term -> 2 + Term -> 2 + Fact * Fact -> 
-> 2 + Int * Fact -> 2 + Int * Int -> 2 + 3 * Int -> 2 + 3 * 4

În continuare vom scrie analizorul recursiv cu proceduri, cu o mică adăugire: fiecare funcţie asociată lui Expr, Term şi Fact va returna valoarea subexpresiei analizate de acel neterminal. Astfel, în final, vom avea valoarea întregii expresii.

Notaţia postfix

Notaţia postfix a unei expresii aritmetice, denumită şi notaţie poloneză inversă, scrie mai întîi operanzii şi apoi operatorii. De exemplu:

  • 3 + 4: notație infixată
  • 4 3 +: notație postfixată sau notație poloneză inversă

Notația postfixată are două mari avantaje:

  1. Nu necesită paranteze. (3 + 4) * 5 se transcrie în notație postfixată ca 3 4 + 5 *
  2. Se poate evalua ușor folosind doar o stivă de operanzi: de câte ori apare un operator se scot elemente de pe stivă (două, sau cîte necesită operatorul), se evaluează operaţia executînd operatorul și se pune rezultatul înapoi pe stivă.

Când întâlnim o sumă de mai mulți termeni sau un produs de mai mulți factori, cel din stânga are prioritate. Cu alte cuvinte, 3 + 4 + 5 se scrie în notație postfixată ca 3 4 + 5 +, nu 3 4 5 + +. Aceasta din urmă ar fi echivalentă cu 3 + (4 + 5). Devine evident de ce avem nevoie de această prioritate dacă ne gândim la expresia 3 - 4 + 5. Dacă o reprezentăm ca 3 4 - 5 +, atunci valoarea ei este 4. Dacă însă o reprezentăm ca 3 4 5 + -, atunci valoarea ei este -6.

Conversia expresiilor la notaţia postfix

Unele probleme, cum ar fi problema pom, nu cer evaluarea unei expresii, ci transformarea ei în notaţie postfix. Cum realizăm acest lucru?

Vom modifica fiecare funcţie corespunzătoare unui simbol neterminal N să scrie la ieşire notaţia postfix a subexpresiei analizate de N. De exemplu, pentru gramatica de la studiul de caz 2, o expresie Expr va chema pe rînd funcţiile neterminalului Term (care vor scrie subexpresiile postfix pentru fiecare termen), iar, începînd cu al doilea termen, va afişa semnul găsit imediat după apelul termenului.

Precum am menţionat mai devreme, modificarea este relativ uşoară.

Temă

La temă vă rămîn problemele de la studiile de caz:

Tema 13 clasa a 7-a

Rezolvări aici [2]