<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Algoritmi - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Impara a programmare gratuitamente! Tutorial di programmazione su Python, JavaScript, Linux e molto altro. ]]>
        </description>
        <link>https://www.freecodecamp.org/italian/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Algoritmi - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/italian/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 25 May 2026 10:43:03 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/italian/news/tag/algoritmi/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Algoritmi di Grafo e Strutture Dati Spiegate con Esempi in Java e C++ ]]>
                </title>
                <description>
                    <![CDATA[ Cos'è un Algoritmo di Grafo? Gli algoritmi di grafo sono un insieme di istruzioni per attraversare (visitare i nodi di) un grafo. Alcuni algoritmi sono usati per trovare uno specifico nodo o il percorso tra due dati nodi. Perché gli Algoritmi di Grafo Sono Importanti I grafi sono strutture dati ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/algoritmi-dei-grafi-e-strutture-dati-spiegate-con-esempi-in-java-e-c/</link>
                <guid isPermaLink="false">648db71de33e9b0675789a82</guid>
                
                    <category>
                        <![CDATA[ Algoritmi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Roberto Pauletto ]]>
                </dc:creator>
                <pubDate>Tue, 04 Jul 2023 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2023/06/5f9c9e3e740569d1a4ca3c1f.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/graph-algorithms-and-data-structures-explained-with-java-and-c-examples/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Graph Algorithms and Data Structures Explained with Java and C++ Examples</a>
      </p><h2 id="cos-un-algoritmo-di-grafo"><strong>Cos'è un Algoritmo di Grafo?</strong></h2><p>Gli algoritmi di grafo sono un insieme di istruzioni per attraversare (visitare i nodi di) un grafo.</p><p>Alcuni algoritmi sono usati per trovare uno specifico nodo o il percorso tra due dati nodi.</p><h3 id="perch-gli-algoritmi-di-grafo-sono-importanti"><strong>Perché gli Algoritmi di Grafo Sono Importanti</strong></h3><p>I grafi sono strutture dati molto utili che possono essere usate per modellare diversi problemi. Questi algoritmi hanno applicazione diretta nei siti di Social Network, nella modellazione di Macchine a Stati e molto altro.</p><h3 id="alcuni-comuni-algoritmi-di-grafo"><strong>Alcuni Comuni Algoritmi di Grafo</strong></h3><p>Alcuni dei più comuni sono:</p><ul><li>Ricerca in Ampiezza - Breadth First Search (BFS)</li><li>Ricerca in Profondità - Depth First Search (DFS)</li><li>Algoritmo di Dijkstra</li><li>Algoritmo di Floyd-Warshall</li></ul><h2 id="algoritmo-di-bellman-ford"><strong>Algoritmo di Bellman Ford</strong></h2><p>L'algoritmo di Bellman Ford è l'algoritmo per trovare il percorso più breve per grafi che possono avere pesi negativi. Questo algoritmo è anche valido per rilevare cicli di pesi negativi visto che l'algoritmo converge verso una soluzione ottimale in O(V*E) passaggi. Se quanto risultato non è ottimale, allora il grafo contiene un ciclo di peso negativo.</p><p>Ecco un'implementazione in &nbsp;Python:</p><pre><code class="language-Python">infinity = 1e10

def bellman_ford(graph, start, end):
    num_vertices = graph.get_num_vertices()
    edges = graph.get_edges()

    distance = [infinity for vertex in range(num_vertices)]
    previous = [None for vertex in range(num_vertices)]

    distance[start] = 0
    for i range(end+1):
        for (u, v) in edges:
            if distance[v] &gt; distance[u] + graph.get_weight(u, v):
                distance[v] = distance[u] + graph.get_weight(u, v)
                previous[v] = u

    for (u,v) in edges:
        if distance[v] &gt; distance[u] + graph.get_weight(u, v):
            raise NegativeWeightCycleError()
    return distance, previous
# 'distance' è la distanza dall'inizio verso quel nodo con il
# percorso più breve, utile per stampare la distanza più breve.
# 'previous' è un array che contiene il nodo che viene prima di 
# quello corrente, utile per stampare il percorso.
</code></pre><h2 id="ricerca-in-profondit-deep-search-first-dfs-"><strong>Ricerca in Profondità - Deep Search First (DFS)</strong></h2><p>È uno dei più semplici algoritmi di grafo. Attraversa il grafo prima verificando il nodo corrente, poi spostandosi verso uno dei suoi successori e ripetendo il processo. Se il nodo corrente non ha successori da verificare, torna indietro al suo predecessore e il processo continua (spostandosi verso un altro successore). Se viene trovata la soluzione la ricerca si arresta.</p><h3 id="implementazione-c-14-"><strong>Implementazione (C++14)</strong></h3><pre><code class="language-c">#include &lt;iostream&gt; 
#include &lt;vector&gt; 
#include &lt;queue&gt;  
#include &lt;algorithm&gt;
using namespace std; 
 
class Graph{ 
   int v;    // numero di vertici 
 
   // puntatore a un vettore che contiene una lista di adiacenze
   vector &lt; int &gt; *adj;
public: 
   Graph(int v);  // Costruttore 
 
   // funzione per aggiugere una connessione tra due nodi (edge)
   void add_edge(int v, int w);  
 
   // stampa l'attraversamento dfs da una data sorgente `s` 
   void dfs();
   void dfs_util(int s, vector &lt; bool&gt; &amp;visited);   
}; 
 
Graph::Graph(int v){ 
   this -&gt; v = v; 
   adj = new vector &lt; int &gt;[v]; 
} 
 
void Graph::add_edge(int u, int v){ 
   adj[u].push_back(v); // aggiunge v alla lista u
   adj[v].push_back(v);  // aggiunge u alla lista v (elimina questa istruzione se il grafo è diretto!)
} 
void Graph::dfs(){
   // vettore dei visitati - per tenere traccia dei visitati durante la ricerca DFS
   vector &lt; bool &gt; visited(v, false);  // marca tutti i nodi/vertici come non visitati
   for(int i = 0; i &lt; v; i++)
       if(!visited[i])
           dfs_util(i, visited);
} 
// nota l'utilizzo di una chiamata per riferimento qui!
void Graph::dfs_util(int s, vector &lt; bool &gt; &amp;visited){ 
   // marca il nodo/vertice corrente come visitato
   visited[s] = true;
    // stampa allo standard output (schermo)
   cout &lt;&lt; s &lt;&lt; " ";
   
   // attraversa la suo lista di adiacenze e chiama ricorsivamente dfs_util per tutti i suoi vicini!
   // (solo se il vicino non è ancora stato visitato!)
   for(vector &lt; int &gt; :: iterator itr = adj[s].begin(); itr != adj[s].end(); itr++)
       if(!visited[*itr])
           dfs_util(*itr, visited); 
} 
 
int main() 
{ 
   // crea un grafo usando la classe Graph definita qui sopra
   Graph g(4); 
   g.add_edge(0, 1); 
   g.add_edge(0, 2); 
   g.add_edge(1, 2); 
   g.add_edge(2, 0); 
   g.add_edge(2, 3); 
   g.add_edge(3, 3); 
 
   cout &lt;&lt; "Il seguente è l'attraversamento Deep First del grafo fornito"
        &lt;&lt; "(a partire dal vertice 0): "; 
   g.dfs(); 
   // il risultato sarebbe: 0 1 2 3
   return 0; 
} 
</code></pre><h3 id="valutazione"><strong>Valutazione</strong></h3><p>Complessità di spazio: O(n)</p><p>Complessità temporale per il caso peggiore O(n). Depth First Search è completo su una serie di nodi finita. Funziona meglio su alberi <em>shallow</em> (vale a dire alberi che hanno una piccola altezza o profondità, cioè un limitato numero di livelli o strati - n.d.t.).</p><h3 id="implementazione-di-dfs-in-c-"><strong>Implementazione di DFS in C++</strong></h3><pre><code class="language-c">#include&lt;iostream&gt;
#include&lt;vector&gt;
#include&lt;queue&gt;

using namespace std;

struct Graph{
	int v;
	bool **adj;
	public:
		Graph(int vcount);
		void addEdge(int u,int v);
		void deleteEdge(int u,int v);
		vector&lt;int&gt; DFS(int s);
		void DFSUtil(int s,vector&lt;int&gt; &amp;dfs,vector&lt;bool&gt; &amp;visited);
};
Graph::Graph(int vcount){
	this-&gt;v = vcount;
	this-&gt;adj=new bool*[vcount];
	for(int i=0;i&lt;vcount;i++)
		this-&gt;adj[i]=new bool[vcount];
	for(int i=0;i&lt;vcount;i++)
		for(int j=0;j&lt;vcount;j++)
			adj[i][j]=false;
}

void Graph::addEdge(int u,int w){
	this-&gt;adj[u][w]=true;
	this-&gt;adj[w][u]=true;
}

void Graph::deleteEdge(int u,int w){
	this-&gt;adj[u][w]=false;
	this-&gt;adj[w][u]=false;
}

void Graph::DFSUtil(int s, vector&lt;int&gt; &amp;dfs, vector&lt;bool&gt; &amp;visited){
	visited[s]=true;
	dfs.push_back(s);
	for(int i=0;i&lt;this-&gt;v;i++){
		if(this-&gt;adj[s][i]==true &amp;&amp; visited[i]==false)
			DFSUtil(i,dfs,visited);
	}
}

vector&lt;int&gt; Graph::DFS(int s){
	vector&lt;bool&gt; visited(this-&gt;v);
	vector&lt;int&gt; dfs;
	DFSUtil(s,dfs,visited);
	return dfs;
}
</code></pre><h2 id="algoritmo-di-floyd-warshall"><strong>Algoritmo di Floyd Warshall</strong></h2><p>È un ottimo algoritmo per trovare la distanza più breve tra tutti i vertici in un grafo. È un algoritmo molto conciso e una complessità temporale di O(V^3) (dove V è il numero dei vertici). Può essere usato con pesi negativi, anche se i cicli di pesi negativi non devono essere presenti nei grafi.</p><h3 id="valutazione-1"><strong>Valutazione</strong></h3><p>Complessità di spazio: O(V^2)</p><p>Complessità temporale per il caso peggiore: O(V^3)</p><h3 id="implementazione-python"><strong>Implementazione Python</strong></h3><pre><code class="language-Python"># Un grande valore come infinito
inf = 1e10 

def floyd_warshall(weights):
    V = len(weights)
    distance_matrix = weights
    for k in range(V):
        next_distance_matrix = [list(row) for row in distance_matrix] # fa una copia della matrice di distanze
        for i in range(V):
            for j in range(V):
                # Sceglie se il vertice k può andare bene come percorso con una distanza minore
                next_distance_matrix[i][j] = min(distance_matrix[i][j], distance_matrix[i][k] + distance_matrix[k][j])
        distance_matrix = next_distance_matrix # update
    return distance_matrix

# Un grafo rappresentato come matrice delle adiacenze 
graph = [
    [0, inf, inf, -3],
    [inf, 0, inf, 8],
    [inf, 4, 0, -2],
    [5, inf, 3, 0]
]

print(floyd_warshall(graph))
</code></pre><h2 id="ricerca-in-ampiezza-breadth-first-search-bfs-"><strong>Ricerca in Ampiezza - Breadth First Search (BFS)</strong></h2><p>È uno degli algoritmi di grafo più semplici. Attraversa il grafo prima verificando il nodo corrente, quindi espandendosi aggiungendo i suoi successori al livello successivo. Questo processo viene ripetuto per tutti i nodi nel livello corrente prima di spostarsi a quello successivo. Se la soluzione viene trovata, la ricerca si arresta.</p><h3 id="visualizzazione"><strong>Visualizzazione</strong></h3><figure class="kg-card kg-image-card"><img src="https://upload.wikimedia.org/wikipedia/commons/4/46/Animated_BFS.gif" class="kg-image" alt="Animated_BFS" width="600" height="400" loading="lazy"></figure><h3 id="valutazione-2"><strong>Valutazione</strong></h3><p>Complessità di spazio: O(n)</p><p>Complessità per il caso peggiore: O(n)</p><p>La Ricerca in Ampiezza è completa su un insieme finito di nodi e ottimale se il costo dello spostamento da un nodo all'altro è costante.</p><h3 id="codice-c-per-l-implementazione-di-bfs"><strong>Codice C++ per l'implementazione di BFS </strong></h3><pre><code class="language-c">// Programma per stampre l'attraversamento BFS da un dato
// vertice sorgente. BFS(int s) attraversa i vertici
// raggiungibili da s
#include&lt;iostream&gt; 
#include &lt;list&gt; 
  
using namespace std; 
  
// Questa classe rappresenta un grafo diretto usando una
// rappresentazione con lista di adiacenze
class Graph 
{ 
    int V;    // No. of vertices 
  
    // Puntatore a un array che contiene una lista di adiacenze
    list&lt;int&gt; *adj;    
public: 
    Graph(int V);  // Costruttore
  
    // funczione per aggiungere una connessione tra due nodi (edge) al grafo
    void addEdge(int v, int w);  
  
    // stampa l'attraversamento BFS da una data sorgente s
    void BFS(int s);   
}; 
  
Graph::Graph(int V) 
{ 
    this-&gt;V = V; 
    adj = new list&lt;int&gt;[V]; 
} 
  
void Graph::addEdge(int v, int w) 
{ 
    adj[v].push_back(w); // Add w to v’s list. 
} 
  
void Graph::BFS(int s) 
{ 
    // marca tutti i vertici come non visitati
    bool *visited = new bool[V]; 
    for(int i = 0; i &lt; V; i++) 
        visited[i] = false; 
  
    // Crea una coda per BFS 
    list&lt;int&gt; queue; 
  
    // Marca il nodo corrente come visitato e lo accoda
    visited[s] = true; 
    queue.push_back(s); 
  
    // 'i' verrà usato per ottenere tutti i vertici 
    // adiacenti di un vertice
    list&lt;int&gt;::iterator i; 
  
    while(!queue.empty()) 
    { 
        // Fa uscire un vertice dalla code e lo stampa
        s = queue.front(); 
        cout &lt;&lt; s &lt;&lt; " "; 
        queue.pop_front(); 
  
        // Ottiene tutti i vertici adiacenti di quello 
        // tolto dalla coda s. Se una adiacente non è stato
        // visitato, viene marcato come visitato e accodato
        for (i = adj[s].begin(); i != adj[s].end(); ++i) 
        { 
            if (!visited[*i]) 
            { 
                visited[*i] = true; 
                queue.push_back(*i); 
            } 
        } 
    } 
} 
  
// Programma per testare i metodi della classe Graph
int main() 
{ 
    // Crea un grafo dato il diagramma seguente
    Graph g(4); 
    g.addEdge(0, 1); 
    g.addEdge(0, 2); 
    g.addEdge(1, 2); 
    g.addEdge(2, 0); 
    g.addEdge(2, 3); 
    g.addEdge(3, 3); 
  
    cout &lt;&lt; "Il seguente è un attraversamento Breadth First Traversal "
         &lt;&lt; "(a partire dal vertice 2) \n"; 
    g.BFS(2); 
  
    return 0; 
}
</code></pre><h2 id="algoritmo-di-dijkstra"><strong>Algoritmo di Dijkstra</strong></h2><p>È un algoritmo presentato da E.W. Dijkstra. Cerca la sorgente del percorso più breve in un grafo con connessioni (edge) non negative (perché?).</p><p>Creiamo 2 array: <code>visited</code> e <code>distance</code>, che registrano rispettivamente se un vertice è visitato e qual è la distanza minima dal vertice sorgente. Inizialmente l'array <code>visited</code> viene assegnato con valori <code>false</code> e <code>distance</code> come infinito.</p><p>Partiamo dal vertice sorgente, che sarà <code>u</code>, e i suoi vertici adiacenti <code>v</code>. Ora per ogni <code>v</code> che è adiacente a <code>u</code>, la distanza è aggiornata se non è stato visitato prima e la distanza da <code>u</code> è minore della distanza corrente. Poi selezioniamo il vertice successivo con la distanza minore e che non sia stato ancora visitato.</p><p>Una coda di priorità è spesso usata per soddisfare quest'ultimo requisito nel minor tempo. Qui sotto un'implementazione della stessa idea usando <code>PriorityQueue</code> in Java.</p><pre><code class="language-Java">import java.util.*;
public class Dijkstra {
    class Graph {
	LinkedList&lt;Pair&lt;Integer&gt;&gt; adj[];
	int n; // Numero di vertici.
	Graph(int n) {
	    this.n = n;
	    adj = new LinkedList[n];
	    for(int i = 0;i&lt;n;i++) adj[i] = new LinkedList&lt;&gt;();
	}
	// aggiunge una connessione tra i vertici a e b con il costo come peso	public void addEdgeDirected(int a, int b, int cost) {
	    adj[a].add(new Pair(b, cost));
	}
	public void addEdgeUndirected(int a, int b, int cost) {
	    addEdgeDirected(a, b, cost);
	    addEdgeDirected(b, a, cost);
	}
    }
    class Pair&lt;E&gt; {
	E first;
	E second;
	Pair(E f, E s) {
	    first = f;
	    second = s;
	}
    }

    // Comparator per ordinare gli oggetti Pairs nella Priority Queue
    class PairComparator implements Comparator&lt;Pair&lt;Integer&gt;&gt; {
	public int compare(Pair&lt;Integer&gt; a, Pair&lt;Integer&gt; b) {
	    return a.second - b.second;
	}
    }

    // Calola il percorso più corto dalla sorgente per ogni vertice e ritorna la distanza
    public int[] dijkstra(Graph g, int src) {
	int distance[] = new int[g.n]; // la distanza più breve di ciascun vertice da src
	boolean visited[] = new boolean[g.n]; // il vertice è visitato o no
	Arrays.fill(distance, Integer.MAX_VALUE);
	Arrays.fill(visited, false);
	PriorityQueue&lt;Pair&lt;Integer&gt;&gt; pq = new PriorityQueue&lt;&gt;(100, new PairComparator());
        pq.add(new Pair&lt;Integer&gt;(src, 0));
	distance[src] = 0;
	while(!pq.isEmpty()) {
	    Pair&lt;Integer&gt; x = pq.remove(); // Estrae il vertice con la distanza più breve da src
	    int u = x.first;
	    visited[u] = true;
	    Iterator&lt;Pair&lt;Integer&gt;&gt; iter = g.adj[u].listIterator();
	    // Itera sui vicini di u e aggiorna le loro distanze
	    while(iter.hasNext()) {
		Pair&lt;Integer&gt; y = iter.next();
		int v = y.first;
		int weight = y.second;
		// Verifica se il vertice v non è visitato
		// Se il nuovo percorso attraverso u offre un costo minore allora aggiorna l'array distance e aggiungi a pq
		if(!visited[v] &amp;&amp; distance[u]+weight&lt;distance[v]) {
		    distance[v] = distance[u]+weight;
		    pq.add(new Pair(v, distance[v]));
		}
	    }
	}
	return distance;
    }

    public static void main(String args[]) {
	Dijkstra d = new Dijkstra();
	Dijkstra.Graph g = d.new Graph(4);
	g.addEdgeUndirected(0, 1, 2);
	g.addEdgeUndirected(1, 2, 1);
	g.addEdgeUndirected(0, 3, 6);
	g.addEdgeUndirected(2, 3, 1);
	g.addEdgeUndirected(1, 3, 3);

	int dist[] = d.dijkstra(g, 0);
	System.out.println(Arrays.toString(dist));
    }
}
</code></pre><h2 id="algoritmo-di-ford-fulkerson"><strong>Algoritmo di Ford Fulkerson</strong></h2><p>Questo algoritmo risolve il problema del massimo flusso del grafo. Trova la migliore soluzione di flusso tramite le connessioni (edge) dei grafi in modo da ottenere il massimo flusso in uscita dall'altra parte. La sorgente ha una specifica velocità di input e ciascuna connessione ha un peso associato a essa che è la quantità massima che può passare attraverso quella connessione.</p><p>L'algoritmo di Ford Fulkerson è anche chiamato algoritmo di Edmund-Karp visto che le specifiche complete sono state fornite da Jack Edmonds e Richard Karp.</p><p>Funziona creando percorsi di aumento, ovvero percorsi dalla sorgente all'uscita che hanno un flusso non zero. Facciamo passare il flusso attraverso i percorsi e aggiorniamo i limiti. Ciò può portare a situazioni in cui non abbiamo più mosse disponibili. Ecco dove la capacità di "annullamento" di questo algoritmo gioca un ruolo importante. In caso di blocco, diminuiamo il flusso e apriamo la connessione per far passare la nostra sostanza corrente.</p><h2 id="passaggi"><strong>Passaggi</strong></h2><ol><li>Imposta il flusso a zero per tutte le connessioni.</li><li>Fintanto che c'è un percorso dalla sorgente allo scarico:</li><li>Cerca il peso minimo sul percorso, che associ a &nbsp;<code>limit</code> .</li><li>Trova tutte le connessioni (<code>u</code>, <code>v</code>) sul percorso e:<br>1. Aggiungi &nbsp;<code>limit</code> &nbsp;al flusso da <code>u</code> a <code>v</code>. (Per la mossa corrente)<br>2. Sottrai &nbsp;<code>limit</code> &nbsp;dal flusso da <code>v</code> a <code>u</code> (Per un annullamento della mossa in futuro)</li></ol><h3 id="valutazione-3"><strong>Valutazione</strong></h3><p>Complessità temporale: <code>O(V*E^2)</code></p><h3 id="implementazione-python-1"><strong>Implementazione Python</strong></h3><pre><code class="language-Python"># Un numero grande per infinito
inf = 1e10

def maximum_flow(graph, source, sink):
  max_flow = 0
  parent = bfs(graph, source, sink)
  while path:
    limit = inf
    v = sink
    while v != source:
        u = parent[s]
        path_flow = min(limit, graph[u][v])
        v = parent[v]
    max_flow += path_flow

    v = sink
    while v != source:
        u = parent[v]
        graph[u][v] -= path_flow
        graph[v][u] += path_flow
        v = parent[v]

    path = bfs(graph, source, sink)
  return max_flow</code></pre> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cos'è la Notazione O Grande: Complessità Spaziale e Temporale ]]>
                </title>
                <description>
                    <![CDATA[ Sai davvero cos'è la notazione O grande? Se lo sai, allora questo sarà un ripasso in previsione di un colloquio di lavoro. In caso contrario, non preoccuparti – unisciti a me in questo viaggio nell'informatica. Se hai frequentato dei corsi riguardanti gli algoritmi, hai probabilmente sentito il termine notazione O ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/cose-la-notazione-o-grande-complessita-spaziale-e-temporale/</link>
                <guid isPermaLink="false">63400f12271b000641be48f7</guid>
                
                    <category>
                        <![CDATA[ Algoritmi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dario Di Cillo ]]>
                </dc:creator>
                <pubDate>Wed, 14 Dec 2022 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2022/10/0_NSxbYAwcC7Qzk7PP.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/big-o-notation-why-it-matters-and-why-it-doesnt-1674cfa8a23c/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">What is Big O Notation Explained: Space and Time Complexity</a>
      </p><p>Sai davvero cos'è la notazione O grande? Se lo sai, allora questo sarà un ripasso in previsione di un colloquio di lavoro. In caso contrario, non preoccuparti – unisciti a me in questo viaggio nell'informatica.</p><p>Se hai frequentato dei corsi riguardanti gli algoritmi, hai probabilmente sentito il termine <strong>notazione O grande</strong>. Se non ne hai mai sentito parlare, lo faremo a breve, per poi raggiugere un comprensione più approfondita di cosa è realmente.</p><p>La notazione O grande è uno degli strumenti fondamentali a disposizione degli informatici, così come per gli ingegneri del software, per analizzare il costo di un algoritmo.</p><p>Questo articolo è scritto con il presupposto che tu abbia già avuto a che fare con del codice. Inoltre, alcuni materiali approfonditi richiedono conoscenze di base di matematica a livello di scuola superiore, quindi possono essere poco adatti a principianti assoluti. Ma sei ti senti pronto, iniziamo!</p><p>In questo articolo, avremo una discussione approfondita riguardo alla notazione O grande. Inizieremo con un esempio di algoritmo per iniziare a capirne qualcosa. Poi passeremo alla matematica per ottenere una comprensione più formale. In seguito parleremo di alcune variazioni comuni di notazione O grande. Alle fine discuteremo di alcune limitazioni di O grande in situazioni pratiche. Di seguito è possibile vedere un sommario.</p><h3 id="sommario"><strong>Sommario</strong></h3><ol><li><a href="#1-cos-la-notazione-o-grande-e-perch-importante">Cos'è la notazione O grande e perché è importante</a></li><li><a href="#2-definizione-formale-di-notazione-o-grande">Definizione formale di notazione O grande</a></li><li><a href="#3-o-grande-o-piccola-omega-e-theta">O grande, o piccola, Omega e Theta</a></li><li><a href="#4-confronto-di-complessit-tra-valori-tipici-di-o-grande">Confronto di complessità tra valori tipici di O grande</a></li><li><a href="#5-complessit-temporale-spaziale">Complessità temporale e spaziale</a></li><li><a href="#6-complessit-attesa-caso-migliore-medio-e-peggiore">Complessità attesa, caso migliore, medio e peggiore</a></li><li><a href="#perch-o-grande-non-conta">Perché O grande non conta</a></li><li><a href="#in-conclusione-">In conclusione…</a></li></ol><p>Iniziamo!</p><h3 id="1-cos-la-notazione-o-grande-e-perch-importante"><strong>1. Cos'è la notazione O grande e perché è importante</strong></h3><blockquote>“La notazione O grande è una notazione matematica che descrive il comportamento asintotico di una funzione quando l'argomento tende a un particolare valore o a infinito. È un membro della famiglia di notazioni inventate da Paul Bachmann, Edmund Landau e altri, collettivamente chiamate notazione Bachmann–Landau o notazione asintotica.”<br><br>— Definizione di notazione O grande da Wikipedia</blockquote><p>In parole povere, la notazione O grande descrive la <strong>complessità </strong>di un codice usando termini algebrici.</p><p>Per capire cos'è la notazione O grande, possiamo dare un'occhiata a un tipico esempio, <strong><strong><em><em>O(n²)</em></em></strong></strong>. La lettera <strong><strong><em><em>“n”</em></em></strong></strong> rappresenta la dimensione dell'input e la funzione <strong><strong><em><em>“g(n) = n²”</em></em></strong></strong> all'interno di <strong><strong><em><em>“O()”</em></em></strong></strong> ci dà un'idea di quanto sia complesso l'algoritmo rispetto alla dimensione dell'input.</p><p>Un tipico algoritmo con complessità pari a O(n²) è l'algoritmo <strong><strong>selection sort</strong></strong>, un algoritmo di ordinamento che itera su una lista per assicurare che ogni elemento all'indice <strong><strong><em><em>i</em></em></strong></strong> sia l'<strong><strong><em><em>i</em></em></strong></strong><em>-esimo</em> elemento più piccolo/grande della lista. Il <strong><strong>CODEPEN</strong></strong> qui sotto fornisce un esempio visuale.</p><figure class="kg-card kg-embed-card"><iframe id="cp_embed_yEpRVr" src="https://codepen.io/iMultiThinker/embed/preview/yEpRVr?height=300&amp;slug-hash=yEpRVr&amp;default-tabs=js,result&amp;host=https://codepen.io" title="Embedded content" scrolling="no" frameborder="0" height="300" allowtransparency="true" class="cp_embed_iframe" loading="lazy" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 22px; vertical-align: middle; width: 760px; overflow: hidden;"></iframe></figure><p>L'algoritmo può essere descritto con il seguente codice. Per assicurare che l'elemento <em>i-esimo</em> sia l'<em>i-esimo</em> elemento più piccolo nella lista (indicato come <em>SmallestElement </em>nello snippet di codice seguente), questo algoritmo itera prima sulla lista con un loop for. Poi, per ogni elemento utilizza un altro loop per trovare l'elemento più piccolo nella parte restante della lista.</p><pre><code class="language-js">SelectionSort(List) {
  for(i from 0 to List.Length) {
    SmallestElement = List[i]
    for(j from i to List.Length) {
      if(SmallestElement &gt; List[j]) {
        SmallestElement = List[j]
      }
    }
    Swap(List[i], SmallestElement)
  }
}</code></pre><p>In questa situazione, consideriamo la variabile <strong><em>L</em><strong><em><em>ist</em></em></strong></strong> come input, dunque la dimensione dell'input n è il <strong>numero di elementi all'interno di List</strong>. Assumendo che l'istruzione if e l'assegnazione del valore legata all'istruzione if necessiti di un tempo costante, possiamo trovare la notazione O grande per la funzione SelectionSort analizzando quante volte vengono eseguite le istruzioni.</p><p>Le istruzioni nel loop interno vengono eseguite n volte. Dopo che<strong><em> i</em></strong> viene incrementata, il loop interno viene eseguito n-1 volte, e così via finché non viene eseguito una volta, prima che entrambi i loop for raggiungano le condizioni di terminazione.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_1ajbPJXjt3z7CofVODlaCw.png" class="kg-image" alt="1_1ajbPJXjt3z7CofVODlaCw" width="600" height="400" loading="lazy"><figcaption>Illustrazione dei loop di Selection Sort&nbsp;</figcaption></figure><p>Alla fine ci ritroviamo con la somma di una serie geometrica, e con un po' di <a href="https://it.wikipedia.org/wiki/1_%2B_2_%2B_3_%2B_4_%2B_%C2%B7_%C2%B7_%C2%B7">matematica da scuole superiori</a> possiamo trovare che il loop interno viene ripetuto 1 + 2... + n volte, ovvero n(n-1)/2 volte. Moltiplicando otteniamo n²/2-n/2.</p><p>Quando calcoliamo la notazione O grande ci interessiamo solo dei <strong>termini dominanti</strong>, senza curarci dei coefficienti. Quindi, prendiamo n² come O grande finale e scriviamo O(n²).</p><p>Ora potresti chiederti perché si riduce tutto al <strong><strong><em><em>“</em></em></strong><em>termine dominante</em><strong><em><em>”</em></em></strong></strong> e perché non ci interessiamo dei coefficienti. Non preoccuparti, affronteremo questi aspetti uno alla volta. Potrebbe essere un po' difficile da capire all'inizio ma diventerà tutto più sensato man mano che leggerai la prossima sezione.</p><h3 id="2-definizione-formale-di-notazione-o-grande"><strong>2. Definizione formale di notazione O grande</strong></h3><p>C'era una volta un re indiano che voleva ricompensare un uomo saggio per la sua bravura. Il saggio chiese nient'altro che il grano che potesse riempire una scacchiera.</p><p>Ma queste erano le sue regole: nella prima casella voleva 1 chicco di grano, poi 2 nella seconda casella, 4 nella successiva e così via, in modo che ogni casella della scacchiera fosse riempita con una quantità di grano pari al doppio della precedente. Il re ingenuo acconsentì senza esitazione, pensando che sarebbe stata una domanda banale da soddisfare, finché non provò a farlo...</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/italian/news/content/images/2022/12/image.png" class="kg-image" alt="image" srcset="https://www.freecodecamp.org/italian/news/content/images/size/w600/2022/12/image.png 600w, https://www.freecodecamp.org/italian/news/content/images/2022/12/image.png 800w" sizes="(min-width: 720px) 720px" width="800" height="594" loading="lazy"><figcaption>Grano e scacchiera, immagine da <a href="https://en.wikipedia.org/wiki/Wheat_and_chessboard_problem#/media/File:Wheat_and_chessboard_problem.jpg">Wikipedia</a></figcaption></figure><p>Quindi, quanti chicchi di grano doveva il re al saggio? Sappiamo che una scacchiera contiene 8 caselle per 8 caselle, per un totale di 64, dunque la casella finale dovrebbe contenere un totale di 2⁶³ chicchi di grano. Se fai il calcolo, otterrai 1.8446744*10¹⁹, ovvero circa 18 seguito da 18 zeri. Assumendo che ogni chicco di grano pesi 0.01 grammi, il totale è 184,467,440,737 tonnellate di grano. E 184 miliardi di tonnellate sono parecchie, non è vero?</p><p>Da un certo punto, i numeri crescono piuttosto velocemente per via dell'andamento esponenziale. La stessa logica vale per gli algoritmi informatici. Se l'impegno richiesto per svolgere un'azione cresce esponenzialmente con la dimensione dell'input, può finire per diventare estremamente grande. &nbsp; </p><p>Come vedremo a breve, la crescita di 2ⁿ è molto più rapida rispetto a n². Con n = 64, il quadrato di 64 è 4096. Se aggiungi questo numero a 2⁶⁴, verrà perso al di fuori delle cifre significative.</p><p>Ecco perché, quando ci interessa la velocità di crescita, ci concentriamo sui termini dominanti. E dato che vogliamo analizzare la crescita rispetto alle dimensioni dell'input, i coefficienti che moltiplicano solo il numero, piuttosto che crescere con le dimensioni dell'input, non contengono informazioni utili.</p><p>Qui sotto puoi vedere la definizione formale di O grande:</p><!--kg-card-begin: markdown--><p>f(N) = O(g(N)), se esistono costanti positive c, N<sub>0</sub> tali che<br>
f(N) ≤ c · g(N) per ogni N ≥ N<sub>0</sub></p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/italian/news/content/images/2022/12/image-1.png" class="kg-image" alt="image-1" srcset="https://www.freecodecamp.org/italian/news/content/images/size/w600/2022/12/image-1.png 600w, https://www.freecodecamp.org/italian/news/content/images/2022/12/image-1.png 800w" sizes="(min-width: 720px) 720px" width="800" height="600" loading="lazy"><figcaption><a href="https://slideplayer.com/slide/9739625/">Slide CSE 373</a> dell'Università di Washington</figcaption></figure><p>La definizione formale è utile quando devi svolgere una prova di matematica. Ad esempio, la complessità temporale di selection sort può essere definita dalla funzione f(n) = n²/2-n/2, come abbiamo discusso nella sezione precedente.</p><p>Se la nostra funzione g(n) è n², possiamo trovare una costante c = 1 e una N₀ = 0, e finché N &gt; N₀, N² sarà sempre più grande di N²/2-N/2. Possiamo provarlo facilmente sottraendo N²/2 da entrambe le funzioni, poi possiamo vedere facilmente che N²/2 &gt; -N/2 è vero quando N &gt; 0. Dunque, possiamo giungere alla conclusione che f(n) = O(n²).</p><p>Potresti aver notato un piccolo trucchetto qui. Ovvero, se g(n) cresce velocemente, molto più veloce di qualsiasi cosa, O(g(n)) sarà sempre abbastanza grande. Ad esempio, per una funzione polinomiale, puoi avere sempre ragione dicendo che è O(2ⁿ) perché 2ⁿ alla fine supererà qualsiasi polinomio.</p><p>Matematicamente è corretto, ma in generale, quando parli di O grande, occorre conoscere l'<strong>estremo</strong> della funzione. Ne capirai di più leggendo la prossima sezione.</p><p>Ma prima di andare avanti, testiamo la tua comprensione con la seguente domanda. La risposta può essere trovata nelle sezioni successive.</p><blockquote><strong>Domanda<strong>:</strong></strong> un'immagine è rappresentata da un array 2D di pixel. Se utilizzi un loop for annidato per iterare su ogni pixel (ovvero, hai un loop for che itera su tutte le colonne e un altro loop for che itera su tutte le righe), qual è la complessità temporale dell'algoritmo quando l'immagine è considerata come input?</blockquote><h3 id="3-o-grande-o-piccola-omega-e-theta"><strong>3. O grande, o piccola, Omega e Theta</strong></h3><blockquote>O grande: “f(n) è O(g(n))” se e solo se per delle costanti c e N₀, f(N) ≤ cg(N) per ogni N &gt; N₀<br><br>Omega: “f(n) è Ω(g(n))” se e solo se per delle costanti c e N₀, f(N) ≥ cg(N) per ogni N &gt; N₀<br><br>Theta: “f(n) è Θ(g(n))” se e solo se f(n) è O(g(n)) e f(n) è Ω(g(n))<br><br>o piccola: “f(n) è o(g(n))” se e solo se f(n) è O(g(n)) e f(n) non è Θ(g(n))<br><br>— Definizione formale di O grande, omega, theta e o piccola</blockquote><p>In parole povere:</p><ul><li><strong><strong>O</strong> grande<strong> (O())</strong></strong> descrive il <strong>limite superiore</strong> della complessità.</li><li><strong><strong>Omega (Ω())</strong></strong> descrive il <strong>limite inferiore</strong> della complessità.</li><li><strong><strong>Theta (Θ())</strong></strong> descrive il <strong>limite asintotico stretto </strong>della complessità.</li><li><strong>o piccola<strong> (o())</strong></strong> descrive il <strong>limite superiore escludendo il limite stretto</strong>.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_O-dcXbYXojkAPEnDuVZMvA.png" class="kg-image" alt="1_O-dcXbYXojkAPEnDuVZMvA" width="600" height="400" loading="lazy"><figcaption>Illustrazione delle relazioni tra O grande, o piccola, Omega &amp; Theta</figcaption></figure><p>Ad esempio, la funzione g(n) = n² + 3n è O(n³), o(n⁴), Θ(n²) e Ω(n). Ma sarebbe ancora corretto se dicessi che è Ω(n²) oppure O(n²).</p><p>In generale, quando parliamo di O grande, ciò intendiamo in realtà è theta. È piuttosto insensato quando il limite superiore è molto più grande dell'ambito dell'analisi. Sarebbe un po' come risolvere diseguaglianze mettendo ∞ dal lato più grande, cosa che ti farebbe avere ragione praticamente quasi sempre.</p><p>Ma come determiniamo quali funzioni sono più complesse di altre? Lo scoprirai nella prossima sezione e imparandolo nel dettaglio.</p><h3 id="4-confronto-di-complessit-tra-valori-tipici-di-o-grande"><strong>4. Confronto di complessità tra valori tipici di O grande</strong></h3><p>Se tentiamo di calcolare la O grande per una particolare funzione g(n), ci interessa solo il <strong>termine dominante</strong> della funzione. Il termine dominante è il termine che cresce più velocemente.</p><p>Ad esempio, n² cresce più velocemente di n, quindi se abbiamo qualcosa come g(n) = n² + 5n + 6, sarà O(n²). Se hai seguito qualche corso di analisi, tutto ciò è molto simile alla scorciatoia per trovare il limite per polinomi frazionari, dove alla fine ti interessa solo il termine dominante per numeratori e denominatori.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/italian/news/content/images/2022/12/image-2.png" class="kg-image" alt="image-2" width="498" height="298" loading="lazy"><figcaption>Un altro modo per considerare O grande, immagine da <a href="https://stackoverflow.com/questions/1364444/difference-between-big-o-and-little-o-notation">Stack Overflow</a>.</figcaption></figure><p>Ma quale funzione cresce più velocemente delle altre? Per determinarlo esistono alcune regole.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/italian/news/content/images/2022/12/image-3.png" class="kg-image" alt="image-3" srcset="https://www.freecodecamp.org/italian/news/content/images/size/w600/2022/12/image-3.png 600w, https://www.freecodecamp.org/italian/news/content/images/2022/12/image-3.png 800w" sizes="(min-width: 720px) 720px" width="800" height="556" loading="lazy"><figcaption>Illustrazione da <a href="https://www.bigocheatsheet.com/">Big O Cheatsheet</a></figcaption></figure><h4 id="1-o-1-ha-la-complessit-minore"><strong>1. O(1) ha la complessità minore</strong></h4><p>Spesso chiamato "<strong>tempo costante</strong>", se crei un algoritmo che risolve un problema in O(1), sei probabilmente al massimo delle tue possibilità. In alcuni casi, la complessità può andare oltre O(1), e puoi analizzarli trovando la sua controparte O(1/g(n)). Ad esempio, O(1/n) è più complessa di O(1/n²).</p><h4 id="2-o-log-n-pi-complessa-di-o-1-ma-meno-complessa-dei-polinomi"><strong>2. O(log(n)) è più complessa di O(1), ma meno complessa dei polinomi</strong></h4><p>Dato che la complessità è spesso correlata agli algoritmi divide et impera, O(log(n)) è generalmente una buona complessità da raggiungere con gli algoritmi di ordinamento. O(log(n)) è meno complessa di O(√n), perché la radice quadrata può essere considerata polinomiale, dove l'esponente è 0.5.</p><h4 id="3-la-complessit-dei-polinomi-cresce-con-il-loro-esponente"><strong>3. La complessità dei polinomi cresce con il loro esponente</strong></h4><p>Ad esempio, O(n⁵) è più complessa di O(n⁴). Grazie a questa semplicità, abbiamo fatto svariati esempi di polinomi nelle sezioni precedenti.</p><h4 id="4-le-funzioni-esponenziali-hanno-una-complessit-maggiore-delle-polinomiali-finch-i-coefficienti-sono-multipli-positivi-di-n"><strong>4. Le funzioni esponenziali hanno una complessità maggiore delle polinomiali finché i coefficienti sono multipli positivi di n</strong></h4><p>O(2ⁿ) è più complesso di O(n⁹⁹), ma O(2ⁿ) è meno complesso di O(1). Generalmente, prendiamo 2 come base per gli esponenziali e i logaritmi perché le cose tendono a essere binarie in informatica, ma gli esponenti possono essere cambiati variando i coefficienti. Quando non specificato, la base dei logaritmi è 2.</p><h4 id="5-i-fattoriali-hanno-una-complessit-maggiore-delle-esponenziali"><strong>5. I fattoriali hanno una complessità maggiore delle esponenziali</strong></h4><p>Se ti interessa il ragionamento, dai un'occhiata alla <a href="https://it.wikipedia.org/wiki/Funzione_Gamma"><strong>funzione gamma</strong></a>, una <strong><a href="https://it.wikipedia.org/wiki/Prolungamento_analitico">continuazione analitica</a></strong> di un fattoriale. Una breve dimostrazione è che sia fattoriali che esponenziali hanno lo stesso numero di moltiplicazioni, ma il numero che viene moltiplicato cresce per i fattoriali, mentre rimane costante per gli esponenziali.</p><h4 id="6-moltiplicare-i-termini"><strong>6. Moltiplicare i termini</strong></h4><p>Moltiplicando, la complessità sarà maggiore di quella originale, ma non più grande del prodotto ottenuto moltiplicando qualcosa di più complesso. Ad esempio, O(n * log(n)) è più complessa di O(n) ma meno complessa di O(n²), perché O(n²) = O(n * n) e n è più complessa di log(n).</p><p>Per metterti alla prova, cerca di ordinare le seguenti funzioni dalla più complessa alla meno complessa. Continuando a leggere potrai trovare la soluzione con spiegazioni dettagliate in una sezione successiva. Alcune sono scelte per essere ingannevoli e potrebbero richiedere conoscenze matematiche approfondite. Man mano che ti avvicini alla soluzione capirai meglio.</p><blockquote><strong>Domanda<strong>:</strong></strong> ordina le seguenti funzioni dalla più complessa alla meno complessa.</blockquote><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/italian/news/content/images/2022/12/image-4.png" class="kg-image" alt="image-4" srcset="https://www.freecodecamp.org/italian/news/content/images/size/w600/2022/12/image-4.png 600w, https://www.freecodecamp.org/italian/news/content/images/2022/12/image-4.png 800w" sizes="(min-width: 720px) 720px" width="800" height="282" loading="lazy"><figcaption>Esempio da <a href="https://www.chegg.com/homework-help/questions-and-answers/problem-ask-refresh-knowledge-asymptotic-notations-rank-following-functions-order-growth-f-q23698273">testo</a></figcaption></figure><blockquote><strong>Soluzione alla domanda della sezione 2<strong>:</strong></strong><br><br>Era volontariamente ideata in modo da essere una domanda insidiosa per metterti alla prova. La domanda ti tenta a rispondere O(n²) perché c'è un loop annidato. Tuttavia, n è la dimensione dell'input. Dato che l'array dell'immagine è l'input e il loop itera su ogni pixel solo una volta, la risposta in realtà è O(n). Nella prossima sezione ci saranno più esempi come questo.</blockquote><h3 id="5-complessit-temporale-spaziale"><strong>5. Complessità temporale &amp; spaziale</strong></h3><p>Finora, abbiamo parlato solo della complessità temporale degli algoritmi, ovvero ci siamo interessati solo al tempo che impiega il programma per completare l'attività. Ma ciò che conta è anche lo spazio che il programma necessita per completare l'attività. La complessità spaziale è correlata a quanta memoria userà il programma, dunque un altro fattore importante da analizzare.</p><p>La complessità spaziale funziona in modo simile alla complessità temporale. Ad esempio, selection sort ha una complessità spaziale di O(1), perché memorizza soltanto un valore minimo e il suo indice come confronto, e lo spazio massimo utilizzato non cresce con la dimensione dell'input.</p><p>Alcuni algoritmi, come bucket sort, hanno una complessità spaziale di O(n), ma sono in grado di ridurre la complessità temporale fino a O(1). Bucket sort ordina l'array creando una lista ordinata di tutti i possibili elementi nell'array, poi incrementa il conteggio ogni volta che incontra un elemento. Alla fine, l'array ordinato sarà la lista ordinata di elementi ripetuti a seconda del loro conteggio.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_GfLWx2TXS55unwqZ5-X26w.png" class="kg-image" alt="1_GfLWx2TXS55unwqZ5-X26w" width="600" height="400" loading="lazy"><figcaption>Illustrazione di bucket sort</figcaption></figure><h3 id="6-complessit-attesa-caso-migliore-medio-e-peggiore"><strong>6. Complessità attesa, caso migliore, medio e peggiore</strong></h3><p>La complessità può anche essere analizzata secondo caso migliore, peggiore, medio e caso atteso</p><p>Prendiamo come esempio<strong> insertion sort</strong>. L'algoritmo itera su tutti gli elementi nella lista. Se un elemento è minore del precedente, lo inserisce indietro, finché non è maggiore dell'elemento che lo precede.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/0*C9ork5K0ay7_CLBv.gif" class="kg-image" alt="0*C9ork5K0ay7_CLBv" width="300" height="180" loading="lazy"><figcaption>Insertion sort illustrato, da <a href="https://en.wikipedia.org/wiki/Insertion_sort">Wikipedia</a></figcaption></figure><p>Se l'array è inizialmente ordinato, non verrà effettuato nessuno scambio. L'algoritmo itererà semplicemente sugli elementi dell'array una volta, ottenendo una complessità di O(n). Dunque, possiamo dire che il caso migliore di complessità temporale di insertion sort è O(n). Una complessità di O(n) è spesso chiamata <strong>complessità lineare</strong>.</p><p>A volte un algoritmo è sfortunato. Quick sort, ad esempio, dovrà scorrere attraverso la lista in un tempo O(n) se gli elementi sono ordinati nel verso opposto, ma in media ordina un array in un tempo O(n * log(n)). Generalmente, quando valutiamo la complessità di un algoritmo, ci interessiamo alle prestazioni nel <strong>caso peggiore</strong>. Parleremo ancora di questo e di quick sort nella prossima sezione.</p><p>La complessità del caso medio descrive la prestazione attesa da un algoritmo. A volte implica il calcolo della probabilità di ogni situazione. Può diventare complicato addentrarsi nei dettagli e perciò non ne discuterò in questo articolo. Qui sotto puoi trovare uno specchietto riassuntivo sulle complessità spaziali e temporali di algoritmi tipici.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/italian/news/content/images/2022/12/image-9.png" class="kg-image" alt="image-9" srcset="https://www.freecodecamp.org/italian/news/content/images/size/w600/2022/12/image-9.png 600w, https://www.freecodecamp.org/italian/news/content/images/2022/12/image-9.png 800w" sizes="(min-width: 720px) 720px" width="800" height="563" loading="lazy"><figcaption><a href="https://www.bigocheatsheet.com/">Fonte</a></figcaption></figure><blockquote><strong>Soluzione alla domanda della sezione 4<strong>:</strong></strong></blockquote><p>Analizzando le funzioni, dovremmo essere in grado di classificare immediatamente i seguenti polinomi dal più complesso al meno complesso con la regola 3. Dove la radice quadrata di n corrisponde semplicemente all'elevamento all'esponente 0.5.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_RKlbisO36urUbi237TjyrQ.png" class="kg-image" alt="1_RKlbisO36urUbi237TjyrQ" width="600" height="400" loading="lazy"></figure><p>Poi, applicando le regole 2 e 6, otteniamo quanto segue. Il logaritmo in base 3 può essere trasformato in base 2 con un <a href="https://www.youmath.it/lezioni/analisi-matematica/le-funzioni-elementari-e-le-loro-proprieta/1012-cambiamento-di-base-per-i-logaritmi.html">cambiamento di base</a>. Il logaritmo in base 3 cresce un po' più lentamente di quelli in base 2 e quindi viene posizionato dopo.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_6R1jrWMGXpKxBqtEre9q8Q.png" class="kg-image" alt="1_6R1jrWMGXpKxBqtEre9q8Q" width="600" height="400" loading="lazy"></figure><p>Il resto potrebbe sembrare un po' complicato, ma tentiamo di svelare l'arcano e vedere dove possiamo inserire le funzioni rimanenti.</p><p>Prima di tutto, 2 elevato a 2 alla n è maggiore di 2 alla n, e il +1 concorre a un aumento ulteriore.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_eGLwpHDUJtr6CuALrpcQ2w.png" class="kg-image" alt="1_eGLwpHDUJtr6CuALrpcQ2w" width="600" height="400" loading="lazy"></figure><p>E dato che sappiamo che 2 elevato a log(n) in base 2 è uguale a n, possiamo convertire le seguenti funzioni. Il logaritmo con esponente 0.001 cresce un po' di più di una costante, ma meno di quasi ogni altra cosa.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_4yo7najRBY_OaTnDpT3cIg.png" class="kg-image" alt="1_4yo7najRBY_OaTnDpT3cIg" width="600" height="400" loading="lazy"></figure><p>La funzione con n alla potenza di log(log(n)), in realtà è una variazione della <a href="https://it.wikipedia.org/wiki/Complessit%C3%A0_temporale#Tempo_quasi-polinomiale"><strong>quasi-polinomiale</strong></a>, che è maggiore della polinomiale ma minore dell'esponenziale. Dato che log(n) cresce più lentamente di n, la complessità è leggermente inferiore. La funzione con il reciproco del logaritmo converge a una costante, dato che 1/log(n) diverge verso infinito.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_ZYUCFuiSbOibqdSfmuwdvA.png" class="kg-image" alt="1_ZYUCFuiSbOibqdSfmuwdvA" width="600" height="400" loading="lazy"></figure><p>I fattoriali possono essere rappresentati con delle moltiplicazioni e quindi possono essere convertiti in addizioni fuori dalla funzione logaritmica. "n su 2" può essere convertito in un polinomio con il termine più grande cubico.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_cbrjlMGsWYCs36u831pLTA.png" class="kg-image" alt="1_cbrjlMGsWYCs36u831pLTA" width="600" height="400" loading="lazy"></figure><p>E finalmente, possiamo ordinare le funzioni dalla più complessa alla meno complessa.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_NHVggTVMGjGOe7SxtSgIpQ.png" class="kg-image" alt="1_NHVggTVMGjGOe7SxtSgIpQ" width="600" height="400" loading="lazy"></figure><h3 id="perch-o-grande-non-conta"><strong>Perché O grande non conta</strong></h3><blockquote><strong><strong>!!! — </strong>ATTENZIONE <strong>— !!!</strong></strong><br><br>I contenuti discussi qui, <strong>non sono generalmente accettati</strong> dalla maggior parte dei programmatori nel mondo. Parlane <strong>a tuo rischio </strong>durante un colloquio. Alcune persone riportano che hanno <strong>fallito </strong>il loro colloquio di lavoro perché hanno messo in discussione l'autorità, come qui.<br><br><strong><strong>!!! — </strong>ATTENZIONE <strong>— !!!</strong></strong></blockquote><p>Dato che abbiamo precedentemente imparato che il caso peggiore di complessità temporale per quick sort è O(n²), mentre per merge sort è O(n * log(n)), merge sort dovrebbe essere più veloce, vero? Hai probabilmente indovinato che la risposta è falso.</p><p>Per dimostrarlo, dai un'occhiata a questo <a href="https://trinket.io/python/87a3166026">trinket.io</a> che ho realizzato. Confronta il tempo per quick sort e merge sort. L'ho testato solo su array con una lunghezza fino a 10000, ma come puoi vedere, il tempo per merge sort cresce più velocemente che per quick sort. Nonostante quick sort abbia una complessità nel caso peggiore di O(n²), la probabilità è davvero bassa. Quando si tratta dell'aumento di velocità, quick sort prevale su merge sort che è vincolato alla complessità O(n * log(n)), e quick sort finisce per avere delle prestazioni migliori in media.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_UvDTlLjNnQurODtnCWjEJg.png" class="kg-image" alt="1_UvDTlLjNnQurODtnCWjEJg" width="600" height="400" loading="lazy"><figcaption>Confronto di tempi tra Quick Sort &amp; Merge Sort</figcaption></figure><p>Ho anche realizzato il grafico qui sotto per confrontare il rapporto tra il tempo che impiegano, dato che è difficile vedere i loro valori. Come puoi vedere, il tempo percentuale impiegato per quick sort è in ordine discendente.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_Zdm_8c-uU5941r7zJd4FPQ.png" class="kg-image" alt="1_Zdm_8c-uU5941r7zJd4FPQ" width="600" height="400" loading="lazy"><figcaption>Time Ratio between Quick Sort &amp; Merge Sort</figcaption></figure><p>La morale della favola è che la notazione O grande è solo un'analisi matematica per fornire un riferimento alle risorse richieste da un algoritmo. Nella pratica, i risultati possono essere differenti, ma in generale, è una buona pratica tentare di abbattere la complessità di un algoritmo, finché non ci imbattiamo in un caso in cui sappiamo cosa stiamo facendo.</p><h3 id="in-conclusione-"><strong>In conclusione…</strong></h3><p>Mi piace programmare, imparare cose nuove e condividerle con la comunità. Se c'è qualcosa che ti interessa particolarmente, per favore, fammelo sapere. Generalmente scrivo di web design, architettura del software, matematica e scienza dei dati. Puoi trovare alcuni articoli validi che ho scritto in precedenza se sei interessato a questi argomenti.</p><p>Buon divertimento con lo studio dell'informatica!!!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Gli algoritmi Divide et Impera ]]>
                </title>
                <description>
                    <![CDATA[ Cosa sono gli algoritmi divide et impera? Divide et impera è un paradigma algoritmico, simile al metodo Greedy o alla programmazione dinamica. Un tipico algoritmo divide et impera risolve problemi usando i tre step seguenti.  1. Divide: suddivisione del problema dato in sotto-problemi dello stesso tipo.    ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/gli-algoritmi-divide-et-impera/</link>
                <guid isPermaLink="false">62c6d0687e58e706f822db08</guid>
                
                    <category>
                        <![CDATA[ Algoritmi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dario Di Cillo ]]>
                </dc:creator>
                <pubDate>Thu, 21 Jul 2022 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2022/07/divide.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/divide-and-conquer-algorithms/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Divide and Conquer Algorithm   Meaning: Explained with Examples</a>
      </p><h2 id="cosa-sono-gli-algoritmi-divide-et-impera"><strong>Cosa sono gli algoritmi divide et impera?</strong></h2><p><em>Divide et impera</em> è un paradigma algoritmico, simile al metodo Greedy o alla programmazione dinamica. Un tipico algoritmo divide et impera risolve problemi usando i tre step seguenti.</p><ol><li><strong><strong>Divide</strong></strong>: suddivisione del problema dato in sotto-problemi dello stesso tipo. I sotto-problemi dovrebbero essere parte del problema originario. Questo passaggio avviene con un approccio ricorsivo, dividendo il problema finché non è possibile una suddivisione ulteriore dei sotto-problemi. In questo stadio, i sotto-problemi non sono ulteriormente scindibili ma rappresentano ancora una parte del problema reale.</li><li><strong>Impera</strong>: risoluzione ricorsiva dei sotto-problemi. Questo passaggio richiede di risolvere un numero elevato di sotto-problemi. Generalmente, a questo livello, i problemi vengono considerati risolti singolarmente.</li><li><strong><strong>Combin</strong>a</strong>: combinazione appropriata delle risposte. Quando i sotto-problemi più piccoli sono risolti, vengono combinati ricorsivamente fino a formulare una soluzione del problema di partenza. Questo approccio algoritmico funziona ricorsivamente, e gli step <strong>impera</strong> e <strong>combina</strong> sono così vicini che appaiono come uno solo.</li></ol><p>Questo metodo, di solito, ci permette di ridurre considerevolmente la complessità temporale.</p><p>Ad esempio, Bubble Sort ha una complessità di <code>O(n<sup>2</sup>)</code>, mentre quicksort (un'applicazione di divide et impera) riduce la complessità temporale a <code>O(nlog(n))</code>. L'algoritmo di ricerca lineare ha una complessità temporale di <code>O(n)</code>, mentre quello di ricerca binaria (un'applicazione di divide et impera) riduce la complessità temporale a <code>O(log(n))</code>.</p><p>Di seguito, sono elencati alcuni algoritmi standard del tipo divide et impera.</p><p><strong>Ricerca binaria</strong> è un algoritmo di ricerca. In ogni step, l'algoritmo confronta l'elemento di input (x) con il valore al centro dell'array. Se i valori coincidono, allora l'algoritmo restituisce l'indice corrispondente. Altrimenti, se x è minore dell'elemento centrale, l'algoritmo passa al lato sinistro dell'elemento centrale. In caso contrario, passa al lato destro.</p><p><strong><strong>Quicksort</strong></strong> è un algoritmo di ordinamento. L'algoritmo riordina gli elementi di un array in modo che tutti gli elementi più piccoli di un elemento, detto pivot, vengono spostati alla sinistra di quest'ultimo, e tutti quelli maggiori alla sua destra. Infine, l'algoritmo ordina ricorsivamente i sotto-array a sinistra e a destra del pivot.</p><p><strong><strong>Merge Sort</strong></strong> è un altro algoritmo di ordinamento. L'algoritmo divide l'array in due metà e le ordina ricorsivamente, per poi unire le due metà ordinate. La complessità temporale è <code>O(nLogn)</code>, che sia il caso migliore, medio o peggiore. La sua complessità può essere compresa facilmente dato che la ricorrenza è: <code>T(n) = 2T(n/2) + n</code>.</p><p><strong>Coppia di punti più vicini.</strong> Il problema è trovare la coppia di punti più vicini tra un insieme di punti in un piano x-y e può essere risolto in un tempo <code>O(n<sup>2</sup>)</code>, calcolando le distanze di ogni coppia di punti e confrontando le distanze per trovare quella minore. L'algoritmo divide et impera risolve il problema in un tempo <code>O(nLogn)</code>.</p><p><strong>L'algoritmo di <strong>Strassen</strong></strong> è un efficiente algoritmo per moltiplicare matrici. Un metodo semplice per moltiplicare due matrici necessita di 3 loop annidati e di un tempo <code>O(n<sup>3</sup>)</code>. L'algoritmo di Strassen moltiplica due matrici in un tempo <code>O(n<sup>2.8974</sup>)</code>.</p><p><strong>L'algoritmo di <strong>Cooley–Tukey </strong>a trasformata di Fourier veloce<strong> (FFT) </strong></strong>è l'algoritmo FFT più comune. È un algoritmo divide et impera che opera in un tempo <code>O(nlogn)</code>.</p><p><strong>L'algoritmo di<strong> Karatsuba </strong></strong>è stato il primo algoritmo di moltiplicazione asintoticamente più veloce degli algoritmi quadratici. Riduce la moltiplicazione di due numeri a n cifre al massimo a n<sup>1.585</sup> (approssimativamente <code>log<sub>2</sub>(3)</code>) moltiplicazioni a una cifra. È quindi pià veloce dell'algoritmo classico, il quale richiede n<sup>2</sup> algoritmi a una cifra.</p><h3 id="divide-et-impera-vs-programmazione-dinamica"><strong>Divide et impera vs programmazione dinamica</strong></h3><p>Entrambi i paradigmi (divide et impera e programmazione dinamica) dividono un dato problema in sotto-problemi e risolvono i sotto-problemi. Come scegliere tra i due? Divide et impera dovrebbe essere utilizzato quando gli stessi sotto-problemi non vengono valutati molte volte. Altrimenti dovrebbe essere usata la programmazione dinamica o la memoizzazione. </p><p>Ad esempio, l'algoritmo di ricerca binaria è un algoritmo divide et impera, in cui non viene mai rivalutato lo stesso sotto-problema. Invece, per calcolare l'n-esimo numero di Fibonacci, è da preferire la programmazione dinamica.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Algoritmi di ordinamento con esempi in JavaScript, Python, Java e C++ ]]>
                </title>
                <description>
                    <![CDATA[ Cos'è un algoritmo di ordinamento? Gli algoritmi di ordinamento sono un insieme di istruzioni che prendono un array o una lista come input e ne riorganizzano gli elementi in un ordine particolare. I più comuni operano in ordine numerico o in una forma di ordine alfabetico (lessicografico) e possono ordinare ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/algoritmi-di-ordinamento-con-esempi-in-javascript-python-java-e-c/</link>
                <guid isPermaLink="false">62bc5ba07e58e706f822c516</guid>
                
                    <category>
                        <![CDATA[ Algoritmi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dario Di Cillo ]]>
                </dc:creator>
                <pubDate>Mon, 18 Jul 2022 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2022/06/sort.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/sorting-algorithms-explained-with-examples-in-python-java-and-c/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Sorting Algorithms Explained with Examples in JavaScript, Python, Java, and C++</a>
      </p><h2 id="cos-un-algoritmo-di-ordinamento"><strong>Cos'è un algoritmo di ordinamento?</strong></h2><p>Gli algoritmi di ordinamento sono un insieme di istruzioni che prendono un array o una lista come input e ne riorganizzano gli elementi in un ordine particolare.</p><p>I più comuni operano in ordine numerico o in una forma di ordine alfabetico (lessicografico) e possono ordinare in modo ascendente (A-Z, 0-9) o discendente (Z-A, 9-0).</p><h2 id="perch-gli-algoritmi-di-ordinamento-sono-importanti"><strong>Perché gli algoritmi di ordinamento sono importanti?</strong></h2><p>Dato che spesso sono in grado di ridurre la complessità di un problema, gli algoritmi di ordinamento sono estremamente importanti in informatica. Questi algoritmi hanno applicazioni dirette in algoritmi di ricerca, algoritmi di database, metodi divide et impera, algoritmi di strutture dati e molti altri.</p><h2 id="pro-e-contro-degli-algoritmi-di-ordinamento"><strong>Pro e contro degli algoritmi di ordinamento</strong></h2><p>Nello scegliere un algoritmo di ordinamento, occorre che ci poniamo qualche domanda. Quanto è grande l'insieme da ordinare? Quanta memoria è disponibile? Dobbiamo aggiungere altri dati in futuro?</p><p>Le risposte a queste domande possono determinare quale algoritmo si adatterà al meglio a ogni situazione. Alcuni algoritmi, come il merge sort, possono richiedere molto spazio o memoria per essere eseguiti, mentre l'insertion sort non è sempre il più rapido, ma non necessita di molte risorse per essere eseguito.</p><p>Dovresti determinare quali sono i requisiti nel tuo caso e considerare le limitazioni del tuo sistema prima di decidere quale algoritmo di ordinamento usare.</p><h2 id="algoritmi-di-ordinamento-comuni"><strong>Algoritmi di ordinamento comuni</strong></h2><p>Ecco alcuni dei più comuni algoritmi di ordinamento:</p><ul><li>Selection sort (ordinamento per selezione)</li><li>Bubble sort (ordinamento a bolla)</li><li>Insertion sort (ordinamento a inserimento)</li><li>Merge sort </li><li>Quick sort</li><li>Heapsort (ordinamento per heap)</li><li>Counting sort</li><li>Radix sort</li><li>Bucket sort</li></ul><p>Ma prima di affrontarli uno per uno, parliamo un po' di come classificare gli algoritmi di ordinamento.</p><h2 id="classificazione-degli-algoritmi-di-ordinamento"><strong>Classificazione degli algoritmi di ordinamento</strong></h2><p>Gli algoritmi di ordinamento possono essere categorizzati in base ai seguenti parametri:</p><ol><li><strong>Il numero richiesto di scambi o inversioni<strong>:</strong></strong> è il numero di volte in cui l'algoritmo scambia gli elementi per riorganizzare l'input. Selection sort richiede il numero minimo di scambi.</li><li><strong>Il numero di confronti<strong>:</strong></strong> è il numero di volte che un algoritmo confronta gli elementi per riorganizzare l'input. Usando la <a href="https://www.freecodecamp.org/italian/news/la-notazione-o-grande-spiegata-con-esempi/">notazione O-grande</a>, gli algoritmi di ordinamento elencati precedentemente richiedono almeno <code>O(nlogn)</code> confronti nel caso migliore e <code>O(n^2)</code> confronti nel caso peggiore per la maggior parte degli output.</li><li><strong>Fanno o non fanno uso della ricorsione<strong>:</strong></strong> &nbsp;alcuni algoritmi di ordinamento, come quick sort, usano tecniche ricorsive per ordinare l'input, Altri algoritmi di ordinamento, come selection sort o insertion sort, usano tecniche non ricorsive. Infine, alcuni come merge sort, usano sia tecniche ricorsive che non per ordinare l'input.</li><li><strong>Sono stabili o instabili<strong>:</strong></strong> gli algoritmi di ordinamento stabili mantengono l'ordine relativo degli elementi con valori o chiavi uguali. Quelli instabili non mantengono l'ordine relativo di elementi con valori/chiavi uguali.<br><br>Ad esempio, immagina di avere l'array di input <code>[1, 2, 3, 2, 4]</code>. Per evidenziare la differenza tra i due valori uguali <code>2</code>, denotiamoli con <code>2a</code> e <code>2b</code>, ottenendo l'array di input <code>[1, 2a, 3, 2b, 4]</code>.<br><br>Gli algoritmi di ordinamento stabili mantengono l'ordine di <code>2a</code> e <code>2b</code>, così che l'output sarà <code>[1, 2a, 2b, 3, 4]</code>. Quelli instabili non mantengono l'ordine dei valori uguali e l'array di output potrebbe essere <code>[1, 2b, 2a, 3, 4]</code>.<br><br>Insertion sort, merge sort e bubble sort sono stabili. Heap sort e quick sort sono instabili.</li><li><strong>La quantità di spazio extra richiesto<strong>: </strong></strong>alcuni algoritmi di ordinamento possono ordinare una lista senza crearne una nuova. Questi sono conosciuti come algoritmi di ordinamento in loco (o in place) e richiedono uno spazio di memoria extra costante <code>O(1)</code>. Invece, gli algoritmi di ordinamento non in loco creano una nuova lista ordinando l'input.<br><br>Insertion sort e quick sort sono in loco, dato che gli elementi vengono spostati intorno a un elemento pivot e non fanno uso di un array separato.<br><br>Merge sort è un esempio di ordinamento non in loco, dato che la dimensione dell'input viene assegnata in anticipo per contenere l'output durante il processo di ordinamento, richiedendo memoria aggiuntiva.</li></ol><h2 id="bucket-sort"><strong>Bucket Sort</strong></h2><p>Bucket sort è un algoritmo di ordinamento che opera sugli elementi dividendoli in diversi <em>bucket </em>per poi ordinare individualmente i <em>bucket</em>. Ognuno di essi viene ordinato separatamente usando uno specifico algoritmo di ordinamento come insertion sort o applicando ricorsivamente bucket sort.</p><p>Bucket sort è principalmente utile quando l'input è uniformemente distribuito in un intervallo. Ad esempio, immagina di avere un grande array di numeri decimali distribuiti uniformemente tra un estremo inferiore e un estremo superiore.</p><p>Potresti utilizzare un altro algoritmo di ordinamento come merge sort o quick sort, tuttavia, questi algoritmi garantiscono nel caso migliore una complessità temporale di <code>O(nlogn)</code>.</p><p>Usando bucket sort, l'ordinamento dello stesso array può avvenire in un tempo <code>O(n)</code>.</p><h3 id="pseudo-codice-per-bucket-sort-"><strong>Pseudo-codice per Bucket Sort:</strong></h3><pre><code>funzione bucketSort(array di numeri a,intero n)
    per ogni numero intero x in n
        inserisci x in bucket[n*x]; 
    per ogni bucket
        ordina(bucket);</code></pre><h2 id="counting-sort"><strong>Counting Sort</strong></h2><p>L'algoritmo counting sort crea inizialmente una lista con il conteggio o le occorrenze di ogni valore unico nell'input, per poi generare la lista finale ordinata in base alla lista con le occorrenze.</p><p>Una cosa importante da tenere a mente è che counting sort può essere usato soltanto conoscendo a priori l'intervallo dei valori possibili.</p><h3 id="esempio-"><strong>Esempio:</strong></h3><p>Hai una lista di interi da 0 a 5:</p><pre><code>input = [2, 5, 3, 1, 4, 2]</code></pre><p>Hai bisogno di creare una lista con il conteggio di ogni valore unico nella lista <code>input</code>. Dato sai che l'intervallo dei valori di <code>input</code> è da 0 a 5, puoi creare una lista con 5 segnaposti per i valori da 0 a 5, rispettivamente:</p><pre><code>count = [0, 0, 0, 0, 0, 0]
  # val: 0  1  2  3  4  5</code></pre><p>Poi, percorri la lista <code>input</code> iterando sull'indice per ogni valore.</p><p>Ad esempio, il primo valore della lista <code>input</code> è 2, quindi aggiungi uno al valore al secondo indice della lista <code>count</code> list, che rappresenta il valore 2:</p><pre><code>count = [0, 0, 1, 0, 0, 0]
  # val: 0  1  2  3  4  5</code></pre><p>Il valore successivo nella lista <code>input</code> è 5, quindi aggiungi uno al valore all'ultimo indice della lista <code>count</code>, che rappresenta il valore 5:</p><pre><code>count = [0, 0, 1, 0, 0, 1]
  # val: 0  1  2  3  4  5</code></pre><p>Continui finché non hai il conteggio totale di ogni valore nella lista <code>input</code>:</p><pre><code>count = [0, 1, 2, 1, 1, 1]
  # val: 0  1  2  3  4  5</code></pre><p>Infine, dato che conosci quante volte appare ogni valore nella lista <code>input</code>, puoi creare facilmente una lista <code>output</code> ordinata. Itera sulla lista <code>count</code> e, per ogni conteggio, aggiungi il valore corrispondente<br>(0 - 5) all'array <code>output</code> quel numero di volte.</p><p>Ad esempio, non ci sono 0 nella lista <code>input</code> list, ma il valore 1 è presente una volta, quindi lo aggiungi all'array <code>output</code> una volta:</p><pre><code>output = [1]</code></pre><p>Il valore 2 è presente due volte, quindi lo aggiungi alla lista <code>output</code> due volte:</p><pre><code>output = [1, 2, 2]</code></pre><p>E così via, finché non ottieni la lista finale <code>output</code> ordinata:</p><pre><code>output = [1, 2, 2, 3, 4, 5]</code></pre><h3 id="propriet-"><strong>Proprietà</strong></h3><ul><li>Complessità spaziale: <code>O(k)</code></li><li>Performance nel caso migliore: <code>O(n+k)</code></li><li>Performance media: <code>O(n+k)</code></li><li>Performance nel caso peggiore: <code>O(n+k)</code></li><li>Stabile: sì (<code>k</code> è nell'intervallo degli elementi dell'array)</li></ul><h3 id="implementazione-in-javascript"><strong>Implementazione in JavaScript</strong></h3><pre><code class="language-js">let numeri = [1, 4, 1, 2, 7, 5, 2];
let conteggio = [];
let output = [];
let i = 0;
let max = Math.max(...numeri);

// inizializza counter
for (i = 0; i &lt;= max; i++) {
  conteggio[i] = 0;
}

// inizializza l'array output
for (i = 0; i &lt; numeri.length; i++) {
  output[i] = 0;
}

for (i = 0; i &lt; numeri.length; i++) {
  conteggio[numeri[i]]++;
}

for (i = 1; i &lt; conteggio.length; i++) {
  conteggio[i] += conteggio[i - 1];
}

for (i = numeri.length - 1; i &gt;= 0; i--) {
  output[--conteggio[numeri[i]]] = numeri[i];
}

// array output ordinato
for (i = 0; i &lt; output.length; i++) {
  console.log(output[i]);
}
</code></pre><h3 id="implementazione-in-c-"><strong>Implementazione in C++</strong></h3><pre><code class="language-cpp">#include &lt;iostream&gt;

#include &lt;vector&gt;

void countSort(int upperBound, int lowerBound, std::vector &lt; int &gt; numbersToSort) // limite inferiore (lowerBound) e superiore (upperBound) dei numeri in vector
{
  int i;
  int range = upperBound - lowerBound; // Crea un range largo abbastanza da contenere tutti i numeri tra il minimo e il massimo
  std::vector &lt; int &gt; counts(range + 1); // Inizializza counts della dimensione del range
  std::fill(counts.begin(), counts.end(), 0); // Riempi il vettore di zeri
  std::vector &lt; int &gt; storedNumbers(numbersToSort.size()); // Inizializza storedNumbers della stessa dimensione del vettore di input
  std::fill(storedNumbers.begin(), storedNumbers.end(), 0); // Riempi il vettore storedNumbers di zeri

  for (i = 0; i &lt; numbersToSort.size(); i++) {
    int index = numbersToSort[i] - lowerBound; // For example, if 5 is the lower bound and numbersToSort[i] is 5, the inPer esempio, se 5 è il limite inferiore, e numbersToSort[i] è 5, l'indice sarà 0
    counts[index] += 1; // e il conteggio di 5 è salvato in counts[0]
  }

  for (i = 1; i &lt; counts.size(); i++) {
    counts[i] += counts[i - 1];
  }

  for (i = numbersToSort.size() - 1; i &gt;= 0; i--) { // Copia gli elementi da numbersToSort a storedNumbers seguento count
    storedNumbers[--counts[numbersToSort[i] - lowerBound]] = numbersToSort[i];
  }

  for (i = 0; i &lt; storedNumbers.size(); i++) {
    std::cout &lt;&lt; storedNumbers[i];
    if (i != storedNumbers.size() - 1)
      std::cout &lt;&lt; ", ";
  }
  std::cout &lt;&lt; std::endl;
}</code></pre><h3 id="implementazione-in-swift"><strong>Implementazione in Swift</strong></h3><pre><code class="language-swift">func countingSort(_ array: [Int]) {
  // Crea un array per salvare il conteggio di ogni elemento
  let maxElement = array.max() ?? 0
  var countArray = [Int](repeating: 0, count: Int(maxElement + 1))

  for element in array {
    countArray[element] += 1
  }

  for i in 1..&lt;countArray.count {
    countArray[i] += countArray[i - 1]
  }

  var sortedArray = [Int](repeating: 0, count: array.count)

  // copia gli elementi da array a sortedArray seguendo count
  for i in (0..&lt;array.count).reversed() {
    countArray[array[i]] -= 1
    sortedArray[countArray[array[i]]] = array[i]
  }

  print(sortedArray)
}
</code></pre><h2 id="insertion-sort"><strong>Insertion Sort</strong></h2><p>Insertion sort è un semplice algoritmo di ordinamento adatto a un numero ridotto di elementi.</p><h3 id="esempio--1"><strong>Esempio:</strong></h3><p>Insertion sort confronta l'elemento <code>key</code> con gli elementi precedenti. Se gli elementi precedenti sono più grandi dell'elemento <code>key</code>, vengono spostati nella posizione successiva.</p><p>Inizia dall'indice 1 fino alla lunghezza dell'array di input.</p><pre><code>[ 8 3 5 1 4 2 ]</code></pre><p>Step 1 :</p><pre><code>key = 3 //partendo dal 1° indice.</code></pre><p><code>key</code> viene confrontato con gli elementi precedenti.</p><p>In questo caso, <code>key</code> viene confrontato con 8. Dato che 8 &gt; 3, l'8 viene spostato nella posizione successiva e <code>key</code> inserita nella precedente.</p><pre><code>Risultato: [ 3 8 5 1 4 2 ]</code></pre><p>Step 2 :</p><figure class="kg-card kg-image-card"><img src="https://github.com/blulion/freecodecamp-resource/blob/master/insertion_sort/2.png?raw=true" class="kg-image" alt="[ 3 8 5 1 4 2 ]" width="600" height="400" loading="lazy"></figure><pre><code>      key = 5 //2° indice

      8 &gt; 5 //sposta 8 al 2° indice e inserisce 5 al 1° indice.

      Risultato: [ 3 5 8 1 4 2 ]
</code></pre><p>Step 3 :</p><figure class="kg-card kg-image-card"><img src="https://github.com/blulion/freecodecamp-resource/blob/master/insertion_sort/3.png?raw=true" class="kg-image" alt="[ 3 5 8 1 4 2 ]" width="600" height="400" loading="lazy"></figure><pre><code>      key = 1 //3° indice

      8 &gt; 1     =&gt; [ 3 5 1 8 4 2 ]  

      5 &gt; 1     =&gt; [ 3 1 5 8 4 2 ]

      3 &gt; 1     =&gt; [ 1 3 5 8 4 2 ]

      Risultato: [ 1 3 5 8 4 2 ]
</code></pre><p>Step 4 :</p><figure class="kg-card kg-image-card"><img src="https://github.com/blulion/freecodecamp-resource/blob/master/insertion_sort/4.png?raw=true" class="kg-image" alt="[ 1 3 5 8 4 2 ]" width="600" height="400" loading="lazy"></figure><pre><code>      key = 4 //4° indice

      8 &gt; 4   =&gt; [ 1 3 5 4 8 2 ]

      5 &gt; 4   =&gt; [ 1 3 4 5 8 2 ]

      3 &gt; 4   ≠&gt;  stop

      Risultato: [ 1 3 4 5 8 2 ]
</code></pre><p>Step 5 :</p><figure class="kg-card kg-image-card"><img src="https://github.com/blulion/freecodecamp-resource/blob/master/insertion_sort/5.png?raw=true" class="kg-image" alt="[ 1 3 4 5 8 2 ]" width="600" height="400" loading="lazy"></figure><pre><code>      key = 2 //5° indice

      8 &gt; 2   =&gt; [ 1 3 4 5 2 8 ]

      5 &gt; 2   =&gt; [ 1 3 4 2 5 8 ]

      4 &gt; 2   =&gt; [ 1 3 2 4 5 8 ]

      3 &gt; 2   =&gt; [ 1 2 3 4 5 8 ]

      1 &gt; 2   ≠&gt; stop

      Risultato: [1 2 3 4 5 8]
</code></pre><figure class="kg-card kg-image-card"><img src="https://github.com/blulion/freecodecamp-resource/blob/master/insertion_sort/6.png?raw=true" class="kg-image" alt="[ 1 2 3 4 5 8 ]" width="600" height="400" loading="lazy"></figure><p>L'algoritmo mostrato qui sotto è una versione leggermente ottimizzata per evitare di scambiare l'elemento <code>key</code> in ogni iterazione. Qui, l'elemento <code>key</code> &nbsp;verrà scambiato alla fine dell'iterazione (step).</p><pre><code>    InsertionSort(arr[])
      for j = 1 to arr.length
         key = arr[j]
         i = j - 1
         while i &gt; 0 and arr[i] &gt; key
            arr[i+1] = arr[i]
            i = i - 1
         arr[i+1] = key
</code></pre><p>Ecco l'implementazione dettagliata in JavaScript:</p><pre><code class="language-js">function insertion_sort(A) {
    var len = array_length(A);
    var i = 1;
    while (i &lt; len) {
        var x = A[i];
        var j = i - 1;
        while (j &gt;= 0 &amp;&amp; A[j] &gt; x) {
            A[j + 1] = A[j];
            j = j - 1;
        }
        A[j + 1] = x;
        i = i + 1;
    }
}</code></pre><p>E un'implementazione breve in Swift:</p><pre><code class="language-swift">var array = [8, 3, 5, 1, 4, 2]

func insertionSort(array: inout [Int]) -&gt; [Int] {
  for j in 0..&lt;array.count {
    let key = array[j]
    var i = j - 1

    while i &gt; 0 &amp;&amp; array[i] &gt; key {
      array[i + 1] = array[i]
      i = i - 1
    }
    array[i + 1] = key
  }
  return array
}
</code></pre><p>Qui sotto puoi vedere un esempio in Java:</p><pre><code class="language-java">public int[] insertionSort(int[] arr) {
    for (j = 1; j &lt; arr.length; j++) {
        int key = arr[j]
        int i = j - 1
        while (i &gt; 0 and arr[i] &gt; key) {
            arr[i + 1] = arr[i]
            i -= 1
        }
        arr[i + 1] = key
    }
    return arr;
}</code></pre><p>E infine in C:</p><pre><code class="language-c">void insertionSort(int arr[], int n) {
  int i, key, j;
  for (i = 1; i &lt; n; i++) {
    key = arr[i];
    j = i - 1;
    while (j &gt;= 0 &amp;&amp; arr[j] &gt; key) {
      arr[j + 1] = arr[j];
      j = j - 1;
    }
    arr[j + 1] = key;
  }
}</code></pre><h3 id="propriet--1"><strong>Proprietà:</strong></h3><ul><li>Complessità spaziale: <code>O(1)</code></li><li>Complessità temporale: <code>O(n)</code>, <code>O(n* n)</code>, <code>O(n* n)</code> rispettivamente per caso migliore, media e caso peggiore.</li><li>Caso migliore: l'array è già ordinato</li><li>Media: l'array è ordinato in modo casuale</li><li>Caso peggiore: l'array è ordinato è invertito.</li><li>Ordinamento in loco: sì</li><li>Stabile: sì</li></ul><h2 id="heapsort"><strong>Heapsort</strong></h2><p>Heapsort è un efficiente algoritmo di ordinamento basato sull'uso di max/min heap. Un heap è una struttura di dati ad albero che soddisfa la proprietà di heap: per un max heap, la chiave di ogni nodo è minore o uguale alla chiave del suo genitore (se ne possiede uno).</p><p>Questa proprietà può essere sfruttata per accedere all'elemento massimo nell'heap in un tempo O(logn), usando il metodo maxHeapify. Svolgiamo questa operazione n volte, ogni volta muovendo il massimo in cima all'heap ed estraendolo per inserirlo nell'array ordinato. Dunque, dopo n iterazioni avremo una versione ordinata dell'array di input.</p><p>È un algoritmo in loco e richiede di costruire inizialmente una struttura di dati heap. L'algoritmo è anche instabile, che significa che in seguito al confronto di oggetti con la stessa chiave, l'ordine originale non viene preservato.</p><p>Questo algoritmo viene eseguito in un tempo <code>O(nlogn)</code> e spazio addizionale <code>O(1)</code> &nbsp;(<code>O(n)</code> includendo lo spazio richiesto per memorizzare i dati di input) dato che tutte le operazioni vengono eseguite completamente in loco.</p><p>La complessità temporale di heapsort nel caso migliore, medio e peggiore è <code>O(nlogn)</code>. Sebbene heapsort abbia una migliore complessità nel caso peggiore &nbsp;rispetto a quicksort, in pratica, quicksort viene eseguito più velocemente se ben implemenetato. È un algortimo basato sul confronto, quindi può essere usato per insiemi di dati non numerici, in quanto una certa relazione (proprietà heap) può essere definita tra gli elementi.</p><p>Qui sotto puoi vedere l'implementazione in Java:</p><pre><code class="language-java">import java.util.Arrays;
public class Heapsort {

    public static void main(String[] args) {
        // array di test
        Integer[] arr = {
            1,
            4,
            3,
            2,
            64,
            3,
            2,
            4,
            5,
            5,
            2,
            12,
            14,
            5,
            3,
            0,
            -1
        };
        String[] strarr = {
            "hope you find this helpful!",
            "wef",
            "rg",
            "q2rq2r",
            "avs",
            "erhijer0g",
            "ewofij",
            "gwe",
            "q",
            "random"
        };
        arr = heapsort(arr);
        strarr = heapsort(strarr);
        System.out.println(Arrays.toString(arr));
        System.out.println(Arrays.toString(strarr));
    }

    // TEMPO O(nlogn), SPAZIO O(1), NON STABOILE
    public static &lt; E extends Comparable &lt; E &gt;&gt; E[] heapsort(E[] arr) {
        int heaplength = arr.length;
        for (int i = arr.length / 2; i &gt; 0; i--) {
            arr = maxheapify(arr, i, heaplength);
        }

        for (int i = arr.length - 1; i &gt;= 0; i--) {
            E max = arr[0];
            arr[0] = arr[i];
            arr[i] = max;
            heaplength--;
            arr = maxheapify(arr, 1, heaplength);
        }

        return arr;
    }

    // Crea maxheap dall'array
    public static &lt; E extends Comparable &lt; E &gt;&gt; E[] maxheapify(E[] arr, Integer node, Integer heaplength) {
        Integer left = node * 2;
        Integer right = node * 2 + 1;
        Integer largest = node;

        if (left.compareTo(heaplength) &lt;= 0 &amp;&amp; arr[left - 1].compareTo(arr[node - 1]) &gt;= 0) {
            largest = left;
        }
        if (right.compareTo(heaplength) &lt;= 0 &amp;&amp; arr[right - 1].compareTo(arr[largest - 1]) &gt;= 0) {
            largest = right;
        }
        if (largest != node) {
            E temp = arr[node - 1];
            arr[node - 1] = arr[largest - 1];
            arr[largest - 1] = temp;
            maxheapify(arr, largest, heaplength);
        }
        return arr;
    }
}</code></pre><p>E in C++:</p><pre><code class="language-cpp">#include &lt;iostream&gt;

using namespace std;
void heapify(int arr[], int n, int i) {
  int largest = i;
  int l = 2 * i + 1;
  int r = 2 * i + 2;
  if (l &lt; n &amp;&amp; arr[l] &gt; arr[largest])
    largest = l;
  if (r &lt; n &amp;&amp; arr[r] &gt; arr[largest])
    largest = r;
  if (largest != i) {
    swap(arr[i], arr[largest]);

    heapify(arr, n, largest);
  }
}

void heapSort(int arr[], int n) {

  for (int i = n / 2 - 1; i &gt;= 0; i--)
    heapify(arr, n, i);

  for (int i = n - 1; i &gt;= 0; i--) {

    swap(arr[0], arr[i]);

    heapify(arr, i, 0);
  }
}
void printArray(int arr[], int n) {
  for (int i = 0; i &lt; n; ++i)
    cout &lt;&lt; arr[i] &lt;&lt; " ";
  cout &lt;&lt; "\n";
}
int main() {
  int arr[] = {
    12,
    11,
    13,
    5,
    6,
    7
  };
  int n = sizeof(arr) / sizeof(arr[0]);

  heapSort(arr, n);

  cout &lt;&lt; "Sorted array is \n";
  printArray(arr, n);
}</code></pre><h2 id="radix-sort"><strong>Radix sort</strong></h2><p>Prerequisito: Counting sort</p><p>Quick sort, merge sort e heapsort sono algoritmi di ordinamento basati sul confronto. Counting sort non lo è. Ha una complessità di <code>O(n+k)</code>, dove <code>k</code> è l'elemento massimo dell'array di input. Quindi, se <code>k</code> è <code>O(n)</code>, counting sort diventa un ordinamento lineare, che è preferibile rispetto aa algoritmi di ordinamento basati sul confronto che hanno complessità temporale <code>O(nlogn)</code>.</p><p>L'idea è di estendere l'algoritmo counting sort per ottenere una migliore complessità temporale quando <code>k</code> tende a <code>O(n^2)</code>. Da qui, l'idea di Radix Sort.</p><h3 id="l-algoritmo-"><strong>L'algoritmo:</strong></h3><p>Per ogni cifra <code>i</code>, dove <code>i</code> varia dalla cifra meno significativa alla più significativa di un numero, ordina l'array di input usando l'algoritmo counting sort secondo la cifra i-esima. Usiamo counting sort perché è stabile.</p><p>Consideriamo come esempio l'array di input:</p><pre><code>10, 21, 17, 34, 44, 11, 654, 123</code></pre><p>L'algoritmo ordinerà l'array di input secondo la cifra delle unità (quella meno significativa).</p><pre><code>0: 10
1: 21 11
2:
3: 123
4: 34 44 654
5:
6:
7: 17
8:
9:</code></pre><p>Così l'array diventa:</p><pre><code>10, 21, 11, 123, 24, 44, 654, 17</code></pre><p>Adesso, l'ordinamento avviene in base alla cifra delle decine:</p><pre><code>0:
1: 10 11 17
2: 21 123
3: 34
4: 44
5: 654
6:
7:
8:
9:</code></pre><p>L'array diventa:</p><pre><code>10, 11, 17, 21, 123, 34, 44, 654</code></pre><p>Infine, l'ordinamento avvien secondo la cifra delle centinaia (quella più significativa):</p><pre><code>0: 010 011 017 021 034 044
1: 123
2:
3:
4:
5:
6: 654
7:
8:
9:</code></pre><p>L'array diventa: </p><pre><code>10, 11, 17, 21, 34, 44, 123, 654</code></pre><p>ed è ordinato. Ecco come funziona quest'algoritmo.</p><p>Ecco un'implementazione in C:</p><pre><code class="language-c">void countsort(int arr[], int n, int place) {

  int i, freq[range] = {
    0
  }; //range for integers is 10 as digits range from 0-9
  int output[n];

  for (i = 0; i &lt; n; i++)
    freq[(arr[i] / place) % range]++;

  for (i = 1; i &lt; range; i++)
    freq[i] += freq[i - 1];

  for (i = n - 1; i &gt;= 0; i--) {
    output[freq[(arr[i] / place) % range] - 1] = arr[i];
    freq[(arr[i] / place) % range]--;
  }

  for (i = 0; i &lt; n; i++)
    arr[i] = output[i];
}

void radixsort(ll arr[], int n, int maxx) { //maxx is the maximum element in the array

  int mul = 1;
  while (maxx) {
    countsort(arr, n, mul);
    mul *= 10;
    maxx /= 10;
  }
}</code></pre><h2 id="selection-sort"><strong>Selection Sort</strong></h2><p>Selection sort è uno degli algoritmi di ordinamento più semplici. Questo algoritmo prende il nome dal modo in cui itera sull'array: seleziona l'elemento più piccolo attuale e lo mette al suo posto.</p><p>Vediamo come funziona:</p><ol><li>Trova l'elemento più piccolo dell'array e lo scambia con il primo elemento.</li><li>Trova il secondo elemento più piccolo dell'array e lo scambia con il secondo elemento dell'array.</li><li>Trova il terzo elemento più piccolo dell'array e lo scambia con il terzo elemento dell'array.</li><li>Ripete il processo di trovare l'elemento più piccolo successivo e posizionarlo correttamente finché l'array non è completamente ordinato.</li></ol><p>Ma come scriveresti il codice per trovare l'indice del secondo valore nell'array?</p><p>Un modo semplice è notare che il valore più piccolo è già stato scambiato e posizionato all'indice 0, quindi il problema si riduce alla ricerca dell'elemento più piccolo partendo dall'indice 1.</p><p>Il numero di confronti di chiavi di selection sort è pari a <code>N(N − 1)/2</code>.</p><h3 id="implementazione-in-c-c-"><strong>Implementazione in C/C++</strong></h3><p>Il seguente programma in C++ contiene un'implementazione iterativa e ricorsiva dell'algoritmo selection sort. Entrambe le implementazioni sono invocate nella funzione <code>main()</code>.</p><pre><code class="language-cpp">#include &lt;iostream&gt;

#include &lt;string&gt;

using namespace std;

template &lt; typename T, size_t n &gt;
  void print_array(T
    const ( &amp; arr)[n]) {
    for (size_t i = 0; i &lt; n; i++)
      std::cout &lt;&lt; arr[i] &lt;&lt; ' ';
    cout &lt;&lt; "\n";
  }

int minIndex(int a[], int i, int j) {
  if (i == j)
    return i;
  int k = minIndex(a, i + 1, j);
  return (a[i] &lt; a[k]) ? i : k;
}

void recurSelectionSort(int a[], int n, int index = 0) {
  if (index == n)
    return;
  int k = minIndex(a, index, n - 1);
  if (k != index)
    swap(a[k], a[index]);
  recurSelectionSort(a, n, index + 1);
}

void iterSelectionSort(int a[], int n) {
  for (int i = 0; i &lt; n; i++) {
    int min_index = i;
    int min_element = a[i];
    for (int j = i + 1; j &lt; n; j++) {
      if (a[j] &lt; min_element) {
        min_element = a[j];
        min_index = j;
      }
    }
    swap(a[i], a[min_index]);
  }
}

int main() {
  int recurArr[6] = {
    100,
    35,
    500,
    9,
    67,
    20
  };
  int iterArr[5] = {
    25,
    0,
    500,
    56,
    98
  };

  cout &lt;&lt; "Recursive Selection Sort" &lt;&lt; "\n";
  print_array(recurArr); // 100 35 500 9 67 20
  recurSelectionSort(recurArr, 6);
  print_array(recurArr); // 9 20 35 67 100 500

  cout &lt;&lt; "Iterative Selection Sort" &lt;&lt; "\n";
  print_array(iterArr); // 25 0 500 56 98
  iterSelectionSort(iterArr, 5);
  print_array(iterArr); // 0 25 56 98 500
}</code></pre><h3 id="implementazione-in-javascript-1"><strong>Implementazione in JavaScript</strong></h3><pre><code class="language-js">function selection_sort(A) {
  var len = A.length;
  for (var i = 0; i &lt; len - 1; i = i + 1) {
    var j_min = i;
    for (var j = i + 1; j &lt; len; j = j + 1) {
      if (A[j] &lt; A[j_min]) {
        j_min = j;
      } else {}
    }
    if (j_min !== i) {
      swap(A, i, j_min);
    } else {}
  }
}

function swap(A, x, y) {
  var temp = A[x];
  A[x] = A[y];
  A[y] = temp;
}
</code></pre><h3 id="implementazione-in-python"><strong>Implementazione in Python</strong></h3><pre><code class="language-python">def seletion_sort(arr):
    if not arr:
        return arr
    for i in range(len(arr)):
        min_i = i
        for j in range(i + 1, len(arr)):
            if arr[j] &lt; arr[min_i]:
                min_i = j
        (arr[i], arr[min_i]) = (arr[min_i], arr[i])</code></pre><h3 id="implementazione-in-java"><strong>Implementazione in Java</strong></h3><pre><code class="language-java">public void selectionsort(int array[]) {
    int n = array.length; // metodo per trovare la lunghezza di un array 
    for (int i = 0; i &lt; n - 1; i++) {
        int index = i;
        int min = array[i]; // prende l'elemento minimo come elemento i-esimo dell'array
        for (int j = i + 1; j &lt; n; j++) {
            if (array[j] &lt; array[index]) {
                index = j;
                min = array[j];
            }
        }
        int t = array[index]; // scambia il posto degli elemento
        array[index] = array[i];
        array[i] = t;
    }
}</code></pre><h3 id="implementazione-in-matlab"><strong>Implementazione in MATLAB</strong></h3><pre><code>function [sorted] = selectionSort(unsorted)
    len = length(unsorted);
    for i = 1:1:len
        minInd = i;
        for j = i+1:1:len
           if unsorted(j) &lt; unsorted(minInd) 
               minInd = j;
           end
        end
        unsorted([i minInd]) = unsorted([minInd i]);    
    end
    sorted = unsorted;
end
</code></pre><h3 id="propriet--2"><strong>Proprietà</strong></h3><ul><li>Complessità spaziale: &nbsp;<code>O(n)</code></li><li>Complessità temporale: &nbsp;<code>O(n^2)</code></li><li>Ordinamento in loco: &nbsp;sì</li><li>Stabile: &nbsp;No</li></ul><h2 id="bubble-sort"><strong>Bubble Sort</strong></h2><p>Bubble sort<strong> </strong>è un semplice algoritmo che ordina una lista, permettendo sia ai valori alti che bassi di raggiungerne la cima. L'algoritmo percorre la lista e confronta i valori adiacenti, scambiandoli se non sono nell'ordine corretto.</p><p>La complessità nel caso peggiore è di <code>O(n^2)</code>. Bubble sort è molto lento se comparato ad altri algoritmi di ordinamento come quick sort. Il lato positivo è che è uno degli algoritmi di ordinamento più facili da comprendere e scrivere da zero.</p><p>Da un punto di vista tecnico, bubble sort è adeguato per ordinare array di piccole dimensioni o se è necessario eseguire un algoritmo di ordinamento su un computer con risorse di memoria particolarmente ridotte.</p><h3 id="esempio--2"><strong>Esempio:</strong></h3><h3 id="primo-passagio-della-lista-"><strong>Primo passagio della lista:</strong></h3><ul><li>Partendo da <code>[4, 2, 6, 3, 9]</code>, l'algoritmo compara i primi due elementi nell'array, 4 e 2. Li scambia perché 2 &lt; 4: <code>[2, 4, 6, 3, 9]</code></li><li>Confronta i due valori successivi, 4 and 6. Dato che 4 &lt; 6, risultano già in ordine e l'algoritmo passa avanti: <code>[2, 4, 6, 3, 9]</code></li><li>I due valori successivi vengono scambiati perché 3 &lt; 6: <code>[2, 4, 3, 6, 9]</code></li><li>Gli ultimi due valori, 6 e 9, sono già in ordine e l'algoritmo non li scambia.</li></ul><h3 id="secondo-passaggio-della-lista-"><strong>Secondo passaggio della lista:</strong></h3><ul><li>2 &lt; 4, quindi non c'è bisogno di scambiare le posizioni: <code>[2, 4, 3, 6, 9]</code></li><li>L'algoritmo scambia i valori successivi perché 3 &lt; 4: <code>[2, 3, 4, 6, 9]</code> </li><li>Non c'è scambio per i valori successivi, in quanto 4 &lt; 6: <code>[2, 3, 4, 6, 9]</code></li><li>E ancora, 6 &lt; 9, quindi non avviene nessuno scambio: <code>[2, 3, 4, 6, 9]</code></li></ul><p>La lista adesso è ordinata, ma l'algoritmo bubble sort non se ne accorge. Invece, necessita di completare un altro passaggio completo della lista senza scambiare alcun valore per sapere che la lista è ordinata.</p><h3 id="terzo-passaggio-della-lista-"><strong>Terzo passaggio della lista:</strong></h3><ul><li><code>[2, 3, 4, 6, 9]</code> =&gt; <code>[2, 3, 4, 6, 9]</code></li><li><code>[2, 3, 4, 6, 9]</code> =&gt; <code>[2, 3, 4, 6, 9]</code></li><li><code>[2, 3, 4, 6, 9]</code> =&gt; <code>[2, 3, 4, 6, 9]</code></li><li><code>[2, 3, 4, 6, 9]</code> =&gt; <code>[2, 3, 4, 6, 9]</code></li></ul><p>Chiaramente, bubble sort è lontano dall'essere l'algoritmo di ordinamento più efficiente. Ad ogni modo, è semplice da capire e da mettere in pratica.</p><h4 id="propriet--3"><strong>Proprietà</strong></h4><ul><li>Complessità spaziale: <code>O(1)</code></li><li>Performance nel caso migliore:`O(n)`</li><li>Performance media: <code>O(n*n)</code></li><li>Performance nel caso peggiore: <code>O(n*n)</code></li><li>Stabile: sì</li></ul><h3 id="esempio-in-javascript"><strong>Esempio in JavaScript</strong></h3><pre><code class="language-js">let arr = [1, 4, 7, 45, 7, 43, 44, 25, 6, 4, 6, 9],
  sorted = false;

while (!sorted) {
  sorted = true;
  for (var i = 0; i &lt; arr.length; i++) {
    if (arr[i] &lt; arr[i - 1]) {
      let temp = arr[i];
      arr[i] = arr[i - 1];
      arr[i - 1] = temp;
      sorted = false;
    }
  }
}</code></pre><h3 id="esempio-in-java"><strong>Esempio in Java</strong></h3><pre><code class="language-java">public class BubbleSort {
    static void sort(int[] arr) {
        int n = arr.length;
        int temp = 0;
        for (int i = 0; i &lt; n; i++) {
            for (int x = 1; x &lt; (n - i); x++) {
                if (arr[x - 1] &gt; arr[x]) {
                    temp = arr[x - 1];
                    arr[x - 1] = arr[x];
                    arr[x] = temp;
                }

            }
        }

    }
    public static void main(String[] args) {

        for (int i = 0; i &lt; 15; i++) {
            int arr[i] = (int)(Math.random() * 100 + 1);
        }

        System.out.println("array before sorting\n");
        for (int i = 0; i &lt; arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        bubbleSort(arr);
        System.out.println("\n array after sorting\n");
        for (int i = 0; i &lt; arr.length; i++) {
            System.out.print(arr[i] + " ");
        }

    }
}</code></pre><h3 id="esempio-in-c-"><strong>Esempio in C++</strong></h3><pre><code class="language-cpp">// implementazione ricorsiva
void bubblesort(int arr[], int n) {
  if (n == 1) // caso base
    return;
  bool swap_flag = false;
  for (int i = 0; i &lt; n - 1; i++) { // dopo questo passaggio l'elemento più grande è spostato alla posizione desiderata

    if (arr[i] &gt; arr[i + 1]) {
      int temp = arr[i];
      arr[i] = arr[i + 1];
      arr[i + 1] = temp;
      swap_flag = true;
    }
  }
  // se non sono stati scambiati elementi nel loop, allora restituisci che l'array è ordinato
  if (swap_flag == false)
    return;
  bubblesort(arr, n - 1); // ricorsione per il resto dell'array
}</code></pre><h3 id="esempio-in-swift"><strong>Esempio in Swift</strong></h3><pre><code class="language-swift">func bubbleSort(_ inputArray: [Int]) -&gt; [Int] {
  // assicurati che l'array di input ha più di un elemento

  guard inputArray.count &gt; 1 else { return inputArray }
  // gli argomenti delle funzioni sono costandi di default, quindi facciamo una copia
  var numbers = inputArray
  for i in 0..&lt;(numbers.count - 1) {
    for j in 0..&lt;(numbers.count - i - 1) {
      if numbers[j] &gt; numbers[j + 1] {
        numbers.swapAt(j, j + 1)
      }
    }
  }
  // restituisce l'array ordinato
  return numbers
}
</code></pre><h3 id="esempio-in-python"><strong>Esempio in Python</strong></h3><pre><code class="language-py">def bubbleSort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n - i - 1):
            if arr[j] &gt; arr[j + 1]:
                (arr[j], arr[j + 1]) = (arr[j + 1], arr[j])
    print arr
</code></pre><h3 id="esempio-in-php"><strong>Esempio in PHP</strong></h3><pre><code class="language-php">function bubble_sort($arr) {
    $size = count($arr)-1;
    for ($i=0; $i&lt;$size; $i++) {
        for ($j=0; $j&lt;$size-$i; $j++) {
            $k = $j+1;
            if ($arr[$k] &lt; $arr[$j]) {
                // Scambia elementi agli indici: $j, $k
                list($arr[$j], $arr[$k]) = array($arr[$k], $arr[$j]);
            }
        }
    }
    return $arr; // restituisci l'array ordinato
}

$arr = array(1,3,2,8,5,7,4,0);
print("Prima di ordinare");
print_r($arr);

$arr = bubble_sort($arr);
print("Dopo aver ordinato usando Bubble Sort");
print_r($arr);
</code></pre><h3 id="esempio-in-c"><strong>Esempio in C</strong></h3><pre><code class="language-c">#include &lt;stdio.h&gt;

int BubbleSort(int array[], int n);

int main(void) {
  int arr[] = {
    10,
    2,
    3,
    1,
    4,
    5,
    8,
    9,
    7,
    6
  };
  BubbleSort(arr, 10);

  for (int i = 0; i &lt; 10; i++) {
    printf("%d", arr[i]);
  }
  return 0;
}
int BubbleSort(int array[], n) {
  for (int i = 0; i &lt; n - 1; i++) {
    for (int j = 0; j &lt; n - i - 1; j++) //n is length of array
    {
      if (array[j] &gt; array[j + 1]) // For decreasing order use 
      {
        int swap = array[j];
        array[j] = array[j + 1];
        array[j + 1] = swap;
      }
    }
  }
}</code></pre><h2 id="quick-sort"><strong>Quick Sort</strong></h2><p>Quick sort è un efficiente algoritmo di ordinamento divide et impera. La sua complessità temporale nel caso medio è <code>O(nlog(n))</code>, mentre quella nel caso peggiore è <code>O(n^2)</code> in base alla selezione dell'elemento pivot, che divide l'array attuale in due sotto-array.</p><p>Ad esempio, la complessità temporale di quick sort è approssimativamente <code>O(nlog(n))</code> quando la selezione del pivot divide l'array originale in due sotto-array di dimensione quasi uguale.</p><p>D'altra parte, se &nbsp;l'algoritmo che seleziona l'elemento pivot dell'array di input restituisce costantemente 2 sotto-array con una grande differenza in termini di dimensione, l'algoritmo quick sort può raggiungere la complessità temporale del caso peggiore pari a <code>O(n^2)</code>.</p><p>Gli step coinvolti in quick sort sono:</p><ul><li>Scelta dell'elemento pivot, in questo caso, l'ultimo elemento dell'array.</li><li>Partizione: ordinamento dell'array in modo che tutti gli elementi minori del pivot siano a sinistra e tutti quelli maggiori a destra.</li><li>Chiamata ricorsiva di quick sort, considerando il pivot precedente per dividere in modo adeguato gli array di sinistra e destra (Una discussione più dettagliata è presente nei commenti qui sotto).</li></ul><h3 id="implementazione-in-javascript-"><strong>Implementazione in JavaScript:</strong></h3><pre><code class="language-js">const arr = [6, 2, 5, 3, 8, 7, 1, 4];
const quickSort = (arr, start, end) =&gt; {
  if (start &lt; end) {
    // puoi imparare come viene selezionato il pivot nei commenti sotto
    let pivot = partition(arr, start, end);
    // assicurati di leggere i conmmenti sotto per capire perché pivot-1 e pivot+1 sono usati
    // queste sono le invocazioni ricorsive a quickSort
    quickSort(arr, start, pivot - 1);
    quickSort(arr, pivot + 1, end);
  }
}
const partition = (arr, start, end) =&gt; {
  let pivot = end;
  // imposta i a start-1 così che possa accedere al primo indice nel caso in cui il valore di arr[0] sia più grande di arr[pivot]
  // i commenti successivi espandono sul commento qua sopra
  let i = start - 1,
    j = start;
  // incrementa j fino all'indice precedente il pivot
  while (j &lt; pivot) {
    // se il valore è più grande del pivot allora incrementa j
    if (arr[j] &gt; arr[pivot]) {
      j++;
    }
    // quando il valore ad arr[j] è minore del pivot:
    // incrementa i (arr[i] sarà un valore più grande di arr[pivot]) scambia i calori arr[i] e arr[j]
    else {
      i++;
      swap(arr, j, i);
      j++;
    }
  }
  // il valore ad arr[i+1] sarà più grande del valore ad arr[pivot]
  swap(arr, i + 1, pivot);
  /* restituisci i+1, visto che i valori alla sua sinistra sono inferiori ad arr[i+1]
     e i valore alla sua destra sono maggiorni di arr[i+1] */
  /* In questo modo, quando i quickSort ricorsivi sono chiamati,
     il nuovo sotto-array non include il valore pivot usato in precedenza */
  return i + 1;
}
const swap = (arr, firstIndex, secondIndex) =&gt; {
  let temp = arr[firstIndex];
  arr[firstIndex] = arr[secondIndex];
  arr[secondIndex] = temp;
}
quickSort(arr, 0, arr.length - 1);
console.log(arr);
</code></pre><h3 id="implementazione-in-c"><strong>Implementazione in C</strong></h3><pre><code class="language-c">#include&lt;stdio.h&gt;

void swap(int * a, int * b) {
  int t = * a;
  * a = * b;
  * b = t;
}
int partition(int arr[], int low, int high) {
  int pivot = arr[high];
  int i = (low - 1);

  for (int j = low; j &lt;= high - 1; j++) {
    if (arr[j] &lt;= pivot) {
      i++;
      swap( &amp; arr[i], &amp; arr[j]);
    }
  }
  swap( &amp; arr[i + 1], &amp; arr[high]);
  return (i + 1);
}
void quickSort(int arr[], int low, int high) {
  if (low &lt; high) {
    int pi = partition(arr, low, high);

    quickSort(arr, low, pi - 1);
    quickSort(arr, pi + 1, high);
  }
}

void printArray(int arr[], int size) {
  int i;
  for (i = 0; i &lt; size; i++)
    printf("%d ", arr[i]);
  printf("n");
}

int main() {
  int arr[] = {
    10,
    7,
    8,
    9,
    1,
    5
  };
  int n = sizeof(arr) / sizeof(arr[0]);
  quickSort(arr, 0, n - 1);
  printf("Sorted array: n");
  printArray(arr, n);
  return 0;
}</code></pre><h3 id="implementazione-in-python3"><strong>Implementazione in Python3</strong></h3><pre><code class="language-python">import random

z = [random.randint(0, 100) for i in range(0, 20)]


def quicksort(z):
    if len(z) &gt; 1:
        piv = int(len(z) / 2)
        val = z[piv]
        lft = [i for i in z if i &lt; val]
        mid = [i for i in z if i == val]
        rgt = [i for i in z if i &gt; val]

        res = quicksort(lft) + mid + quicksort(rgt)
        return res
    else:
        return z


ans1 = quicksort(z)
print ans1</code></pre><h3 id="implementazione-in-matlab-1"><strong>Implementazione in MATLAB</strong></h3><pre><code class="language-MATLAB">a = [9,4,7,3,8,5,1,6,2];

sorted = quicksort(a,1,length(a));

function [unsorted] =  quicksort(unsorted, low, high)
    if low &lt; high
        [pInd, unsorted] = partition(unsorted, low, high);
        unsorted = quicksort(unsorted, low, pInd-1);
        unsorted = quicksort(unsorted, pInd+1, high);
    end

end

function [pInd, unsorted] = partition(unsorted, low, high)
    i = low-1;
    for j = low:1:high-1
        if unsorted(j) &lt;= unsorted(high)
            i = i+1;
            unsorted([i,j]) = unsorted([j,i]);
            
        end
    end
    unsorted([i+1,high]) = unsorted([high,i+1]);
    pInd = i+1;

end
</code></pre><h3 id="complessit-"><strong>Complessità</strong></h3><p>Le complessità nel caso migliore, medio, peggiore e di memoria sono rispettivamente: <code>n log(n)</code>, <code>n log(n)</code>, <code>n^2</code>, <code>log(n)</code>. Non è un algoritmo stabile ed è di solito eseguito in loco con <code>O(log(n))</code> spazio di stack.</p><p>La complessità spaziale di quick sort è <code>O(n)</code>. Rappresenta un miglioramento rispetto ad altri algoritmi di ordinamento divide et impera, che prendono uno spazio di <code>O(nlog(n))</code>.</p><p>Per quick sort, ciò avviene cambiando l'ordine degli elementi all'interno dell'array dato. Fai il confronto con merge sort, che crea 2 array, ognuno di lunghezza <code>n/2</code> in ogni chiamata di funzione.</p><p>Tuttavia, se il pivot viene mantenuto nel mezzo, questi algoritmi di ordinamento vengono eseguiti in <code>O(n*n)</code>. Si può evitare questo problema usando un pivot random.</p><h2 id="timsort"><strong>Timsort</strong></h2><p>Timsort è un algoritmo di ordinamento veloce che opera in modo stabile con complessità <code>O(N log(N))</code>.</p><p>Timsort è un mix di insertion sort e merge sort. Questo algoritmo è implementato in <code>Arrays.sort()</code> di Java, ma anche <code>sorted()</code> e <code>sort()</code> in Python. Le parti più piccole vengono ordinate usando insertion sort e poi unite insieme con merge sort.</p><p>Implementazione in Python:</p><pre><code class="language-py">def binary_search(
    the_array,
    item,
    start,
    end,
    ):
    if start == end:
        if the_array[start] &gt; item:
            return start
        else:
            return start + 1
    if start &gt; end:
        return start

    mid = round((start + end) / 2)

    if the_array[mid] &lt; item:
        return binary_search(the_array, item, mid + 1, end)
    elif the_array[mid] &gt; item:

        return binary_search(the_array, item, start, mid - 1)
    else:

        return mid


def insertion_sort(the_array):
    l = len(the_array)
    for index in range(1, l):
        value = the_array[index]
        pos = binary_search(the_array, value, 0, index - 1)
        the_array = the_array[:pos] + [value] + the_array[pos:index] \
            + the_array[index + 1:]
    return the_array


def merge(left, right):
    """Prende due liste ordinare
    e restituisce una singola lista ordinata
    comparando gli elementi uno alla volta.
    [1, 2, 3, 4, 5, 6]
    """

    if not left:
        return right
    if not right:
        return left
    if left[0] &lt; right[0]:
        return [left[0]] + merge(left[1:], right)
    return [right[0]] + merge(left, right[1:])


def timsort(the_array):
    (runs, sorted_runs) = ([], [])
    length = len(the_array)
    new_run = [the_array[0]]

    # per ogni i nel range da 1 alla lunghezza dell'array
    for i in range(1, length):

		# se i è alla fine della lista
        if i == length - 1:
            new_run.append(the_array[i])
            runs.append(new_run)
            break

		# se l'i-esimo elemento dell'aray è inferiore a quello prima
        if the_array[i] &lt; the_array[i - 1]:

			# se new_run ha valore di None (NULL)
            if not new_run:
                runs.append([the_array[i]])
                new_run.append(the_array[i])
            else:
                runs.append(new_run)
                new_run = []
        else:

		# altrimenti se è uguare a maggiore di 
            new_run.append(the_array[i])

	# per ogni item in runs, appendilo usando insertion sort
    for item in runs:
        sorted_runs.append(insertion_sort(item))

	# per ogni run in sorted_runs, uniscile
    sorted_array = []
    for run in sorted_runs:
        sorted_array = merge(sorted_array, run)

    print sorted_array


timsort([
    2,
    3,
    1,
    5,
    6,
    7,
    ])
</code></pre><h3 id="complessit--1"><strong>Complessità:</strong></h3><p>Timsort ha una complessità stabile di <code>O(N log(N))</code> ed è ben comparabile con quick sort. Puoi vedere un confronto tra le complessità in questa tabella.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/italian/news/content/images/2022/07/image.png" class="kg-image" alt="image" srcset="https://www.freecodecamp.org/italian/news/content/images/size/w600/2022/07/image.png 600w, https://www.freecodecamp.org/italian/news/content/images/2022/07/image.png 802w" sizes="(min-width: 720px) 720px" width="600" height="400" loading="lazy"></figure><h2 id="merge-sort"><strong>Merge Sort</strong></h2><p>Merge sort è un algoritmo <a href="https://www.freecodecamp.org/italian/news/gli-algoritmi-divide-et-impera/">divide et impera</a>. Divide l'array di input in due metà, chiama sé stesso per le due metà e unisce le due porzioni ordinate. La porzione maggiore dell'algoritmo riceve due array ordinati, da unire in un singolo array ordinato. L'intero procedimento di ordinamento di un array di N interi può essere riassunto in 3 step:</p><ul><li>Divisione dell'array in due metà.</li><li>Ordinamento della metà di sinistra e della metà di destra usando ricorsivamente lo stesso algoritmo.</li><li>Unione delle due metà ordinate.</li></ul><p>Esiste un algoritmo detto Two Finger che ci aiuta a unire assieme due array ordinati. Usando questo sottoprogramma e chiamando la funzione merge sort sulle metà dell'array ricorsivamente, otterremo l'array finale ordinato che stiamo cercando.</p><p>Dato che questo è un algoritmo basato sulla ricorsione, presenta una relazione di ricorrenza. La relazione di ricorrenza è semplicemente un modo di rappresentare &nbsp;un problema in termini dei suoi sottoproblemi.</p><p><code>T(n) = 2 * T(n / 2) + O(n)</code></p><p>Traducendo in italiano: dividiamo il sottoproblema in due parti ad ogni step, e abbiamo una quantità lineare di operazioni da svolgere per unire le due metà ordinate in ogni step.</p><h3 id="complessit--2"><strong>Complessità</strong></h3><p>Il vantaggio più grande dell'uso di merge sort è che la complessità temporale per l'ordinamento di un intero array è solamente <code>n*log(n)</code>, molto meglio di <code>n^2</code> per bubble sort o insertion sort.</p><p>Prima di scrivere il codice, vediamo di capire come funziona merge sort con l'aiuto di un diagramma.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/06/4712ef1a5d856dbb4af393fcc08a820a38787395.png" class="kg-image" alt="4712ef1a5d856dbb4af393fcc08a820a38787395" width="600" height="400" loading="lazy"></figure><ul><li>Inizialmente abbiamo un array di 6 interi non ordinati: <code>Arr(5, 8, 3, 9, 1, 2)</code></li><li>Dividiamo l'array in due metà <code>Arr1 = (5, 8, 3)</code> e <code>Arr2 = (9, 1, 2)</code></li><li>Di nuovo, li dividiamo in due metà: <code>Arr3 = (5, 8)</code> e <code>Arr4 = (3)</code> e <code>Arr5 = (9, 1)</code> e <code>Arr6 = (2)</code></li><li>Di nuovo, li dividiamo in due metà: <code>Arr7 = (5)</code>, <code>Arr8 = (8)</code>, <code>Arr9 = (9)</code>, <code>Arr10 = (1)</code> and <code>Arr6 = (2)</code></li><li>Confrontiamo gli elementi nei sotto-array per poi unirli.</li></ul><h3 id="propriet--4"><strong>Proprietà:</strong></h3><ul><li>Complessità spaziale: <code>O(n)</code></li><li>Complessità temporale: <code>O(n*log(n))</code>. La complessità temporale per merge sort potrebbe non essere ovvia di primo acchito. Il fattore <code>log(n)</code> deriva dalla relazione di ricorrenza che abbiamo menzionato precedentemente.</li><li>Ordinamento in loco: no, in una implementazione usuale.</li><li>Stabile: sì.</li><li>Parallelizzabile: sì (svariate varianti parallele sono discusse nella terza edizione di Introduction to Algorithms di Cormen, Leiserson, Rivest e Stein).</li></ul><h3 id="implementazione-in-c--1"><strong><strong><strong>Implementa</strong></strong>zione in <strong><strong>C++ </strong></strong></strong></h3><pre><code class="language-cpp">void merge(int array[], int left, int mid, int right) {
  int i, j, k;

  // dimensione della sotto-lista sinistra 
  int size_left = mid - left + 1;

  // dimensione della sotto-lista denstra
  int size_right = right - mid;

  // crea gli array temporanei
  int Left[size_left], Right[size_right];

  // copia i dati agli array temporanei destro e sinistro
  for (i = 0; i &lt; size_left; i++) {
    Left[i] = array[left + i];
  }

  for (j = 0; j &lt; size_right; j++) {
    Right[j] = array[mid + 1 + j];
  }

  // unisci gli array temporanei in arr[left ..right]
  i = 0; // Indice iniziale del sotto-array sinistro
  j = 0; // Indice iniziare del sotto-array destro
  k = left; // Indice iniziare del sotto-array unito

  while (i &lt; size_left &amp;&amp; j &lt; size_right) {
    if (Left[i] &lt;= Right[j]) {
      array[k] = Left[i];
      i++;
    } else {
      array[k] = Right[j];
      j++;
    }
    k++;
  }

  // copia gli elementi rimanenti del sotto-array sinistro
  while (i &lt; size_left) {
    array[k] = Left[i];
    i++;
    k++;
  }

  // copia gli elementi rimanenti del sotto-array destro
  while (j &lt; size_right) {
    array[k] = Right[j];
    j++;
    k++;
  }
}

void mergeSort(int array[], int left, int right) {
  if (left &lt; right) {
    int mid = (left + right) / 2;

    mergeSort(array, left, mid);
    mergeSort(array, mid + 1, right);

    // infine uniscili
    merge(array, left, mid, right);
  }
}</code></pre><h3 id="implementazione-in-javascript-2"><strong><strong><strong>Implementa</strong></strong>zione in <strong><strong>JavaScript </strong></strong></strong></h3><pre><code class="language-js">function mergeSort(arr) {
	if (arr.length &lt; 2) return arr;
	var mid = Math.floor(arr.length / 2);
	var subLeft = mergeSort(arr.slice(0, mid));
	var subRight = mergeSort(arr.slice(mid));
	return merge(subLeft, subRight);
}</code></pre><p>Prima verifichiamo la lunghezza dell'array. Se è 1, restituiamo semplicemente l'array. Questo è il caso base. Altrimenti, troviamo il valore centrale e dividiamo l'array in due metà. Ordiniamo entrambe le metà con chiamate ricorsive della funzione mergeSort.</p><pre><code class="language-js">function merge (a,b) {
    var result = [];
    while (a.length &gt;0 &amp;&amp; b.length &gt;0)
        result.push(a[0] &lt; b[0]? a.shift() : b.shift());
    return result.concat(a.length? a : b);
}</code></pre><p>Quando uniamo le due metà, memorizziamo il risultato in un array ausiliario. Confronteremo l'elemento iniziale dell'array di sinistra con l'elemento iniziale dell'array di destra. Qualsiasi sia il minore, verrà inserito nell'array con il risultato e rimosso da quello d'appartenenza con l'operatore shift(). Se alla fine ci sono ancora valori negli array di sinistra o destra, verranno concatenati alla fine del risultato. Ecco l'array ordinato:</p><pre><code class="language-js">var test = [5, 6, 7, 3, 1, 3, 15];
console.log(mergeSort(test));

&gt;&gt; [1, 3, 3, 5, 6, 7, 15]</code></pre><h3 id="implementazione-in-js"><strong><strong><strong>Implementa</strong></strong>zione in JS</strong></h3><pre><code class="language-js">const list = [23, 4, 42, 15, 16, 8, 3]

const mergeSort = (list) =&gt; {
	if (list.length &lt;= 1) return list;
	const middle = list.length / 2;
	const left = list.slice(0, middle);
	const right = list.slice(middle, list.length);
	return merge(mergeSort(left), mergeSort(right));
}

const merge = (left, right) =&gt; {
	var result = [];
	while (left.length || right.length) {
		if (left.length &amp;&amp; right.length) {
			if (left[0] &lt; right[0]) {
				result.push(left.shift())
			} else {
				result.push(right.shift())
			}
		} else if (left.length) {
			result.push(left.shift())
		} else {
			result.push(right.shift())
		}
	}
	return result;
}

console.log(mergeSort(list)) // [ 3, 4, 8, 15, 16, 23, 42 ]</code></pre><h3 id="implementazione-in-c-1"><strong>Implementazione in C</strong></h3><pre><code class="language-c">#include&lt;stdlib.h&gt;

#include&lt;stdio.h&gt;

void merge(int arr[], int l, int m, int r) {
  int i, j, k;
  int n1 = m - l + 1;
  int n2 = r - m;

  int L[n1], R[n2];

  for (i = 0; i &lt; n1; i++)
    L[i] = arr[l + i];
  for (j = 0; j &lt; n2; j++)
    R[j] = arr[m + 1 + j];
  i = 0;
  j = 0;
  k = l;
  while (i &lt; n1 &amp;&amp; j &lt; n2) {
    if (L[i] &lt;= R[j]) {
      arr[k] = L[i];
      i++;
    } else {
      arr[k] = R[j];
      j++;
    }
    k++;
  }

  while (i &lt; n1) {
    arr[k] = L[i];
    i++;
    k++;
  }

  while (j &lt; n2) {
    arr[k] = R[j];
    j++;
    k++;
  }
}

void mergeSort(int arr[], int l, int r) {
  if (l &lt; r) {
    int m = l + (r - l) / 2;

    mergeSort(arr, l, m);
    mergeSort(arr, m + 1, r);

    merge(arr, l, m, r);
  }
}
void printArray(int A[], int size) {
  int i;
  for (i = 0; i &lt; size; i++)
    printf("%d ", A[i]);
  printf("\n");
}
int main() {
    int arr[] = {
      12,
      11,
      13,
      5,
      6,
      7
    };
    int arr_size = sizeof(arr) / sizeof(arr[0]);

    printf("Given array is \n");
    printArray(arr, arr_size);

    mergeSort(arr, 0, arr_size - 1);

    printf("\nSorted array is \n");
    printArray(arr, arr_size);
    return 0;</code></pre><h3 id="implementazione-in-c--2"><strong>Implementazione in C++</strong></h3><p>Consideriamo l'array <code>A = {2,5,7,8,9,12,13}</code> e l'array <code>B = {3,5,6,9,15}</code>. Vogliamo che anche l'array C sia in ordine ascendente.</p><pre><code class="language-cpp">void mergesort(int A[], int size_a, int B[], int size_b, int C[]) {
  int token_a, token_b, token_c;
  for (token_a = 0, token_b = 0, token_c = 0; token_a &lt; size_a &amp;&amp; token_b &lt; size_b;) {
    if (A[token_a] &lt;= B[token_b])
      C[token_c++] = A[token_a++];
    else
      C[token_c++] = B[token_b++];
  }

  if (token_a &lt; size_a) {
    while (token_a &lt; size_a)
      C[token_c++] = A[token_a++];
  } else {
    while (token_b &lt; size_b)
      C[token_c++] = B[token_b++];
  }

}</code></pre><h3 id="implementazione-in-python-1"><strong>Implementazione in Python</strong></h3><pre><code class="language-py">#!/usr/bin/python
# -*- coding: utf-8 -*-


def merge(left, right, compare):
    result = []
    (i, j) = (0, 0)
    while i &lt; len(left) and j &lt; len(right):
        if compare(left[i], right[j]):
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    while i &lt; len(left):
        result.append(left[i])
        i += 1
    while j &lt; len(right):
        result.append(right[j])
        j += 1
    return result


def merge_sort(arr, compare=lambda x, y: x &lt; y):

    # Usa funzione lambda per ordinare l'array in entrambi (crescente e decrescente) gli ordini
    # Come default ordina l'array in ordine crescente

    if len(arr) &lt; 2:
        return arr[:]
    else:
        middle = len(arr) // 2
        left = merge_sort(arr[:middle], compare)
        right = merge_sort(arr[middle:], compare)
        return merge(left, right, compare)


arr = [2, 1, 4, 5, 3]
print merge_sort(arr)
</code></pre><h3 id="implementazione-in-java-1"><strong>Implementazione in Java</strong></h3><pre><code class="language-java">public class mergesort {

    public static int[] mergesort(int[] arr, int lo, int hi) {

        if (lo == hi) {
            int[] ba = new int[1];
            ba[0] = arr[lo];
            return ba;
        }

        int mid = (lo + hi) / 2;
        int arr1[] = mergesort(arr, lo, mid);
        int arr2[] = mergesort(arr, mid + 1, hi);
        return merge(arr1, arr2);
    }

    public static int[] merge(int[] arr1, int[] arr2) {
        int i = 0, j = 0, k = 0;
        int n = arr1.length;
        int m = arr2.length;
        int[] arr3 = new int[m + n];
        while (i &lt; n &amp;&amp; j &lt; m) {
            if (arr1[i] &lt; arr2[j]) {
                arr3[k] = arr1[i];
                i++;
            } else {
                arr3[k] = arr2[j];
                j++;
            }
            k++;
        }

        while (i &lt; n) {
            arr3[k] = arr1[i];
            i++;
            k++;
        }

        while (j &lt; m) {
            arr3[k] = arr2[j];
            j++;
            k++;
        }

        return arr3;

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int arr[] = {
            2,
            9,
            8,
            3,
            6,
            4,
            10,
            7
        };
        int[] so = mergesort(arr, 0, arr.length - 1);
        for (int i = 0; i &lt; arr.length; i++)
            System.out.print(so[i] + " ");
    }

}</code></pre><h3 id="esempio-in-java-1"><strong>Esempio in Java</strong></h3><pre><code class="language-java">public class mergesort {
    public static int[] mergesort(int[] arr, int lo, int hi) {
        if (lo == hi) {
            int[] ba = new int[1];
            ba[0] = arr[lo];
            return ba;
        }
        int mid = (lo + hi) / 2;
        int arr1[] = mergesort(arr, lo, mid);
        int arr2[] = mergesort(arr, mid + 1, hi);
        return merge(arr1, arr2);
    }

    public static int[] merge(int[] arr1, int[] arr2) {
        int i = 0, j = 0, k = 0;
        int n = arr1.length;
        int m = arr2.length;
        int[] arr3 = new int[m + n];
        while (i &lt; n &amp;&amp; j &lt; m) {
            if (arr1[i] &lt; arr2[j]) {
                arr3[k] = arr1[i];
                i++;
            } else {
                arr3[k] = arr2[j];
                j++;
            }
            k++;
        }
        while (i &lt; n) {
            arr3[k] = arr1[i];
            i++;
            k++;
        }
        while (j &lt; m) {
            arr3[k] = arr2[j];
            j++;
            k++;
        }
        return arr3;
    }

    public static void main(String[] args) {
        int arr[] = {
            2,
            9,
            8,
            3,
            6,
            4,
            10,
            7
        };
        int[] so = mergesort(arr, 0, arr.length - 1);
        for (int i = 0; i &lt; arr.length; i++)
            System.out.print(so[i] + " ");
    }
}</code></pre> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Come programmare un cifrario di Cesare: un'introduzione alla codifica di base ]]>
                </title>
                <description>
                    <![CDATA[ Il cifrario di Cesare è una famosa attuazione della crittografia risalente ai suoi primordi. Prende una frase e la riorganizza, basandosi su una chiave che viene adoperata sull'alfabeto. Ad esempio, considera la chiave 3 e la frase  > Mi piace indossare i cappelli.  Quando la frase viene codificata ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/come-programmare-un-cifrario-di-cesare-unintroduzione-alla-codifica-di-base/</link>
                <guid isPermaLink="false">62bb026a7e58e706f822c24b</guid>
                
                    <category>
                        <![CDATA[ Java ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Algoritmi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dario Di Cillo ]]>
                </dc:creator>
                <pubDate>Fri, 08 Jul 2022 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2022/06/0_tuogeHoQ53SQACY-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/how-to-code-the-caesar-cipher-an-introduction-to-basic-encryption-3bf77b4e19f7/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to code the Caesar Cipher: an introduction to basic encryption</a>
      </p><p>Il cifrario di Cesare è una famosa attuazione della crittografia risalente ai suoi primordi. Prende una frase e la riorganizza, basandosi su una chiave che viene adoperata sull'alfabeto. Ad esempio, considera la chiave 3 e la frase </p><blockquote>Mi piace indossare i cappelli. </blockquote><p>Quando la frase viene codificata usando la chiave 3, diventa:</p><blockquote>Pl sldfh lqgrvvduh l fdsshool.</blockquote><p>In questo modo, è difficile leggere il messaggio, che può passare inosservato.</p><p>Mentre questo è un esempio di codifica m0lto semplice, è un progetto perfetto su cui far pratica per chi sta imparando a programmare.</p><h4 id="comprendere-il-cifrario"><strong>Comprendere il cifrario</strong></h4><p>Per implementare questo codice, almeno in Java, avrai bisogno di riflettere a fondo su ciò che è stato fatto. Quindi, vediamo quali sono i passaggi fondamentali da considerare per questo codice. </p><p>Step 1: identifica il carattere nella frase.</p><p>Step 2: trova la posizione del carattere nell'alfabeto.</p><p>Step 3: identifica la somma di posizione del carattere + posizione della chiave nell'alfabeto.</p><p>Note* se la posizione del carattere + chiave &gt; 26, torna indietro e ricomincia a contare da 1.</p><p>Step 4: costruisci una nuova frase usando i nuovi caratteri al posto di quelli originali.</p><p>Step 5: ripeti fino a raggiungere la lunghezza della frase (loop for).</p><p>Step 6: restituisci il risultato.</p><h4 id="codificare-il-cifrario"><strong>Codificare il cifrario</strong></h4><p>Questi sono dei buoni step da seguire, ma dovremmo pensare a cosa ci serve fare in termini di codice.</p><p>Step 0: crea una funzione che accetta un messaggio e una chiave.</p><p>Qualcosa del genere:</p><pre><code>public String Encrypt(String message, int key) {

}</code></pre><p>Step 1: identifica il carattere nella frase.</p><p>Per farlo, avremo bisogno di stabilire un alfabeto a cui fare riferimento.</p><p>Crea una variabile <code>alphabet</code> che contiene le 26 lettere dell'alfabeto.</p><pre><code>String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
String alphabet2 = alphabet.toLowerCase();</code></pre><p>Step 2: trova la posizione del carattere nell'alfabeto.</p><p>Poi crea un loop for che itera su ogni carattere del messaggio. Sarà più semplice svolgere quest'operazione se creiamo uno <code>StringBuilder</code>.</p><pre><code>StringBuilder encrypted = new StringBuilder(message);
for (int q = 0; q &lt; encrypted.length(); q++) {
    char currchar = encrypted.charAt(q);
    int index = alphabet.indexOf(currchar);
}</code></pre><p>A questo punto, dovremmo assicurarci che la posizione corrisponda a una lettera.</p><pre><code>if (index != -1) {}</code></pre><p>Step 3: &nbsp;identifica la somma di posizione del carattere + posizione della chiave nell'alfabeto.</p><p>Se è una lettera, allora dobbiamo trovare la posizione nell'alfabeto modificato, Non abbiamo ancora creato una variabile per l'alfabeto modificato, quindi dovremmo farlo adesso.</p><p>Step 4: costruisci una nuova frase usando i nuovi caratteri al posto di quelli originali.</p><p>Una volta trovato il valore nell'alfabeto modificato, dovremmo impostarlo sulla stessa posizione nello StringBuilder che abbiamo creato.</p><pre><code>public String Encryption(String input, int key) {
        StringBuilder encrypted = new StringBuilder(input);
        String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        String alphabet2 = alphabet.toLowerCase();
        String keyedalphabet = alphabet.substring(key) + alphabet.substring(0, key);
        for (int q = 0; q &lt; encrypted.length(); q++) {
            char currChar = encrypted.charAt(q);
            int index = alphabet.indexOf(currChar);
            if (index != -1) {
                char newChar = keyedalphabet.charAt(index);
                encrypted.setCharAt(q, newChar);
            }</code></pre><p>Step 5: ripeti fino a raggiungere la lunghezza della frase (loop for).</p><p>Ora, abbiamo controllato se il carattere è maiuscolo, ma dobbiamo controllare anche se è minuscolo. Per farlo, dobbiamo accedere a <code>alphabet2</code> che abbiamo creato precedentemente.</p><pre><code>index = alphabet2.indexOf(currChar);
if (index != -1) {
    String keyedalphabet2 = keyedalphabet.toLowerCase();
    char newChar = keyedalphabet2.charAt(index);
    encrypted.setCharAt(q, newChar);
}</code></pre><p>Step 6: restituisci il risultato.</p><p>Una volta completato il loop for, tutto ciò che ci resta da fare è restituire la stringa.</p><pre><code>public String Encryption(String input, int key) {
    StringBuilder encrypted = new StringBuilder(input);
    String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    String alphabet2 = alphabet.toLowerCase();
    String keyedalphabet = alphabet.substring(key) + alphabet.Asubstring(0, key);
    for (int q = 0; q &lt; encrypted.length(); q++) {
        char currChar = encrypted.charAt(q);
        int index = alphabet.indexOf(currChar);
        if (index != -1) {
            char newChar = keyedalphabet.charAt(index);
            encrypted.setCharAt(q, newChar);
        }
        index = alphabet2.indexOf(currChar);
        if (index != -1) {
            String keyedalphabet2 = keyedalphabet.toLowerCase();
            char newChar = keyedalphabet2.charAt(index);
            encrypted.setCharAt(q, newChar);
        }
    }
    return encrypted
}</code></pre><p>Step 7: Debug.</p><p>Un momento! Così non funzionerà! <code>encrypted</code> non è una stringa, ma uno <code>StringBuilder</code> e questa funzione richiede specificatamente che venga restituita una stringa.</p><p>Fortunatamente, esiste una funzione molto semplice per rimediare a &nbsp;questa svista.</p><pre><code>public String Encryption(String input, int key) {
    StringBuilder encrypted = new StringBuilder(input);
    String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    String alphabet2 = alphabet.toLowerCase();
    String keyedalphabet = alphabet.substring(key) + alphabet.substring(0, key);
    for (int q = 0; q &lt; encrypted.length(); q++) {
        char currChar = encrypted.charAt(q);
        int index = alphabet.indexOf(currChar);
        if (index != -1) {
            char newChar = keyedalphabet.charAt(index);
            encrypted.setCharAt(q, newChar);
        }
        index = alphabet2.indexOf(currChar);
        if (index != -1) {
            String keyedalphabet2 = keyedalphabet.toLowerCase();
            char newChar = keyedalphabet2.charAt(index);
            encrypted.setCharAt(q, newChar);
        }
    }
    return encrypted.toString();
}</code></pre><p>Ecco come puoi ottenere la versione codificata della tua frase originale. Prova tu stesso!</p><p>Grazie per aver letto quest'articolo.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Algoritmi di forza bruta ]]>
                </title>
                <description>
                    <![CDATA[ Gli algoritmi di forza bruta (o ricerca esaustiva) sono esattamente ciò che il loro nome suggerisce: sono dei metodi diretti di risoluzione di problemi che si basano sulla pura potenza computazionale utilizzando ogni possibilità, piuttosto che tecniche avanzate mirate a migliorare l'efficienza. Ad esempio, immagina di avere un lucchetto con ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/algoritmi-di-forza-bruta/</link>
                <guid isPermaLink="false">6284cf12be0d9a0529437f77</guid>
                
                    <category>
                        <![CDATA[ Algoritmi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dario Di Cillo ]]>
                </dc:creator>
                <pubDate>Thu, 26 May 2022 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2022/05/algo.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/brute-force-algorithms-explained/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Brute Force Algorithms Explained</a>
      </p><p>Gli algoritmi di forza bruta (o ricerca esaustiva) sono esattamente ciò che il loro nome suggerisce: sono dei metodi diretti di risoluzione di problemi che si basano sulla pura potenza computazionale utilizzando ogni possibilità, piuttosto che tecniche avanzate mirate a migliorare l'efficienza.</p><p>Ad esempio, immagina di avere un lucchetto con una combinazione a 4 cifre, ognuna da 0 a 9. Hai dimenticato la combinazione, ma non vuoi comprarne uno nuovo. Visto che non ricordi nessuna cifra, devi usare un metodo di forza bruta per aprirlo.</p><p>Quindi riporti tutti i numeri su 0 e inizi a provare tutti le possibili combinazioni, una alla volta: 0001, 0002, 0003 e così via finché non si apre. Nel caso peggiore, ti serviranno 10<sup>4</sup> (10,000) tentativi prima di trovare la combinazione corretta. </p><p>Nel campo informatico, un esempio classico è quello del problema del commesso viaggiatore. Supponiamo che un commesso abbia bisogno di recarsi in 10 città del Paese. Come si può determinare l'ordine in cui dovrà visitarle in modo da minimizzare la distanza totale percorsa?</p><p>La soluzione forza bruta si basa semplicemente sul calcolo della distanza di ogni possibile percorso per poi selezionare il più corto. Questo modo di operare non è particolarmente efficiente perché è possibile eliminare molti percorsi utilizzando degli algoritmi ingegnosi.</p><p>La complessità temporale di forza bruta è <strong>O(mn)</strong>, talvolta riportata come <strong><strong>O(n*m)</strong></strong>, quindi se cerchiamo una stringa di "n" caratteri in una stringa di "m" caratteri usando un metodo forza bruta, ci occorreranno n * m tentativi.</p><h2 id="pi-informazioni-sugli-algoritmi"><strong>Più informazioni sugli algoritmi</strong></h2><p>In informatica, un algoritmo è semplicemente un insieme di procedure sistematiche per risolvere un determinato problema. Gli algoritmi possono essere progettati per effettuare calcoli, processare dati o compiere operazioni logiche automatizzate.</p><p>Ecco la definizione di algoritmo secondo <a href="https://en.wikipedia.org/wiki/Algorithm">Wikipedia</a>:</p><blockquote>Un algoritmo è un metodo efficace che può essere espresso in uno spazio e in un tempo finiti e in un ben definito linguaggio formale per calcolare una funzione. Partendo da uno step iniziale e un input iniziale (anche nullo), le istruzioni descrivono un calcolo che, quando eseguito, procede attraverso un numero finito di stati ben precisati, producendo infine un "output" e terminando in uno stato finale. La transizione tra uno stato al successivo non è necessariamente deterministica; alcuni algoritmi, noti come randomizzati, utilizzano input casuali.</blockquote><p>Ci sono alcuni requisiti a cui un algoritmo deve attenersi. Un algoritmo deve essere:</p><ol><li>Deterministico: ogni step del processo deve essere definito in modo preciso.</li><li>Calcolabile: ogni step del processo deve poter essere svolto da un computer.</li><li>Finito: alla fine, il programma deve terminare con successo.</li></ol><p>Tra i tipi più comuni ci sono gli algoritmi di:</p><ul><li>Ordinamento</li><li>Ricerca</li><li>Compressione</li></ul><p>Le classi degli algoritmi includono</p><ul><li>Grafi</li><li>Programmazione dinamica</li><li>Ordinamento</li><li>Ricerca</li><li>Stringhe</li><li>Matematica</li><li>Geometria computazionale</li><li>Ottimizzazione</li><li>Altro</li></ul><p>Anche se tecnicamente non sono classi di algoritmi, le strutture di dati vengono spesso incluse nell'elenco.</p><h3 id="efficienza"><strong><strong><strong>Effic</strong></strong>ienza</strong></h3><p>Gli algoritmi vengono spesso valutati in base alla loro efficienza e alla quantità di risorse di calcolo di cui hanno bisogno per operare.</p><p>Un modo comune di valutare un algoritmo è considerare la sua complessità temporale, che riflette la crescita del tempo di esecuzione di un algoritmo all'aumentare della dimensione degli input. Dato che gli algoritmi al giorno d'oggi devono operare su dati di input consistenti, è essenziale che abbiano un tempo di esecuzione ragionevole.</p><h3 id="algoritmi-di-ordinamento"><strong>Algoritmi di ordinamento</strong></h3><p>Gli algoritmi di ordinamento sono piuttosto variabili a seconda della necessità. Tra quelli più comuni ci sono:</p><h4 id="quicksort"><strong><strong><strong>Quicksort</strong></strong></strong></h4><p>Non c'è discussione sull'ordinamento che possa terminare senza l'algoritmo quicksort. <a href="http://me.dt.in.th/page/Quicksort/">Qui</a> troverai i concetti di base.</p><h4 id="mergesort"><strong><strong><strong>Mergesort</strong></strong></strong></h4><p>È un algoritmo che si basa sul concetto usato per unire degli array in un unico array ordinato. <a href="https://www.geeksforgeeks.org/merge-sort/">Qui</a> puoi approfondire questo argomento.</p><p>Il curriculum di freeCodeCamp dà molta importanza alla creazione di algoritmi. Questo perché l'utilizzo di algoritmi è un ottimo modo per fare pratica con la programmazione ed è un'abilità molto richiesta per un lavoro da sviluppatore.</p><h2 id="libri-sugli-algoritmi-in-javascript-"><strong>Libri sugli algoritmi in JavaScript:</strong></h2><p><em><em>Data Structures in JavaScript</em></em></p><ul><li>Libro gratuito che parla di strutture di dati in JavaScript.</li><li><a href="https://www.gitbook.com/book/pmary/data-structure-in-javascript/details">GitBook</a></li></ul><p><em><em>Learning JavaScript Data Structures and Algorithms - Second Edition</em></em></p><ul><li>Tratta di programmazione orientata agli oggetti, Covers object oriented programming, ereditarietà prototipale, algoritmi di ordinamento e ricerca, quicksort, mergesort, alberi di ricerca binaria e concetti avanzati degli algoritmi.</li><li><a href="https://www.amazon.com/Learning-JavaScript-Data-Structures-Algorithms/dp/1785285491">Amazon</a></li><li>ISBN-13: 978-1785285493</li></ul><p><em><em>Data Structures and Algorithms with JavaScript: Bringing classic computing approaches to the Web</em></em></p><ul><li>Tratta di ricorsività, ordinamento e ricerca, Covers recursion, sorting and searching algorithms, liste concatenate e alberi di ricerca binaria.</li><li><a href="https://www.amazon.com/Data-Structures-Algorithms-JavaScript-approaches/dp/1449364934">Amazon</a></li><li>ISBN-13: 978-1449364939</li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ La notazione O-grande spiegata con esempi ]]>
                </title>
                <description>
                    <![CDATA[ La notazione O-grande è un modo di descrivere la velocità o la complessità di un dato algoritmo. Se il tuo progetto attuale richiede un algoritmo predefinito, è importante capire quanto questo sia veloce o lento rispetto ad altre opzioni. Cos'è la notazione O-grande e come funziona? La notazione O-grande esprime ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/la-notazione-o-grande-spiegata-con-esempi/</link>
                <guid isPermaLink="false">62837002be0d9a0529437c10</guid>
                
                    <category>
                        <![CDATA[ Algoritmi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dario Di Cillo ]]>
                </dc:creator>
                <pubDate>Mon, 23 May 2022 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2022/05/5f9c9cf0740569d1a4ca3502.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/big-o-notation-explained-with-examples/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Big O Notation Explained with Examples</a>
      </p><p>La notazione O-grande è un modo di descrivere la velocità o la complessità di un dato algoritmo. Se il tuo progetto attuale richiede un algoritmo predefinito, è importante capire quanto questo sia veloce o lento rispetto ad altre opzioni.</p><h2 id="cos-la-notazione-o-grande-e-come-funziona"><strong>Cos'è la notazione O-grande e come funziona?</strong></h2><p>La notazione O-grande esprime il numero di operazioni compiute da un algoritmo. Prende il nome dalla "O" maiuscola posta davanti al numero stimato di operazioni.</p><p>Quello che la notazione O-grande non ti dice è la velocità di un algoritmo in secondi. Ci sono troppi fattori che influenzano il tempo necessario all'esecuzione di un algoritmo. Invece, utilizzerai la notazione O-grande per confrontare algoritmi diversi tramite il numero di operazioni che svolgono.</p><h3 id="la-o-grande-definisce-il-tempo-di-esecuzione-per-il-caso-peggiore"><strong>La O-grande definisce il tempo di esecuzione per il caso peggiore</strong></h3><p>Immagina di essere un insegnante e di avere uno studente di nome Gianni. Vuoi trovare i suoi dati usando un algoritmo di ricerca semplice cercando nel database della tua scuola.</p><p>La ricerca semplice viene eseguita in O(n) volte. Ciò vuol dire che, nel caso peggiore, dovrai cercare tra tutti i singoli dati (rappresentati da n) per trovare quelli di Gianni.</p><p>Ma quando esegui la ricerca semplice, trovi che i dati di Gianni sono la prima voce del database. Non devi analizzare ogni voce perché hai trovato i dati che cercavi al primo tentativo. </p><p><em>Questo algoritmo impiega un tempo O(n)? Oppure O(1), visto che hai trovato i dati al primo colpo?</em></p><p>In questo caso, o(1) rappresenta il caso più favorevole - sei stato fortunato e hai trovato i dati di Gianni in cima al database. La notazione O-grande si riferisce al caso peggiore, che è o(n) per una ricerca semplice. La certezza è che la ricerca semplice non sarà mai più lunga di un tempo O(n).</p><h3 id="i-tempi-di-esecuzione-degli-algoritmi-crescono-a-velocit-diverse"><strong>I tempi di esecuzione degli algoritmi crescono a velocità diverse</strong></h3><p>Considera di impiegare 1 millisecondo per controllare ciascun elemento del database della scuola.</p><p>Con una ricerca semplice, se devi controllare 10 voci, impiegherai 10 ms, ma con un <em>algoritmo di ricerca binaria</em> devi controllare soltanto 3 elementi, impiegando 3 ms.</p><p>Nella maggior parte dei casi, la lista o il database in cui effettui la ricerca avranno centinaia o migliaia di elementi.</p><p>Se c'è 1 miliardo di elementi, usando la ricerca semplice il tempo massimo necessario è 1 miliardo di ms, o 11 giorni. D'altro canto, usando la ricerca binaria ci vorranno soltanto 32 ms nel caso più sfavorevole:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/03/31781165-723a053c-b500-11e7-937c-7b33db281efe.png" class="kg-image" alt="31781165-723a053c-b500-11e7-937c-7b33db281efe" width="600" height="400" loading="lazy"></figure><p>Chiaramente i tempi di esecuzione per una ricerca semplice e per una ricerca binaria non crescono alla stessa velocità. Man mano che le voce in elenco aumentano, la ricerca binaria impiega un po' più di tempo. Il tempo di esecuzione della ricerca semplice cresce esponenzialmente man mano che le voci in elenco aumentano.</p><p>Ecco perché è importante sapere come cresce il tempo di esecuzione in relazione alla dimensione di un elenco ed è esattamente ciò per cui la notazione O-grande risulta utile.</p><h3 id="la-notazione-o-grande-mostra-il-numero-di-operazioni"><strong>La notazione O-grande mostra il numero di operazioni</strong></h3><p>Come detto precedentemente, la notazione O-grande non mostra il <em>tempo </em>che un algoritmo impiega per essere eseguito, ma indica il numero di operazioni che svolge e quanto velocemente cresce in confronto ad altri.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/03/31781175-768c208e-b500-11e7-9718-e632d1391e2d.png" class="kg-image" alt="31781175-768c208e-b500-11e7-9718-e632d1391e2d" width="600" height="400" loading="lazy"></figure><p>Ecco alcuni algoritmi comuni con i relativi tempi di esecuzione in notazione O-grande:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Notazione O-grande</th>
<th>Algoritmo di esempio</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(log n)</td>
<td>Ricerca binaria</td>
</tr>
<tr>
<td>O(n)</td>
<td>Ricerca semplice</td>
</tr>
<tr>
<td>O(n * log n)</td>
<td>Quicksort</td>
</tr>
<tr>
<td>O(n<sup>2</sup>)</td>
<td>Ordinamento per selezione</td>
</tr>
<tr>
<td>O(n!)</td>
<td>Commesso viaggiatore</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Adesso ne sai abbastanza sulla notazione O-grande per iniziare a confrontare algoritmi!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ L'algoritmo dei cammini minimi di Dijkstra - Una dettagliata introduzione grafica ]]>
                </title>
                <description>
                    <![CDATA[ Se hai sempre voluto conoscere e capire l'algoritmo di Dijkstra, questo è l'articolo che fa per te. Vedrai come funziona passo passo con delle spiegazioni grafiche.  In quest'articolo imparerai:  * Concetti di base dei grafi (un breve riassunto).  * Per cosa viene utilizzato l'algoritmo di Dijkstra.  ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/lalgoritmo-dei-cammini-minimi-di-dijkstra-una-dettagliata-introduzione-grafica/</link>
                <guid isPermaLink="false">6278e99a9e71e9053df3493c</guid>
                
                    <category>
                        <![CDATA[ Algoritmi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dario Di Cillo ]]>
                </dc:creator>
                <pubDate>Fri, 20 May 2022 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2022/05/Algorithm-Image-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/dijkstras-shortest-path-algorithm-visual-introduction/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Dijkstra's Shortest Path Algorithm - A Detailed and Visual Introduction</a>
      </p><p>Se hai sempre voluto conoscere e capire l'algoritmo di Dijkstra, questo è l'articolo che fa per te. Vedrai come funziona passo passo con delle spiegazioni grafiche. </p><p><strong>In quest'articolo imparerai<strong>:</strong></strong></p><ul><li>Concetti di base dei grafi (un breve riassunto).</li><li>Per cosa viene utilizzato l'algoritmo di Dijkstra.</li><li>Come funziona in background con degli esempi.</li></ul><p><strong>Iniziamo!<strong> ✨</strong></strong></p><h2 id="-introduzione-ai-grafi"><strong>🔹 Introduzione ai grafi</strong></h2><p>Iniziamo con una breve introduzione sui grafi.</p><h3 id="concetti-di-base"><strong>Concetti di base</strong></h3><p>I grafi sono strutture di dati usate per rappresentare "connessioni" tra coppie di elementi.</p><ul><li>Questi elementi vengono chiamati <strong>nodi </strong>e possono rappresentare oggetti della vita reale, persone o entità.</li><li>Le connessioni tra i nodi sono chiamate <strong>archi</strong>.</li></ul><p>Ecco una rappresentazione grafica di un grafo:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-123.png" class="kg-image" alt="image-123" width="600" height="400" loading="lazy"></figure><p>I<strong> nodi</strong> sono rappresentati con dei cerchi colorati e gli <strong>archi </strong>con delle linee che connettono i cerchi.</p><p><strong><strong>💡 </strong>Suggerimento<strong>: </strong></strong>due nodi sono connessi se c'è un arco tra di loro.</p><h3 id="applicazioni"><strong>Applicazioni</strong></h3><p>I grafi vengono utilizzati direttamente in situazioni reali. Ad esempio, possiamo usarli per progettare una rete di trasporto in cui i nodi rappresentano le strutture che inviano o ricevono dei prodotti e gli archi rappresentano le strade o i percorsi che le collegano (vedi qui sotto).</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-121.png" class="kg-image" alt="image-121" width="600" height="400" loading="lazy"><figcaption>Una rete di distribuzione rappresentata con un grafo</figcaption></figure><h3 id="tipi-di-grafi"><strong>Tipi di grafi</strong></h3><p>I grafi possono essere:</p><ul><li><strong>Non orientati<strong>: </strong></strong>se per ogni coppia di nodi connessi, puoi andare da un nodo all'altro in entrambi i versi.</li><li><strong>Orientati<strong>: </strong></strong>se per ogni coppia di nodi connessi, puoi muoverti da un nodo all'altro soltanto in una verso specifico. Utilizziamo delle frecce, invece di semplici linee, per rappresentare gli archi orientati.</li></ul><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-124.png" class="kg-image" alt="image-124" width="600" height="400" loading="lazy"><figcaption>Un esempio di grafo non orientato (sinistra) e orientato (destra).</figcaption></figure><p><strong><strong>💡 </strong>Suggerimento<strong>:</strong></strong> in quest'articolo, avremo a che fare con grafi <strong>non orientati</strong>.</p><h3 id="grafi-pesati"><strong>Grafi pesati</strong></h3><p>Un <strong>grafo pesato</strong> è un grafo ai cui archi è associato un "peso" o un "costo". Il peso di un arco può rappresentare una distanza, un tempo o qualsiasi altra cosa relativa alla connessione che l'arco stabilisce tra due nodi.</p><p>Ad esempio, nel grafo pesato qui sotto, puoi vedere un numero in blu, riportato vicino ad ogni arco. Questo numero viene usato per rappresentare il peso dell'arco corrispondente.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-126.png" class="kg-image" alt="image-126" width="600" height="400" loading="lazy"></figure><p><strong><strong>💡 </strong>Suggerimento<strong>:</strong></strong> come vedrai tra poco, questi pesi sono essenziali per l'algoritmo di Dijkstra.</p><h2 id="-introduzione-all-algoritmo-di-dijkstra"><strong>🔸 Introduzione all'algoritmo di Dijkstra</strong></h2><p>Adesso che conosci i concetti di base dei grafi, possiamo iniziare a parlare di questo meraviglioso algoritmo.</p><ul><li>Scopo e casi di utilizzo</li><li>Storia</li><li>Concetti di base dell'algoritmo di Dijkstra</li><li>Requisiti</li></ul><h3 id="scopo-e-casi-di-utilizzo"><strong>Scopo e casi di utilizzo</strong></h3><p>Con l'algoritmo di Dijkstra, puoi trovare il cammino minimo che intercorre tra i nodi di un grafo. In particolare, puoi <strong>trovare il cammino minimo tra un nodo (chiamato "nodo sorgente") e tutti gli altri nodi del grafo</strong>, producendo un albero dei cammini minimi.</p><p>Questo algoritmo viene usato nei dispositivi GPS per cercare il cammino minimo tra la località attuale e quella di destinazione e ha ampie applicazione nell'industria, specialmente in campi che richiedono la modellazione di network.</p><h3 id="storia"><strong>Storia</strong></h3><p>Questo algoritmo è stato creato e pubblicato da <a href="https://it.wikipedia.org/wiki/Edsger_Dijkstra">Dr. Edsger W. Dijkstra</a>, un brillante informatico olandese e ingegnere del software. </p><p>Nel 1959, ha pubblicato un articolo di 3 pagine intitolato "A note on two problems in connexion with graphs" in cui ha illustrato il suo nuovo algoritmo.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/italian/news/content/images/2022/05/image-1.png" class="kg-image" alt="image-1" srcset="https://www.freecodecamp.org/italian/news/content/images/size/w600/2022/05/image-1.png 600w, https://www.freecodecamp.org/italian/news/content/images/size/w1000/2022/05/image-1.png 1000w, https://www.freecodecamp.org/italian/news/content/images/2022/05/image-1.png 1200w" sizes="(min-width: 720px) 720px" width="1200" height="822" loading="lazy"><figcaption><a href="https://commons.wikimedia.org/wiki/File:Edsger_Dijkstra_1994.jpg">Dr. Edsger W. Dijkstra</a> al <a href="https://it.wikipedia.org/wiki/Politecnico_federale_di_Zurigo">Politecnico federale di Zurigo</a> nel 1994 (immagine di Andreas F. Borchert)</figcaption></figure><p>Durante un'intervista nel 2001, il Dr. Dijkstra ha rivelato come e perché ha sviluppato l'algortitmo:</p><blockquote>Come ottenere il percorso più breve per viaggiare da Rotterdam a Groningen? Con l'algoritmo per il cammino minimo che ho creato in circa 20 minuti. Una mattina, mi trovavo con la mia giovane fidanzata a fare shopping ad Amsterdam. Eravamo stanchi e ci siamo seduti sulla terrazza di un bar a bere un caffè, mentre stavo pensando a come risolvere questo problema e poi ho elaborato l'algoritmo per il cammino minimo. Come ho detto, è stata una invenzione da 20 minuti. Il lavoro fu pubblicato nel 1959, tre anni dopo. La pubblicazione è piuttosto bella e una &nbsp;delle ragioni che la rende così bella è che ho progettato il tutto senza carta e penna. In questo modo, sei quasi forzato a evitare ogni inutile complessità. Alla fine quell'algoritmo è diventato, con mio grande stupore, una delle pietre angolari della mia fama. - Come citato nell'articolo <a href="https://en.wikipedia.org/wiki/Edsger_W._Dijkstra">Edsger W. Dijkstra</a> da <a href="https://dl.acm.org/doi/pdf/10.1145/1787234.1787249">An interview with Edsger W. Dijkstra</a>.</blockquote><p>⭐ <strong>Incredibile<strong>, </strong>vero<strong>?</strong></strong> In soli 20 minuti, il Dr. Dijkstra ha elaborato uno dei più famosi algoritmi nella storia dell'informatica.</p><h3 id="concetti-di-base-dell-algoritmo-di-dijkstra"><strong>Concetti di base dell'algoritmo di Dijkstra</strong></h3><ul><li>Sostanzialmente, l'algoritmo di Dijkstra parte dal nodo che scegli (il nodo sorgente) e analizza il grafo per trovare il percorso più breve tra quel nodo e tutti gli altri nodi del grafo.</li><li>L'algoritmo tiene traccia delle attuali distanze più brevi tra ogni nodo e il nodo sorgente e aggiorna questi valori quando trova un percorso più breve.</li><li>Una volta che l'algoritmo ha trovato il tragitto più breve tra il nodo sorgente e un altro nodo, quest'ultimo viene segnato come "visitato" a aggiunto al percorso.</li><li>Il processo continua finché tutti i nodi nel grafo sono stati aggiunti al percorso. In questo modo, otteniamo un percorso che connette il nodo sorgente a tutti gli altri nodi seguendo il cammino più breve possibile per raggiungere ogni nodo.</li></ul><h3 id="requisiti"><strong>Requisiti</strong></h3><p>L'algoritmo di Dijkstra funziona soltanto con grafi che hanno pesi <strong>positivi</strong>. Questo perché, durante il processo, i pesi degli archi vengono sommati per trovare il cammino minimo.</p><p>Se nel grafo è presente un peso negativo, l'algoritmo non funzionerà adeguatamente. Una volta che un nodo viene segnato come "visitato", il percorso attuale per quel nodo viene indicato come il più breve per raggiungerlo. Un peso negativo può alterare il processo se il peso totale viene ridotto dopo questo step.</p><h2 id="-esempi-dell-algoritmo-di-dijkstra"><strong>🔹 Esempi dell'algoritmo di Dijkstra</strong></h2><p>Ora che ne sai di più su quest'algoritmo, vediamo come funziona dietro le quinte con un esempio passo passo.</p><p>Consideriamo il grafo:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-76.png" class="kg-image" alt="image-76" width="600" height="400" loading="lazy"></figure><p>L'algoritmo genererà il percorso più breve tra il nodo <code>0</code> e tutti gli altri nodi nel grafo.</p><p><strong><strong>💡 </strong>Suggerimento<strong>: </strong></strong>in questo grafo, assumeremo che i pesi degli archi rappresentino le distanze tra due nodi.</p><p>Il percorso più breve sarà quello tra il nodo <code>0</code> e il nodo <code>1</code>, dal nodo <code>0</code> al nodo <code>2</code>, dal nodo <code>0</code> al nodo <code>3</code> e così via per ogni nodo del grafo.</p><p>Inizialmente, abbiamo la lista delle distanze riportata di seguito:</p><ul><li>La distanza tra il nodo sorgente e sé stesso è <code>0</code>. In quest'esempio, il nodo sorgente sarà il nodo <code>0</code> ma possiamo scegliere qualsiasi nodo.</li><li>La distanza dal nodo sorgente a tutti gli altri nodi non è ancora stata determinata, quindi utilizziamo il simbolo di infinito per rappresentare questa condizione iniziale.</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-77.png" class="kg-image" alt="image-77" width="600" height="400" loading="lazy"></figure><p>Abbiamo anche la seguente lista, che tiene traccia dei nodi che non sono stati ancora visitati (i nodi che non sono ancora stati inclusi nel percorso):</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-78.png" class="kg-image" alt="image-78" width="600" height="400" loading="lazy"></figure><p><strong><strong>💡 </strong>Suggerimento<strong>: </strong></strong>ricorda che l'algoritmo è completato una volta che tutti i nodi sono stati aggiunti al percorso.</p><p>Dato che abbiamo deciso di partire dal nodo <code>0</code>, possiamo segnare questo nodo come visitato. In modo equivalente, lo cancelliamo dalla lista dei nodi non visitati e aggiungiamo un contorno rosso al nodo corrispondente nel diagramma:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-87.png" class="kg-image" alt="image-87" width="600" height="400" loading="lazy"></figure><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-83.png" class="kg-image" alt="image-83" width="600" height="400" loading="lazy"></figure><p>Adesso abbiamo bisogno di iniziare a verificare le distanze tra il nodo <code>0</code> e i nodi adiacenti. Come puoi vedere, si tratta dei nodi <code>1</code> e<code>2</code> (vedi gli archi rossi):</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-89.png" class="kg-image" alt="image-89" width="600" height="400" loading="lazy"></figure><p><strong><strong>💡 </strong>Suggerimento<strong>:</strong></strong> questo non significa che stiamo aggiungendo immediatamente i due nodi adiacenti al percorso più breve. Prima di fare ciò, dobbiamo verificare se abbiamo trovato il percorso più breve per raggiungerli. Stiamo semplicemente svolgendo una valutazione iniziale per analizzare le opzioni disponibili.</p><p>Dobbiamo aggiornare le distanze dal nodo <code>0</code> al nodo <code>1</code> e al nodo <code>2</code> con i pesi degli archi che li connettono al nodo <code>0</code> (il nodo sorgente). Questi pesi sono rispettivamente 2 e 6:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-90.png" class="kg-image" alt="image-90" width="600" height="400" loading="lazy"></figure><p>Una volta aggiornate le distanze dei nodi adiacenti, abbiamo bisogno di:</p><ul><li>Selezionare il nodo più vicino al nodo sorgente in base alle distanze attuali conosciute.</li><li>Segnarlo come visitato.</li><li>Aggiungerlo al percorso.</li></ul><p>Se controlliamo la lista delle distanze, possiamo vedere che al nodo <code>1</code> corrisponde la distanza minore dal nodo sorgente (distanza di 2), quindi lo aggiungiamo al percorso.</p><p>Nel diagramma, lo rappresentiamo con un arco rosso:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-94.png" class="kg-image" alt="image-94" width="600" height="400" loading="lazy"></figure><p>Lo segniamo con un quadrato rosso nella lista per indicare che è stato visitato e che abbiamo trovato il percorso più breve per questo nodo:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-92.png" class="kg-image" alt="image-92" width="600" height="400" loading="lazy"></figure><p>E lo cancelliamo dalla lista dei nodi non visitati:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-93.png" class="kg-image" alt="image-93" width="600" height="400" loading="lazy"></figure><p>Adesso dobbiamo analizzare i nuovi nodi adiacenti per trovare il percorso più breve per raggiungerli. Consideriamo soltanto i nodi adiacenti ai nodi che fanno già parte del percorso più breve (quello marcata dagli archi rossi).</p><p>I nodi <code>3</code> e <code>2</code> sono entrambi vicini ai nodi che fanno già parte del percorso perché, come puoi vedere qui sotto, sono direttamente collegati rispettivamente ai nodi <code>1</code> e <code>0</code>. Questi sono i nodi che analizzeremo nel prossimo passaggio.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-94.png" class="kg-image" alt="image-94" width="600" height="400" loading="lazy"></figure><p>Dato che abbiamo già la distanza tra il nodo sorgente e il nodo <code>2</code> scritta nella nostra lista, stavolta non dobbiamo aggiornarla. Dobbiamo soltanto aggiornare la distanza tra il nodo sorgente e il nuovo nodo adiacente (nodo <code>3</code>):</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-98.png" class="kg-image" alt="image-98" width="600" height="400" loading="lazy"></figure><p>La distanza è <strong><strong>7</strong></strong>. Vediamo perché.</p><p>Per trovare la distanza tra nodo sorgente e un altro nodo (in questo caso, il nodo <code>3</code>), aggiungiamo i pesi di tutti gli archi che determinano il cammino minimo per raggiungere il nodo:</p><ul><li><strong>Per il nodo<strong> <code>3</code>:</strong></strong> la distanza totale è <strong><strong>7</strong></strong> perché aggiungiamo i pesi degli archi che formano il percorso <code>0 -&gt; 1 -&gt; 3</code> (2 &nbsp;per l'arco <code>0 -&gt; 1</code> e 5 per l'arco <code>1 -&gt; 3</code>).</li></ul><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-94.png" class="kg-image" alt="image-94" width="600" height="400" loading="lazy"></figure><p>Ora che conosciamo la distanza dei nodi adiacenti, dobbiamo scegliere quale nodo verrà aggiunto al percorso. Dobbiamo selezionare un nodo <strong>non visitato</strong> con la distanza minore (attualmente conosciuta) dal nodo sorgente.</p><p>Se diamo un'occhiata alla lista delle distanze, possiamo immediatamente renderci conto che è il nodo <code>2</code> alla distanza <strong>6</strong>:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-98.png" class="kg-image" alt="image-98" width="600" height="400" loading="lazy"></figure><p>Lo aggiungiamo al percorso graficamente contrassegnandolo in rosso:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-96.png" class="kg-image" alt="image-96" width="600" height="400" loading="lazy"></figure><p>Segniamo il nodo come visitato aggiungendo un quadratino rosso nella lista delle distanze e cancellandolo dalla lista dei nodi non visitati:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-99.png" class="kg-image" alt="image-99" width="600" height="400" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-100.png" class="kg-image" alt="image-100" width="600" height="400" loading="lazy"></figure><p>Adesso abbiamo bisogno di ripetere il processo per trovare il percorso più breve dal nodo sorgente al nuovo nodo adiacente, ovvero il nodo <code>3</code>.</p><p>Come puoi notare, ci sono due percorsi possibili: <code>0 -&gt; 1 -&gt; 3</code> o <code>0 -&gt; 2 -&gt; 3</code>. Vediamo come stabilire quale dei due è il più corto.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-96.png" class="kg-image" alt="image-96" width="600" height="400" loading="lazy"></figure><p>La distanza del nodo <code>3</code> è già stata inserita nella lista (7, vedi la lista precedente). Questa distanza risultava da uno step precedente, in cui abbiamo aggiunto i pesi 5 e 2 degli archi da percorrere nel tragitto <code>0 -&gt; 1 -&gt; 3</code>.</p><p>Adesso, abbiamo un'altra alternativa. Se scegliamo di seguire il percorso <code>0 -&gt; 2 -&gt; 3</code>, avremo bisogno di passare per gli archi <code>0 -&gt; 2</code> and <code>2 -&gt; 3</code> che hanno rispettivamente peso <strong>6 </strong>e <strong>8</strong>, per una distanza totale di <strong>14</strong>.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-101.png" class="kg-image" alt="image-101" width="600" height="400" loading="lazy"></figure><p>Chiaramente, la prima distanza è minore (7 vs 14), così sceglieremo il percorso originario <code>0 -&gt; 1 -&gt; 3</code>. <strong>Aggiorniamo la distanza solo se il nuovo percorso è più corto</strong>.</p><p>Quindi, aggiungiamo questo nodo al percorso usando la prima alternativa: <code>0 -&gt; 1 -&gt; 3</code>.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-104.png" class="kg-image" alt="image-104" width="600" height="400" loading="lazy"></figure><p>Segniamo il nodo come visitato e lo cancelliamo dalla lista dei nodi non visitati:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-103.png" class="kg-image" alt="image-103" width="600" height="400" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-106.png" class="kg-image" alt="image-106" width="600" height="400" loading="lazy"></figure><p>E ripetiamo di nuovo il processo.</p><p>Valutiamo i nodi adiacenti che non sono stati ancora visitati. Questa volta, si tratta dei nodi <code>4</code> e <code>5</code> dato che sono contigui al nodo <code>3</code>.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-104.png" class="kg-image" alt="image-104" width="600" height="400" loading="lazy"></figure><p>Aggiorniamo le distanze di questi nodi dal nodo sorgente, cercando sempre il tragitto più corto:</p><ul><li><strong>Per il nodo<strong> <code>4</code>:</strong></strong> la distanza è <strong><strong>17</strong></strong>, seguendo il percorso &nbsp;<code>0 -&gt; 1 -&gt; 3 -&gt; 4</code>.</li><li><strong>Per il nodo<strong> <code>5</code>:</strong></strong> la distanza è <strong><strong>22</strong></strong> seguendo il percorso <code>0 -&gt; 1 -&gt; 3 -&gt; 5</code>.</li></ul><p><strong><strong>💡 </strong>Suggerimento<strong>:</strong></strong> nota che abbiamo considerato soltanto di estendere il percorso più breve (segnato in rosso). Non possiamo considerare di passare attraverso archi che non sono stati aggiunti al percorso più breve (ad esempio, non possiamo usare il percorso che contiene l'arco <code>2 -&gt; 3</code>).</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-105.png" class="kg-image" alt="image-105" width="600" height="400" loading="lazy"></figure><p>Adesso, dobbiamo scegliere quale nodo non visitato verrà segnato come visitato. In questo caso, si tratta del nodo <code>4</code> perché ha la distanza minore nella lista delle distanze. Lo aggiungiamo graficamente al diagramma:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-108.png" class="kg-image" alt="image-108" width="600" height="400" loading="lazy"></figure><p>Inoltre, lo segniamo &nbsp;come "visitato" aggiungendo un quadratino rosso nella lista:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-107.png" class="kg-image" alt="image-107" width="600" height="400" loading="lazy"></figure><p>E lo cancelliamo dalla lista dei nodi non visitati:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-109.png" class="kg-image" alt="image-109" width="600" height="400" loading="lazy"></figure><p>Ripetiamo ancora il processo. Valutiamo i nodi adiacenti: nodi <code>5</code> e <code>6</code>. Dobbiamo analizzare ogni possibile percorso che possiamo seguire per raggiungerli partendo dai nodi già visitati e aggiunti al percorso.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-108.png" class="kg-image" alt="image-108" width="600" height="400" loading="lazy"></figure><p><strong>Per il nodo<strong> <code>5</code>:</strong></strong></p><ul><li>La prima opzione è di seguire il percorso <code>0 -&gt; 1 -&gt; 3 -&gt; 5</code>, che ha distanza <strong><strong>22 </strong></strong>dal nodo sorgente (2 + 5 + 15). Questa distanza è già stata registrata nella lista delle distanze nello step precedente.</li><li>La seconda opzione è di seguire il percorso <code>0 -&gt; 1 -&gt; 3 -&gt; 4 -&gt; 5</code>, che ha distanza <strong><strong>23 </strong></strong>dal nodo sorgente (2 + 5 + 10 + 6).</li></ul><p>Ovviamente, il primo percorso è più corto e lo scegliamo per raggiungere il nodo <code>5</code>.</p><p><strong>Per il nodo<strong> <code>6</code>:</strong></strong></p><ul><li>Il percorso disponibile è <code>0 -&gt; 1 -&gt; 3 -&gt; 4 -&gt; 6</code>, che ha distanza <strong><strong>19</strong></strong> dal nodo sorgente (2 + 5 + 10 + 2).</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-110.png" class="kg-image" alt="image-110" width="600" height="400" loading="lazy"></figure><p>Segniamo come visitato il nodo con la distanza (attualmente conosciuta) più breve. In questo caso, il nodo <code>6</code>.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-140.png" class="kg-image" alt="image-140" width="600" height="400" loading="lazy"></figure><p>E lo cancelliamo dalla lista dei nodi non visitati:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-111.png" class="kg-image" alt="image-111" width="600" height="400" loading="lazy"></figure><p>Adesso, abbiamo questo percorso (segnato in rosso):</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-112.png" class="kg-image" alt="image-112" width="600" height="400" loading="lazy"></figure><p>Soltanto il nodo <code>5</code> non è stato ancora visitato. Vediamo come includerlo nel percorso.</p><p>Ci sono tre diversi percorsi che possiamo usare per raggiungere il nodo <code>5</code> dai nodi che abbiamo aggiunto al percorso:</p><ul><li><strong><strong>Op</strong>zione<strong> 1: </strong></strong><code>0 -&gt; 1 -&gt; 3 -&gt; 5</code> con una distanza di <strong><strong>22 </strong></strong>(2 + 5 + 15).</li><li><strong><strong>Op</strong>zione<strong> 2: </strong></strong><code>0 -&gt; 1 -&gt; 3 -&gt; 4 -&gt; 5</code> con una distanza di <strong><strong>23</strong></strong> (2 + 5 + 10 + 6).</li><li><strong><strong>Op</strong>zione<strong> 3:</strong></strong> <code>0 -&gt; 1 -&gt; 3 -&gt; 4 -&gt; 6 -&gt; 5</code> con una distanza di <strong><strong>25 </strong></strong>(2 + 5 + 10 + 2 + 6).</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-113.png" class="kg-image" alt="image-113" width="600" height="400" loading="lazy"></figure><p>Selezioniamo il percorso più breve: <code>0 -&gt; 1 -&gt; 3 -&gt; 5</code> con una distanza di <strong><strong>22</strong></strong>.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-115.png" class="kg-image" alt="image-115" width="600" height="400" loading="lazy"></figure><p>Segniamo il nodo come visitato e lo cancelliamo dalla lista dei nodi non visitati:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-114.png" class="kg-image" alt="image-114" width="600" height="400" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-116.png" class="kg-image" alt="image-116" width="600" height="400" loading="lazy"></figure><p><strong>E<strong> voilà!</strong></strong> Abbiamo il risultato finale del percorso più breve dal nodo <code>0</code> ad ogni nodo del grafo.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-115.png" class="kg-image" alt="image-115" width="600" height="400" loading="lazy"></figure><p>Nel diagramma, il percorso più breve è segnato in rosso. Devi seguire questi archi per percorrere il cammino minimo che collega un dato nodo con quello sorgente (nodo <code>0</code>).</p><p>Ad esempio, se vuoi raggiungere il nodo <code>6</code> partendo dal nodo <code>0</code>, devi semplicemente seguire gli archi evidenziati in rosso e percorrerai automaticamente <code>0 -&gt; 1 -&gt; 3 -&gt; 4 - &gt; 6</code>.</p><h2 id="-in-conclusione"><strong>🔸 In conclusione</strong></h2><ul><li>I grafi vengono utilizzati per creare modelli di connessioni tra oggetti, persone o entità. Sono costituiti da due elementi principali: nodi e archi. I nodi rappresentano gli oggetti e gli archi rappresentano le connessioni tra essi.</li><li>L'algoritmo di Dijkstra trova il cammino minimo tra un dato nodo (denominato "sorgente") e tutti gli altri nodi in un grafo.</li><li>Questo algoritmo utilizza i pesi degli archi per trovare il percorso che minimizza la distanza totale tra il nodo sorgente e gli altri nodi.</li></ul><p><strong>Spero davvero che quest'articolo ti sia piaciuto e che ti abbia aiutato<strong>. </strong></strong>Adesso sai come funziona l'algoritmo di Dijkstra.</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
