GLI ALGORITMI: ANALISI E
RAPPRESENTAZIONE DI UN
PROBLEMA
Che cos'è un algoritmo
Il computer è una macchina creata per svolgere compiti e aiutare a risolvere problemi di
vario tipo e complessità. Un problema è un "ostacolo" che rende difficile raggiungere un
determinato obiettivo o soddisfare una certa esigenza.
Per risolvere un problema generalmente sono necessarie delle informazioni iniziali, dei dati
da analizzare e un procedimento di elaborazione che permette di produrre un risultato e,
quindi, giungere a una soluzione.
Il modo corretto di affrontare un problema è quello di farne dapprima un'analisi,
individuare i dati iniziali ed elaborare un procedimento che permetta di combinare i dati
a disposizione in modo da produrne di nuovi che rappresentano, dove possibile, la
soluzione della questione.
dati input
procedimento
dati output
Per esempio, se l'obiettivo a cui occorre arrivare è la realizzazione di un dolce, dapprima
valutiamo la ricetta che abbiamo a disposizione, controlliamo di avere tutti gli ingredienti,
eventualmente acquistiamo quelli mancanti e attuiamo un procedimento che permetta
di ottenere l'impasto necessario fino a ottenere il dolce finito.
In un computer, il procedimento che viene attuato per risolvere un problema è detto
programma: questo si fonda a sua volta su uno o più algoritmi, cioè l'insieme di istruzioni
che permettono di giungere all'obiettivo prefissato attraverso un numero finito di passi.
Il termine algoritmo deriva dalla trascrizione latina del nome del matematico persiano alKhwarizmi, considerato uno dei primi matematici ad aver teorizzato l'utilizzo di questa
procedura.
Per saperne di più
SI CONOSCE POCO DELLA VITA DI MUHAMMAD IBN MUSA AL-KHWARIZMI, MATEMATICO E
SCIENZIATO DI ORIGINE ARABA NATO PROBABILMENTE A BABILONIA INTORNO AL 780 DOPO
CRISTO. FU RESPONSABILE DELLA BIBLIOTECA DI BAGHDAD E FECE TRADURRE MOLTE OPERE
MATEMATICHE INDIANE E DEL PERIODO GRECO-ELLENISTICO. VIENE CONSIDERATO TRA I
PADRI DELL'ALGEBRA, PAROLA DERIVANTE DALL' ARABO AI-JABR, CHE SIGNIFICA
"COMPLETARE". LO STESSO TERMINE ALGORITMO DERIVA DALLA LATINIZZAZIONE DEL SUO
NOME.
Ma come si giunge a individuare l'algoritmo che permette di risolvere il problema di
partenza?
Bisogna innanzitutto analizzare il problema, cercando di eliminare i dettagli inutili e le
ambiguità presenti; fatto ciò, si individuano i reali obiettivi del problema, si stila un elenco
dei vincoli che la soluzione deve soddisfare e infine si valuta quali sono i dati di input da
elaborare e il risultato che ne deriva.
Poniamo il caso di un obiettivo relativamente semplice: preparare un pentolino di crema
pasticciera. Le ambiguità di una richiesta così formulata sono evidenti: un pentolino
quanto grande? Quanta crema dobbiamo preparare?
Per risolvere la prima ambiguità si può decidere la quantità di crema che si vuole
preparare, per esempio 500 gr.
I vincoli riguardano il tipo di crema da preparare: trattandosi di crema pasticciera sono
richiesti determinati ingredienti, che la differenziano da qualsiasi altro tipo di crema.
I dati di input sono quindi gli ingredienti (proporzionati per la quantità di crema da
produrre), il risultato atteso è la crema.
In alternativa, se gli ingredienti sono insufficienti a produrre la quantità di crema
desiderata, si possono adattare i dati in ingresso per produrne un quantitativo minore.
Una volta risolte queste prime problematiche si passa all'individuazione della strategia
risolutiva, progettando possibili soluzioni e individuando quella più efficiente, cioè quella
che porta al risultato nel minor tempo possibile e con il migliore utilizzo delle risorse a
disposizione.
Nel nostro caso possiamo scomporre l'obiettivo finale in istruzioni più semplici che
andranno a costituire quella che è la "ricetta" per realizzare il dolce.
La soluzione al problema "preparare 500 gr. di crema pasticciera" si presenta quindi come
una serie di istruzioni che porteranno all'obiettivo finale (output).
Le istruzioni per preparare la ricetta sono un numero finito di passaggi elementari non
ulteriormente scomponibili; ogni passaggio ha un tempo d'esecuzione determinato e
finito, non ambiguo, e produce un risultato univoco.
Queste caratteristiche corrispondono alle proprietà che un algoritmo deve avere per
definirsi tale:
Atomicità
I passi che lo compongono devono essere elementari, ovvero non ulteriormente
scomponibili
Finitezza
Il numero delle istruzioni che lo compongono deve essere finito, così come i dati in
ingresso
Non ambiguità
I passi risolutivi devono poter essere interpretati in modo chiaro e univoco da chi esegue
l'algoritmo, sia esso un uomo o una macchina
Effettività
L'esecuzione deve portare a un risultato univoco
Terminazione
L'esecuzione di ogni istruzione deve avere un termine dopo un tempo finito
Realizzazione
I passi devono essere eseguibili materialmente
Se tutte le proprietà dell'algoritmo sono rispettate, il procedimento potrà essere ripetuto
più volte ottenendo lo stesso risultato.
Riassumendo, i punti fondamentali sui quali si fonda un algoritmo sono:
1 I dati su cui deve operare
2 La sequenza esatta delle azioni da eseguire
3 Il controllo dell'ordine con cui le azioni devono essere eseguite
4 L'azione di arresto
In ambito informatico, una volta analizzato il problema e definito l'algoritmo di risoluzione
si passa alla codifica delle istruzioni e alla realizzazione del programma che permetterà di
applicare lo stesso algoritmo a differenti dati in input.
Come si rappresenta un algoritmo: i diagrammi di
flusso
È possibile rappresentare un algoritmo in diversi modi, grafici o testuali. Un esempio di
rappresentazione testuale di un algoritmo è la ricetta della crema pasticciera che
abbiamo visto prima.
Un altro modo di rappresentare gli algoritmi prevede di utilizzare delle convenzioni
grafiche determinate che prendono il nome di diagrammi di flusso (flow chart). Ogni
elemento del diagramma ha un significato ben preciso. Qualsiasi problema, sia esso
numerico o non numerico, può essere rappresentato come un diagramma di flusso:
l'importante è che sia un problema risolvibile con una procedura.
A ogni forma dei blocchi che compongono i flow chart corrisponde un preciso significato.
Ogni blocco è obbligatoriamente collegato a un altro da una freccia.
Alcuni esempi di diagrammi:
Dall’algoritmo al programma
Un qualsiasi programma software è di fatto una sequenza ordinata di istruzioni (cioè un
algoritmo) che a partire da dei dati in ingresso (input) restituisce dei risultati in uscita
(output), in seguito all'elaborazione da parte di un computer.
Come si fa a trasformare un algoritmo in un programma per computer?
Bisogna innanzitutto scrivere le istruzioni in un linguaggio che sia comprensibile alla
macchina: tali linguaggi si chiamano linguaggi di programmazione.
Un linguaggio di programmazione è costituito, come ogni altro tipo di linguaggio, da un
alfabeto di simboli, con cui vengono costruite le parole e le frasi, e da un insieme di regole
sintattiche e lessicali per la costruzione e l'uso corretto delle parole e delle frasi del
linguaggio.
Esistono molti tipi di linguaggi di programmazione, che si sono evoluti nel tempo e variano
in base alloro grado di complessità.
Storia dei linguaggi di programmazione
Per incontrare uno dei primi esempi di programma pensato per essere eseguito da una
macchina dobbiamo andare indietro nel tempo fino all'inizio dell'Ottocento e guardare al
lavoro di Ada Lovelace, che realizzò un algoritmo per la macchina analitica di Babbage,
una grande macchina alimentata a vapore, molto simile ai telai per l'industria tessile,
inventati proprio in quegli anni in Inghilterra, programmabile attraverso delle schede
perforate e capace di svolgere le quattro operazioni matematiche, memorizzare i dati e
produrre risultati. Ada Lovelace è considerata la prima programmatrice della storia.
Il primo linguaggio di programmazione della storia è probabilmente il PlankalkU! di Konrad
Zuse, sviluppato in Svizzera durante la seconda guerra mondiale e pubblicato nel 1946.
Plankalkul non venne mai realmente usato per programmare.
La programmazione dei primi elaboratori era realizzata in short code o in codice binario,
da cui si è evoluto l'assembly, al quale faremo cenno nelle prossime pagine.
La maggior parte dei linguaggi di programmazione successivi cercarono di
rappresentare gli algoritmi di programmazione in maniera più leggibile per il
programmatore. Tali linguaggi vengono definiti di alto livello. Tra
i primi linguaggi ad alto livello ci fu il Fortran, creato nel 1957 da John Backus, da cui
nacque il BASIC (1964).
Compaiono in questi linguaggi il controllo delle
condizioni (se succede qualcosa allora fai
qualcos'altro), reso con l'istruzione IF, e nuove strutture di
controllo di flusso come i cicli WHILE
e FOR. In questo modo i salti nel codice quasi
scompaiono (GOTO), rendendo più facile la
manutenzione dei programmi.
Con i primi mini e microcomputer e le ricerche nella
Silicon Valley, nel 1983 viene inventato il Small-talk,
capostipite dei linguaggi definiti object-oriented
("orientati agli oggetti").
La programmazione orientata agli oggetti raggruppa in
un'unica entità (la classe) sia le strutture dati che le
procedure che operano su di esse, creando per
l'appunto un "oggetto".
Esempi di linguaggi object-oriented odierni sono il C++ e
Java, ideato nel 1995.
I linguaggi di programmazione
Il motore interno di un computer è la CPU, o processore, composta da unità di controllo e
unità aritmetico-logica. All'interno della CPU sono contenuti milioni di transistor, che
possiamo immaginare con tanti cancelli capaci di registrare solo due informazioni: aperto
(1) o chiuso (O). Le istruzioni necessarie per far compiere delle azioni alla CPU sono scritte
in linguaggio macchina, a sua volta costituito da un alfabeto, detto codice binario,
composto da due sole lettere: O e 1.
L'unità aritmetico-logica della CPU esegue a sua volta operazioni logiche (OR, NOT, AND,
XOR e così via) che restituiscono falso o vero (O o 1), utilizzando lo stesso alfabeto del
linguaggio macchina.
Un linguaggio è di livello tanto più basso quanto è più vicino al linguaggio macchina.
Usando i linguaggi di livello basso, vengono scritte manualmente le varie istruzioni e gli
indirizzi di memoria. L'esempio tipico di linguaggio di basso livello è l'Assembly. Data la
natura" elementare" dei programmi scritti in linguaggio macchina, un programma scritto
per un determinato processore non funzionerà per un computer dotato di un processore
diverso
Possiamo quindi dire che la CPU viene programmata in linguaggio macchina e che
immediatamente sopra il linguaggio macchina esiste il linguaggio Assembly. Se si utilizza
un linguaggio con un livello più alto rispetto al linguaggio macchina, è necessario un
passaggio intermedio che "traduca" il linguaggio sorgente in linguaggio macchina, ossia
in operazioni elementari che interagiscano direttamente con gli elementi del computer. I
tipi di traduttori sono due: i compilatori e gli interpreti.
Gli interpreti traducono un'istruzione alla volta e contemporaneamente la eseguono.
Durante la fase di interpretazione viene anche eseguito un controllo sulla sintassi del
programma e ne vengono segnalate le irregolarità. In caso di errore, è compito del
programmatore apportare le dovute correzioni alla riga di codice incriminata per
permettere l'avanzamento nell'interpretazione dell'algoritmo. Una volta corretti tutti gli
errori, il programma può essere eseguito. Esempi di linguaggi interpreti (o interpretati) sono
Python e Java.
I compilatori traducono un programma scritto in un linguaggio di alto livello in un
programma comprensibile al codice macchina di uno specifico hardware. Il vantaggio
della compilazione è quello di ottenere dei file eseguibili in poco tempo, ma ha lo
svantaggio di dover creare un programma diverso per ogni sistema operativo o hardware
differente, sacrificando quella che si definisce "portabilità" del software. Esempi di
linguaggi compilati sono C e Pascal.
Il vantaggio di un programma interpretato risiede nella portabilità del software creato,
che è quindi compatibile con macchine e piattaforme diverse: questo avviene tuttavia a
discapito della velocità di esecuzione, dato che, oltre a eseguire il codice, l'interprete
deve controllarlo.
In passato la compilazione è stata la norma per tutti i linguaggi di programmazione di uso
genera-le, ora si tende invece a scegliere linguaggi interpretati o linguaggi pensati come
un ibrido tra la compilazione e l'interpretazione. Sebbene il divario tra questi due approcci
sia stato ridotto notevolmente, la compilazione rimane il metodo preferito per applicazioni
che richiedono le massime prestazioni possibili.
Il numero dei linguaggi conosciuti è molto alto: sono circa 2500 i linguaggi più o meno noti
e diffusi. Come abbiamo visto, possono essere classificati in base alla distanza dal
linguaggio macchina (distinguendo linguaggi di alto e di basso livello) oppure in base
all'approccio usato (distinguendo linguaggi compilati e interpretati).
Lisp, C, C++, Pascal, HML, CSS, JavaScript, Java, PHP, Python, Ruby ecc. sono fra i
linguaggi più conosciuti. Non tutti però sono linguaggi di programmazione veri e propri:
alcuni sono linguaggi di programmazione puri, altri sono orientati al Web, altri sono
linguaggi di markup.
I linguaggi di markup permettono di rappresentare in maniera strutturale un testo. Si
usano per marcare la struttura di un documento: per esempio, una volta individuati il
titolo, il sottotitolo e il paragrafo di un documento vengono apposte delle etichette (o
tag) che delimitano l'elemento. Questo permette di identificare le varie parti del testo e
codificarle, in maniera da renderle interpretabili ed eseguibili da più software.
I linguaggi di markup hanno convenzioni e marcature diverse. I più famosi sono HTML (che
serve per la creazione di ipertesti nel Web), XML, Xhtml (un'unione dei due linguaggi
precedenti), TeX e le sue varianti (come LaTeX). La formattazione grafica dei testi
codificati con questi linguaggi viene definita da ulteriori codici, come CSS, Xsl e altri
linguaggi analoghi.
I linguaggi di programmazione orientati al Web sono invece PHP, ASP, RubyOnRails (una
variante di Ruby), Javascript e molti altri. Anche tra questi ci sono delle differenze: sono
tutti linguaggi interpretati, ma mentre i primi vengono interpretati lato server (ossia
l'interprete risiede sulla macchina server dove sono ospitate le pagine di codice)
JavaScript ha il suo interprete direttamente nel browser.
Infine, possono essere considerati linguaggi di programmazione a tutti gli effetti Lisp, C,
C++, Pascal, Java, Python e molti altri, che servono per codificare dei veri e propri
programmi.