Proiect informatică Clasa XI-C

Fractali

Ce sunt fractalii, cum se desenează pe calculator și ce legătură au cu algoritmii de la informatică.

Demonstrații
Triunghiul lui Sierpiński

Ce este un fractal?

Un fractal este o figură în care același model se repetă la dimensiuni din ce în ce mai mici. Dacă mărești o bucată din ea, vezi o formă asemănătoare cu întregul. Această proprietate se numește autosimilaritate.

Un exemplu din natură este copacul: o ramură seamănă cu un copac mic, iar o rămurică de pe ea seamănă cu o ramură mică. În matematică, repetarea poate continua la nesfârșit. Pe calculator ne oprim după un număr de pași, deci desenăm o aproximare.

Pentru desene folosim noțiuni de la informatică: funcții, bucle, recursivitate și coordonate.

↳

Recursivitate

O funcție se apelează pe ea însăși, de fiecare dată pentru o problemă mai mică. Se oprește când problema devine suficient de simplă.

↻

Iterație

Aceeași operație se repetă de mai multe ori. Rezultatul unui pas este punctul de plecare pentru pasul următor.

n 2

Complexitate

Cu cât cerem mai multe detalii, cu atât calculatorul face mai multe operații și are nevoie de mai mult timp.

Teorie

De unde vine ideea de fractal și cum se măsoară un fractal.

T1

ISTORIE

Scurtă istorie

Primii fractali au apărut în matematică la începutul secolului XX. Abia după 1970, cu ajutorul calculatoarelor, s-a văzut că ei descriu multe forme din natură.

  1. 1904
    Helge von Koch descrie curba Koch, obținută prin împărțirea repetată a unui segment (demonstrația 02).
  2. 1915
    Wacław Sierpiński descrie triunghiul care îi poartă numele (demonstrația 01), iar în 1916, covorul.
  3. 1918
    Gaston Julia studiază ce se întâmplă când aplici aceeași formulă de multe ori la rând. Felix Hausdorff definește o dimensiune care poate fi și un număr cu zecimale (T2).
  4. 1975
    Mandelbrot introduce cuvântul fractal, din latinescul fractus, care înseamnă „rupt” sau „fragmentat”.
  5. 1980
    Mandelbrot studiază, pe calculatoarele firmei IBM, mulțimea care îi poartă numele (demonstrația 03). Loren Carpenter prezintă Vol Libre, un film scurt cu munți desenați pe calculator cu ajutorul fractalilor.
T2

PUTERI & LOGARITMI

Dimensiunea fractală

Dimensiunea se poate afla cu o întrebare: câte copii micșorate ale unei figuri sunt necesare ca să refaci figura întreagă?

  • Un segment împărțit în 2 părți egale: obții 2 bucăți. 2 = 2¹, deci dimensiunea este 1.
  • Un pătrat cu latura împărțită în 2: obții 4 pătrățele. 4 = 2², deci dimensiunea este 2.
  • Un cub cu latura împărțită în 2: obții 8 cubulețe. 8 = 2³, deci dimensiunea este 3.

Dimensiunea este deci exponentul. Dacă o figură este formată din N copii ale ei, fiecare de k ori mai mică, dimensiunea D respectă relația de mai jos. Exponentul îl aflăm cu logaritmi.

N = kD ⇒ D = log N / log k

Triunghiul lui Sierpiński este format din 3 copii ale lui, fiecare de 2 ori mai mică. Avem 3 = 2D, deci D = log 3 / log 2 ≈ 1,585. Rezultatul nu este un număr întreg: triunghiul ocupă mai mult decât o linie, dar mai puțin decât o suprafață plină.

CALCULATOR

DIMENSIUNEA D

1,585

Demonstrații

01 /03

RECURSIVITATE & CONDIȚIE DE OPRIRE

Triunghiul lui Sierpiński

Pornim de la un triunghi. Unim mijloacele laturilor și obținem 4 triunghiuri mai mici. Îl eliminăm pe cel din mijloc și repetăm același lucru pentru celelalte 3. Numărul de repetări este nivelul.

3 copii, fiecare de 2 ori mai mică Nivelul 4
0 · un triunghi 7 · 2.187 triunghiuri
81 triunghiuri desenate 3ⁿ

Algoritmul în C++ ↴

Funcția primește n, numărul de repetări rămase, și coordonatele x și y ale vârfurilor A, B și C. Rândurile evidențiate sunt cele care se execută la nivelul ales.

