Algoritmi și structuri de date

Presupun că ești programator de calculator. Poate că ești un nou student de informatică sau poate că ești un inginer software cu experiență. Indiferent de locul în care vă aflați pe acest spectru, sunt importante algoritmii și structurile de date. Nu doar ca concepte teoretice, ci și ca elemente de construcție utilizate pentru a crea soluții la problemele de afaceri.

Sigur, s-ar putea să știți cum să utilizați C # Listă sau Grămadă dar înțelegi ce se întâmplă sub capace? Dacă nu, faci cu adevărat cele mai bune decizii cu privire la algoritmii și structurile de date pe care le folosești?

Înțelegerea semnificativă a algoritmilor și structurilor de date începe cu o modalitate de a exprima și compara costurile lor relative.

Analiza asimptotică

Când vorbim despre măsurarea costului sau a complexității unui algoritm, ceea ce vorbim cu adevărat este efectuarea unei analize a algoritmului când seturile de intrări sunt foarte mari. Analizând ceea ce se întâmplă când numărul de intrări devine foarte mare este denumit analiză asimptotică. Cum se schimbă complexitatea algoritmului atunci când se aplică la zece sau o mie sau zece milioane de elemente? Dacă un algoritm rulează în cinci milisecunde cu o mie de elemente, ce putem spune despre ce se va întâmpla atunci când acesta rulează cu un milion? Vor dura cinci secunde sau cinci ani? Nu v-ați gândi mai bine în fața clientului dvs.?

Chestia asta contează!

Rata de crestere

Rata de creștere descrie modul în care complexitatea algoritmului se modifică odată cu creșterea dimensiunii de intrare. Acest lucru este reprezentat în mod obișnuit utilizând notația Big-O. O notație Big-O folosește un capital O ("ordin") și o formulă care exprimă complexitatea algoritmului. Formula poate avea o variabilă, n, care reprezintă dimensiunea intrării. Următoarele sunt câteva funcții comune de ordine pe care le vom vedea în această carte, dar această listă nu este completă.

Constant - O (1)

Un algoritm O (1) este unul a cărui complexitate este constantă indiferent de cât de mare este mărimea intrării. "1" nu înseamnă că există o singură operație sau că operațiunea durează un timp redus. Ar putea dura o microsecundă sau ar putea dura o oră. Ideea este că mărimea intrării nu influențează timpul necesar operațiunii.

public int GetCount (int [] elemente) return items.Length;  

Linear - O (n)

Un algoritm O (n) este unul a cărui complexitate crește liniar cu mărimea intrării. Este rezonabil să ne așteptăm ca dacă o mărime de intrare a unuia durează cinci milisecunde, o intrare cu o mie de elemente va dura cinci secunde.

Puteți adesea recunoaște un algoritm O (n) căutând un mecanism de looping care accesează fiecare membru.

public long GetSum (int [] elemente) sumă lungă = 0; foreach (int în elemente) sum + = i; sumă retur;  

Logaritmica - O (log n)

Un algoritm O (log n) este unul a cărui complexitate este logaritmică la dimensiunea sa. Mulți algoritmi de împărțire și cucerire se încadrează în această găleată. Arborele binar de căutare Conține metoda implementează un algoritm O (log n).

Linearitmic - O (n log n)

Un algoritm linearithmic sau loglinear este un algoritm care are o complexitate de O (n log n). Unii algoritmi de împărțire și cucerire se încadrează în această găleată. Vom vedea două exemple atunci când ne uităm la sortarea și sortarea rapidă.

Quadratic - O (n ^ 2)

Un algoritm O (n ^ 2) este unul a cărui complexitate este patratică față de mărimea lui. Deși nu este întotdeauna evitabilă, folosirea unui algoritm quadratic este un semn potențial pe care trebuie să-l reconsiderați algoritmului sau alegerii structurii datelor. Analiza algoritmilor quadratici nu se mărește la fel ca mărimea intrărilor. De exemplu, o matrice cu 1000 de numere întregi ar necesita 1.000.000 de operații pentru a finaliza. O intrare cu un milion de articole ar necesita un miliard de operațiuni (1 000 000 000 000). Pentru a pune acest lucru în perspectivă, dacă fiecare operație durează o milisecundă pentru a finaliza, un algoritm O (n ^ 2) care primește o intrare de un milion de elemente va dura aproape 32 de ani pentru a finaliza. A face acest algoritm de 100 de ori mai rapid ar dura încă 84 de zile.

Vom vedea un exemplu de algoritm patrat atunci când ne uităm la sortarea bulelor.

Cel mai bun, mediu și cel mai rău caz

Când spunem că un algoritm este O (n), ce spunem cu adevărat? Vom spune că algoritmul este O (n) în medie? Sau descriem scenariul cel mai bun sau cel mai rău?

În mod normal, înseamnă scenariul cel mai rău, cu excepția cazului în care cazul comun și cel mai rău caz sunt foarte diferite. De exemplu, vom vedea exemple în această carte unde un algoritm este O (1) în medie, dar periodic devine O (n) (a se vedea ArrayList.Add). În aceste cazuri voi descrie algoritmul ca O (1) în medie și apoi voi explica când se schimbă complexitatea.

Punctul cheie este acela că O (n) nu înseamnă că este întotdeauna n operații. Ar putea fi mai puțin, dar nu ar trebui să fie mai mult.

Ce măsuram?

Când se măsoară algoritmi și structuri de date, vorbim de obicei despre unul din cele două lucruri: timpul necesar pentru finalizarea operației (complexitatea operațională) sau cantitatea de resurse (memorie) utilizată de un algoritm (complexitatea resurselor).

Un algoritm care rulează de zece ori mai rapid, dar utilizează de zece ori mai multă memorie ar putea fi perfect acceptabil într-un mediu server cu cantități mari de memorie disponibilă, dar poate să nu fie adecvat într-un mediu încorporat unde memoria disponibilă este foarte limitată.

În această carte mă voi concentra în primul rând pe complexitatea operațională, dar în secțiunea Algoritmi de sortare vom vedea câteva exemple de complexitate a resurselor.

Unele exemple specifice de lucruri pe care le putem măsura includ:

  • Operațiunile de comparare (mai mari decât, mai puțin, egale cu).
  • Alocări și schimburi de date.
  • Alocări de memorie.

Contextul operațiunii care va fi efectuat va indica, de obicei, ce tip de măsurare se face.

De exemplu, atunci când discutăm despre complexitatea unui algoritm care caută un element dintr-o structură de date, aproape sigur vorbim despre operații de comparație. Căutarea este, în general, o operație numai pentru citire, astfel încât nu ar trebui să existe nicio necesitate de a efectua sarcini sau de a aloca memorie.

Cu toate acestea, atunci când vorbim despre sortarea datelor, ar putea fi logic să presupunem că am putea vorbi despre comparații, sarcini sau alocări. În cazurile în care există o ambiguitate, voi indica ce tip de măsurare se referă de fapt la complexitatea.

Urmatorul

Aceasta completează prima parte despre algoritmi și structuri de date. În continuare, vom trece la lista asociată. 

Cod