void fractal(int n,
             double xA, double yA,
             double xB, double yB,
             double xC, double yC) {
    // La nivelul 0, desenam si ne oprim.
    if (n == 0) {
        deseneazaTriunghi(xA, yA, xB, yB, xC, yC);
        return;
    }

    // Calculam mijloacele celor trei laturi.
    double xAB = (xA + xB) / 2;
    double yAB = (yA + yB) / 2;
    double xBC = (xB + xC) / 2;
    double yBC = (yB + yC) / 2;
    double xCA = (xC + xA) / 2;
    double yCA = (yC + yA) / 2;

    // Continuam cu triunghiul din coltul A.
    fractal(n - 1, xA, yA, xAB, yAB, xCA, yCA);

    // Continuam cu triunghiul din coltul B.
    fractal(n - 1, xAB, yAB, xB, yB, xBC, yBC);

    // Continuam cu triunghiul din coltul C.
    fractal(n - 1, xCA, yCA, xBC, yBC, xC, yC);
}
Programul complet în C++ (sierpinski.cpp) ↴
// Triunghiul lui Sierpinski, varianta recursiva.
// Programul scrie desenul in fisierul sierpinski.svg.
#include <fstream>
using namespace std;

ofstream fout("sierpinski.svg");

void deseneazaTriunghi(double xA, double yA, double xB, double yB, double xC, double yC) {
    fout << "<polygon fill='#d34c2a' points='"
         << xA << "," << yA << " " << xB << "," << yB << " " << xC << "," << yC << "'/>\n";
}

void fractal(int n,
             double xA, double yA,
             double xB, double yB,
             double xC, double yC) {
    // La nivelul 0, desenam si ne oprim.
    if (n == 0) {
        deseneazaTriunghi(xA, yA, xB, yB, xC, yC);
        return;
    }

    // Calculam mijloacele celor trei laturi.
    double xAB = (xA + xB) / 2, yAB = (yA + yB) / 2;
    double xBC = (xB + xC) / 2, yBC = (yB + yC) / 2;
    double xCA = (xC + xA) / 2, yCA = (yC + yA) / 2;

    // Continuam cu cele trei triunghiuri din colturi.
    fractal(n - 1, xA, yA, xAB, yAB, xCA, yCA);
    fractal(n - 1, xAB, yAB, xB, yB, xBC, yBC);
    fractal(n - 1, xCA, yCA, xBC, yBC, xC, yC);
}

int main() {
    int n = 5;
    fout << "<svg xmlns='http://www.w3.org/2000/svg' width='600' height='530'>\n";
    fout << "<rect width='600' height='530' fill='white'/>\n";
    fractal(n, 300, 10, 10, 512, 590, 512);
    fout << "</svg>\n";
    return 0;
}
LEGĂTURA CU INFORMATICA

Metoda divide et impera. O problemă mare se împarte în probleme mai mici de același fel, rezolvate cu aceeași funcție. Condiția n == 0 oprește apelurile. Fără ea, funcția s-ar apela la nesfârșit, până când programul ar rămâne fără memorie.

02 /03

ITERAȚIE & COMPLEXITATE

Curba Koch

Pornim de la un segment și îl împărțim în trei părți egale. Partea din mijloc o înlocuim cu două segmente care formează un vârf, ca un triunghi fără bază. Repetăm pentru fiecare segment nou. Dacă pornim de la un triunghi, obținem un fulg de zăpadă.

4 segmente, fiecare de 3 ori mai scurt Nivelul 3
0 6
Forma
64 segmente
2,37× față de lungimea inițială

La fiecare repetare, fiecare segment devine 4 segmente. După n repetări, o latură are 4ⁿ segmente. Numărul crește foarte repede: la 6 repetări sunt deja 4.096.

Programul complet în C++ (koch.cpp) ↴
// Curba Koch, varianta recursiva.
// Programul scrie desenul in fisierul koch.svg.
#include <fstream>
#include <cmath>
using namespace std;

ofstream fout("koch.svg");

void segment(double x1, double y1, double x2, double y2) {
    fout << "<line x1='" << x1 << "' y1='" << y1
         << "' x2='" << x2 << "' y2='" << y2 << "' stroke='#3e5b9c'/>\n";
}

void koch(int n, double x1, double y1, double x2, double y2) {
    // La nivelul 0, desenam segmentul si ne oprim.
    if (n == 0) {
        segment(x1, y1, x2, y2);
        return;
    }
    double dx = (x2 - x1) / 3, dy = (y2 - y1) / 3;

    // P si Q impart segmentul in trei parti egale.
    double xP = x1 + dx, yP = y1 + dy;
    double xQ = x1 + 2 * dx, yQ = y1 + 2 * dy;

    // V este varful: rotim segmentul PQ cu 60 de grade in jurul lui P.
    double xV = xP + dx * 0.5 + dy * sqrt(3.0) / 2;
    double yV = yP + dy * 0.5 - dx * sqrt(3.0) / 2;

    koch(n - 1, x1, y1, xP, yP);
    koch(n - 1, xP, yP, xV, yV);
    koch(n - 1, xV, yV, xQ, yQ);
    koch(n - 1, xQ, yQ, x2, y2);
}

int main() {
    int n = 4;
    fout << "<svg xmlns='http://www.w3.org/2000/svg' width='600' height='220'>\n";
    fout << "<rect width='600' height='220' fill='white'/>\n";
    koch(n, 10, 200, 590, 200);
    fout << "</svg>\n";
    return 0;
}
LUNGIME ȘI ARIE

Lungimea crește, aria nu. Fiecare repetare face curba de 4/3 ori mai lungă, deci lungimea crește fără limită. Totuși, fulgul rămâne în aceeași zonă, iar aria lui nu depășește 8/5 din aria triunghiului inițial. Pe ecran desenăm doar primele repetări.

03 /03

BUCLE, CONDIȚII & NUMERE COMPLEXE

Mulțimea Mandelbrot

Fiecare punct al imaginii corespunde unui număr c. Pornim de la z = 0 și calculăm de multe ori z ← z² + c. Dacă valorile rămân aproape de origine, punctul face parte din mulțime și îl colorăm închis. Dacă se depărtează, punctul nu face parte din mulțime, iar culoarea arată după câți pași s-a depărtat.

MANDELBROT
1,0×
Mai rapid Mai multe detalii
REGULA CARE SE REPETĂ
z n+1 = z n ² + c

z₀ = 0   ·   c = a + bi

Închis: valorile au rămas aproape de origine pe toată durata calculului, deci punctul aparține probabil mulțimii. Colorat: valorile s-au depărtat, deci punctul nu aparține mulțimii.

Cum se calculează z² + c ↴

Numărul c = a + bi corespunde punctului (a, b). Litera i este un număr cu proprietatea i² = −1. Dacă z = x + yi, atunci z² + c se calculează separat pentru x și y:

x nou = x² − y² + a
y nou = 2xy + b

Ambele formule folosesc valorile vechi ale lui x și y. Ne oprim când x² + y² > 4, adică atunci când punctul este la o distanță mai mare de 2 față de origine. De acolo, se știe sigur că se va depărta la nesfârșit.

Programul complet în C++ (mandelbrot.cpp) ↴
// Multimea Mandelbrot desenata cu caractere, in consola.
#include <iostream>
using namespace std;

int main() {
    int latime = 78, inaltime = 32, maxPasi = 100;
    char simboluri[] = " .:-=+*#%&";   // de la "evadeaza repede" la "evadeaza greu"

    for (int linie = 0; linie < inaltime; linie++) {
        for (int col = 0; col < latime; col++) {
            // Punctul c = a + bi corespunzator caracterului de pe ecran.
            double a = -2.2 + col * 3.0 / latime;
            double b = 1.2 - linie * 2.4 / inaltime;

            // Pornim de la z = 0 si repetam z = z*z + c.
            double x = 0, y = 0;
            int pas = 0;
            while (x * x + y * y <= 4 && pas < maxPasi) {
                double xNou = x * x - y * y + a;
                y = 2 * x * y + b;
                x = xNou;
                pas++;
            }

            if (pas == maxPasi) cout << '@';    // nu a evadat: punct din multime
            else {
                int k = pas;                    // a evadat dupa "pas" pasi
                if (k > 9) k = 9;
                cout << simboluri[k];
            }
        }
        cout << '\n';
    }
    return 0;
}
LEGĂTURA CU INFORMATICA

Două bucle și o condiție. O buclă parcurge pixelii imaginii. Pentru fiecare pixel, a doua buclă repetă calculul până când punctul se depărtează sau până la numărul maxim de pași. Mai mulți pași înseamnă mai multe detalii, dar și mai mult timp de calcul.

Informatică

Algoritmii din spatele desenelor, explicați cu noțiuni din clasa a XI-a: recursivitatea și stiva de apeluri.

I1

RECURSIVITATE & STIVĂ

Recursivitatea și stiva de apeluri

Când o funcție se apelează pe ea însăși, calculatorul trebuie să țină minte unde a rămas cu fiecare apel neterminat. Pentru asta folosește o zonă de memorie numită stiva de apeluri. Funcționează ca un teanc de farfurii: o farfurie nouă se pune deasupra, iar prima luată este tot cea de deasupra. Regula se numește LIFO (Last In, First Out): ultimul intrat, primul ieșit.

Mai jos rulează, pas cu pas, funcția fractal din demonstrația 01. Fiecare apel fractal(n) se pune în vârful stivei, iar când ajunge la return, iese din stivă și programul continuă apelul de dedesubt.

apelul care rulează apeluri care așteaptă desenat

Stiva de apeluri · vârful este sus

    Stiva este goală.

    apeluri făcute
    0
    în stivă acum
    0
    maximul din stivă
    0
    triunghiuri desenate
    0
    DE REȚINUT

    La nivelul n se fac în total 1 + 3 + 9 + … + 3ⁿ = (3ⁿ⁺¹ − 1) / 2 apeluri, dar în stivă stau cel mult n + 1 în același timp: câte un apel pentru fiecare nivel. Dacă funcția nu ar avea condiția de oprire n == 0, stiva s-ar umple, iar programul s-ar opri cu eroarea stack overflow (depășirea stivei).

    Curiozități

    Două locuri neașteptate în care apar fractalii și câteva exemple din jurul nostru.

    C1

    MULȚIMEA MANDELBROT

    Numărul π în mulțimea Mandelbrot

    În 1991, Dave Boll, student la informatică, a studiat punctele c = −0,75 + εi, unde ε (epsilon) este un număr foarte mic. Aceste puncte sunt chiar lângă locul unde corpul mare al mulțimii se unește cu discul din stânga. Pentru fiecare punct, a numărat câți pași durează până când valorile se depărtează.

    A observat că numărul de pași înmulțit cu ε se apropie de π. Cu cât ε este mai mic, cu atât rezultatul este mai aproape de 3,14159… Aaron Klebanoff a demonstrat mai târziu un rezultat asemănător pentru punctele c = 0,25 + ε.

    ε pași n n · ε
    Apasă „Calculează” ca să vezi rezultatele.

    π = 3,1415926…

    C2

    COMBINĂRI & OPERAȚII PE BIȚI

    Triunghiul lui Pascal și triunghiul lui Sierpiński

    În triunghiul lui Pascal, fiecare număr este suma celor două numere de deasupra lui: C(n, k) = C(n−1, k−1) + C(n−1, k). Dacă colorăm doar numerele impare, apare triunghiul lui Sierpiński. Putem colora și după restul împărțirii la 3 sau la 5.

    64 de rânduri colorat = nu se împarte exact
    Restul împărțirii la
    729 numere impare

    Pentru împărțirea la 2 obținem 3ᵏ numere impare, adică exact câte triunghiuri are Sierpiński la nivelul k.

    Același desen în C++, fără să calculăm combinările ↴

    Teorema lui Lucas spune că C(i, j) este impar exact atunci când fiecare bit 1 din j este 1 și în i. În C++ verificăm asta cu (i & j) == j, unde & este operația „și” pe biți.

    for (int i = 0; i < 32; i++) {
        for (int j = 0; j <= i; j++)
            cout << (((i & j) == j) ? "* " : "  ");
        cout << '\n';
    }

    Alte exemple

    NATURĂ

    Conopida romanesco

    Fiecare buchețel are forma întregii conopide, la scară mai mică. Buchețelele sunt așezate în spirale, iar numărul spiralelor este, de obicei, un număr din șirul lui Fibonacci.

    CORPUL UMAN

    Plămânii

    Căile respiratorii se ramifică de aproximativ 23 de ori, ca un arbore fractal. Astfel, o suprafață de zeci de metri pătrați încape în piept.

    TEHNOLOGIE

    Antenele fractale

    În 1987, radioastronomul Nathan Cohen a construit o antenă în formă de fractal. O astfel de antenă poate fi mică și poate funcționa pe mai multe frecvențe, de aceea ideea este folosită în dispozitive mobile.

    FILM

    Peisaje desenate pe calculator

    Loren Carpenter a folosit fractali pentru munții din filmul scurt Vol Libre (1980) și pentru o planetă din filmul Star Trek II (1982). Jocurile video folosesc metode asemănătoare pentru peisaje.