Ricorsione Avanzata
Backtracking, Programmazione Dinamica (DP), Divide & Conquer. Le tecniche algoritmiche più potenti.
1. Divide & Conquer
Paradigma: Dividi il problema in sottoproblemi più piccoli → Risolvi ricorsivamente → Combina le soluzioni. Esempi: Merge Sort, Quick Sort, FFT, Strassen.
#include <stdio.h>
// Potenza in O(log n) invece di O(n)
double potenza(double base, int esp) {
if (esp == 0) return 1.0;
if (esp % 2 == 0) {
double meta = potenza(base, esp/2);
return meta * meta; // Divide a metà
}
return base * potenza(base, esp-1);
}
// Massimo sottoarray (Kadane modificato con D&C)
int max_crossing_subarray(int arr[], int sx, int mid, int dx) {
int sx_max = arr[mid], sx_sum = arr[mid];
for (int i=mid-1; i>=sx; i--) {
sx_sum += arr[i];
if (sx_sum > sx_max) sx_max = sx_sum;
}
int dx_max = arr[mid+1], dx_sum = arr[mid+1];
for (int i=mid+2; i<=dx; i++) {
dx_sum += arr[i];
if (dx_sum > dx_max) dx_max = dx_sum;
}
return sx_max + dx_max;
}
int max_subarray_dc(int arr[], int sx, int dx) {
if (sx == dx) return arr[sx];
int mid = (sx+dx)/2;
int sx_max = max_subarray_dc(arr, sx, mid);
int dx_max = max_subarray_dc(arr, mid+1, dx);
int cross = max_crossing_subarray(arr, sx, mid, dx);
return sx_max > dx_max ? (sx_max > cross ? sx_max : cross)
: (dx_max > cross ? dx_max : cross);
}
// Contare le inversioni (D&C durante merge sort)
long long conta_inversioni(int arr[], int tmp[], int sx, int dx) {
if (sx >= dx) return 0;
int mid = (sx+dx)/2;
long long inv = 0;
inv += conta_inversioni(arr, tmp, sx, mid);
inv += conta_inversioni(arr, tmp, mid+1, dx);
// Merge e conta inversioni
int i=sx, j=mid+1, k=sx;
while (i<=mid && j<=dx) {
if (arr[i] <= arr[j]) tmp[k++] = arr[i++];
else { tmp[k++] = arr[j++]; inv += (mid-i+1); }
}
while (i<=mid) tmp[k++]=arr[i++];
while (j<=dx) tmp[k++]=arr[j++];
for(int l=sx;l<=dx;l++) arr[l]=tmp[l];
return inv;
}
int main() {
printf("2^10 = %.0f\n", potenza(2.0, 10));
printf("3^20 = %.0f\n", potenza(3.0, 20));
int arr[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
int n=9;
printf("Max sottoarray: %d\n", max_subarray_dc(arr, 0, n-1)); // 6
int arr2[] = {5,3,1,2,4};
int tmp[5];
printf("Inversioni in [5,3,1,2,4]: %lld\n",
conta_inversioni(arr2, tmp, 0, 4)); // 7
return 0;
}
2. Backtracking
Tecnica per trovare tutte le soluzioni (o una soluzione) di un problema con vincoli: prova una scelta, avanza, e se non porta a una soluzione torna indietro (backtrack) e prova la prossima.
#include <stdio.h>
#include <string.h>
#define MAX_N 12
int board[MAX_N]; // board[i] = colonna della regina nella riga i
int soluzioni = 0;
int valido(int riga, int col, int n) {
for (int r=0; r<riga; r++) {
if (board[r] == col) return 0; // Stessa colonna
if (abs(board[r]-col) == abs(r-riga)) return 0; // Stessa diagonale
}
return 1;
}
void stampa_board(int n) {
printf("Soluzione %d:\n", ++soluzioni);
for (int i=0; i<n; i++) {
for (int j=0; j<n; j++)
printf("%s ", j==board[i] ? "Q" : ".");
printf("\n");
}
printf("\n");
}
void n_queens(int riga, int n, int stampa) {
if (riga == n) {
if (stampa) stampa_board(n);
else soluzioni++;
return;
}
for (int col=0; col<n; col++) {
if (valido(riga, col, n)) {
board[riga] = col;
n_queens(riga+1, n, stampa);
// Backtrack: la prossima iterazione sovrascrive board[riga]
}
}
}
int main() {
// Mostra tutte le soluzioni per N=4
soluzioni = 0;
n_queens(0, 4, 1);
printf("N=4: %d soluzioni\n\n", soluzioni);
// Solo conta per N maggiori
for (int n=1; n<=10; n++) {
soluzioni = 0;
n_queens(0, n, 0);
printf("N=%2d: %5d soluzioni\n", n, soluzioni);
}
return 0;
}
#include <stdio.h>
#define N 9
int board[N][N] = {
{5,3,0, 0,7,0, 0,0,0},
{6,0,0, 1,9,5, 0,0,0},
{0,9,8, 0,0,0, 0,6,0},
{8,0,0, 0,6,0, 0,0,3},
{4,0,0, 8,0,3, 0,0,1},
{7,0,0, 0,2,0, 0,0,6},
{0,6,0, 0,0,0, 2,8,0},
{0,0,0, 4,1,9, 0,0,5},
{0,0,0, 0,8,0, 0,7,9}
};
int valido_sudoku(int riga, int col, int num) {
for(int i=0;i<N;i++) if(board[riga][i]==num || board[i][col]==num) return 0;
int br=riga-riga%3, bc=col-col%3;
for(int i=br;i<br+3;i++) for(int j=bc;j<bc+3;j++) if(board[i][j]==num) return 0;
return 1;
}
int risolvi() {
for(int r=0;r<N;r++) for(int c=0;c<N;c++) {
if(board[r][c]==0) {
for(int num=1;num<=9;num++) {
if(valido_sudoku(r,c,num)) {
board[r][c]=num;
if(risolvi()) return 1;
board[r][c]=0; // Backtrack
}
}
return 0; // Nessun numero valido → torna indietro
}
}
return 1; // Tutti i campi riempiti
}
void stampa() {
for(int i=0;i<N;i++) {
if(i%3==0&&i) printf("------+-------+------\n");
for(int j=0;j<N;j++) {
if(j%3==0&&j) printf("| ");
printf("%d ", board[i][j]);
}
printf("\n");
}
}
int main() {
printf("Sudoku da risolvere:\n"); stampa();
if(risolvi()) { printf("\nSoluzione:\n"); stampa(); }
else printf("Nessuna soluzione!\n");
return 0;
}
3. Programmazione Dinamica (DP)
Tecnica per ottimizzare la ricorsione: ogni sottoproblema viene risolto una sola volta e il risultato viene memorizzato (memoization o tabulation).
#include <stdio.h>
#include <string.h>
// =========== 1. FIBONACCI con DP ===========
long long fib_dp(int n) {
if (n <= 1) return n;
long long dp[n+1];
dp[0]=0; dp[1]=1;
for (int i=2; i<=n; i++) dp[i] = dp[i-1] + dp[i-2];
return dp[n];
}
// =========== 2. ZAINO 0/1 (Knapsack) ===========
// Oggetti con peso e valore, zaino con capacità W.
// Massimizza il valore totale senza superare W.
int zaino(int pesi[], int valori[], int n, int W) {
int dp[n+1][W+1];
memset(dp, 0, sizeof(dp));
for (int i=1; i<=n; i++) {
for (int w=0; w<=W; w++) {
dp[i][w] = dp[i-1][w]; // Non prendo l'oggetto i
if (pesi[i-1] <= w) {
int prendo = dp[i-1][w-pesi[i-1]] + valori[i-1];
if (prendo > dp[i][w]) dp[i][w] = prendo;
}
}
}
return dp[n][W];
}
// =========== 3. LCS (Longest Common Subsequence) ===========
int lcs(const char *s1, const char *s2) {
int m=strlen(s1), n=strlen(s2);
int dp[m+1][n+1];
memset(dp, 0, sizeof(dp));
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++)
if (s1[i-1]==s2[j-1]) dp[i][j] = dp[i-1][j-1]+1;
else dp[i][j] = dp[i-1][j]>dp[i][j-1] ? dp[i-1][j] : dp[i][j-1];
// Ricostruzione della LCS
printf("LCS: ");
int i=m, j=n;
char result[100]; int ri=0;
while (i>0 && j>0) {
if (s1[i-1]==s2[j-1]) { result[ri++]=s1[i-1]; i--; j--; }
else if (dp[i-1][j]>dp[i][j-1]) i--;
else j--;
}
result[ri]='\0';
// Inverti
for(int a=0,b=ri-1;a<b;a++,b--){char t=result[a];result[a]=result[b];result[b]=t;}
printf("%s (lunghezza %d)\n", result, ri);
return dp[m][n];
}
// =========== 4. COIN CHANGE ===========
// Quante monete (minimo) per fare amount?
int coin_change(int monete[], int n, int amount) {
int dp[amount+1];
for(int i=0;i<=amount;i++) dp[i]=amount+1; // "Infinito"
dp[0]=0;
for(int a=1;a<=amount;a++) {
for(int i=0;i<n;i++) {
if(monete[i]<=a && dp[a-monete[i]]+1 < dp[a])
dp[a] = dp[a-monete[i]]+1;
}
}
return dp[amount]>amount ? -1 : dp[amount];
}
int main() {
printf("Fib(30) = %lld\n", fib_dp(30));
int pesi[] = {2, 3, 4, 5};
int valori[] = {3, 4, 5, 6};
printf("Zaino (cap=8): valore max = %d\n", zaino(pesi,valori,4,8));
printf("LCS di 'ABCBDAB' e 'BDCAB': ");
lcs("ABCBDAB", "BDCAB");
int monete[] = {1, 5, 10, 25};
printf("Monete per 36 centesimi: %d\n", coin_change(monete,4,36));
return 0;
}
#include <stdio.h>
#include <string.h>
// Quante operazioni (insert, delete, replace) servono per trasformare s1 in s2?
int edit_distance(const char *s1, const char *s2) {
int m=strlen(s1), n=strlen(s2);
int dp[m+1][n+1];
for(int i=0;i<=m;i++) dp[i][0]=i; // Elimina tutti i char di s1
for(int j=0;j<=n;j++) dp[0][j]=j; // Inserisci tutti i char di s2
for(int i=1;i<=m;i++) {
for(int j=1;j<=n;j++) {
if(s1[i-1]==s2[j-1]) dp[i][j]=dp[i-1][j-1];
else {
int ins=dp[i][j-1]+1, del=dp[i-1][j]+1, rep=dp[i-1][j-1]+1;
dp[i][j]=ins<del?(ins<rep?ins:rep):(del<rep?del:rep);
}
}
}
return dp[m][n];
}
int main() {
printf("Edit dist('kitten','sitting'): %d\n", edit_distance("kitten","sitting")); // 3
printf("Edit dist('sunday','saturday'): %d\n", edit_distance("sunday","saturday")); // 3
printf("Edit dist('abc','abc'): %d\n", edit_distance("abc","abc")); // 0
return 0;
}
Genera tutte le permutazioni di un array [1,2,3,...,N]. Usa il backtracking: ad ogni passo scegli un elemento non ancora usato. Stampa ogni permutazione e conta il totale (dovrebbe essere N!).
#include <stdio.h>
#include <string.h>
int count = 0;
void permutazioni(int arr[], int usato[], int corrente[], int n, int idx) {
if (idx == n) {
for(int i=0;i<n;i++) printf("%d ",corrente[i]);
printf("\n");
count++;
return;
}
for (int i=0; i<n; i++) {
if (!usato[i]) {
usato[i]=1; corrente[idx]=arr[i];
permutazioni(arr, usato, corrente, n, idx+1);
usato[i]=0; // Backtrack
}
}
}
int main() {
int n=3, arr[]={1,2,3};
int usato[3]={0}, corrente[3];
permutazioni(arr, usato, corrente, n, 0);
printf("Totale: %d (dovrebbe essere %d! = 6)\n", count, n);
return 0;
}Dato un labirinto come matrice NxM (0=libero, 1=muro), trova e stampa il percorso dall'angolo in alto-sinistra (0,0) all'angolo in basso-destra (N-1,M-1). Usa backtracking: prova su, giù, sinistra, destra; se arrivi in un vicolo cieco torna indietro.
#include <stdio.h>
#define N 5
#define M 6
int labirinto[N][M] = {
{0,0,1,0,0,0},
{1,0,1,0,1,0},
{0,0,0,0,1,0},
{0,1,1,1,1,0},
{0,0,0,0,0,0}
};
int soluzione[N][M] = {0};
int risolvi(int r, int c) {
if(r==N-1 && c==M-1) { soluzione[r][c]=1; return 1; }
if(r<0||r>=N||c<0||c>=M||labirinto[r][c]||soluzione[r][c]) return 0;
soluzione[r][c]=1;
if(risolvi(r+1,c)||risolvi(r,c+1)||risolvi(r-1,c)||risolvi(r,c-1)) return 1;
soluzione[r][c]=0; // Backtrack
return 0;
}
int main() {
if(risolvi(0,0)) {
for(int i=0;i<N;i++){
for(int j=0;j<M;j++)
printf("%s ", soluzione[i][j]?"*":(labirinto[i][j]?"#":"."));
printf("\n");
}
} else printf("Nessun percorso!\n");
return 0;
}Trova la lunghezza della più lunga sottosequenza crescente di un array. Es: [10,9,2,5,3,7,101,18] → LIS = [2,3,7,101] → lunghezza 4. Implementa sia la soluzione O(n²) DP che quella O(n log n) con patience sorting.
#include <stdio.h>
// O(n²)
int lis_n2(int arr[], int n) {
int dp[n];
for(int i=0;i<n;i++) dp[i]=1;
for(int i=1;i<n;i++)
for(int j=0;j<i;j++)
if(arr[j]<arr[i] && dp[j]+1>dp[i]) dp[i]=dp[j]+1;
int max=0; for(int i=0;i<n;i++) if(dp[i]>max) max=dp[i];
return max;
}
// O(n log n) — patience sorting
int lower_bound_lis(int arr[], int n, int val) {
int sx=0,dx=n;
while(sx<dx){int m=(sx+dx)/2;if(arr[m]<val)sx=m+1;else dx=m;}
return sx;
}
int lis_nlogn(int arr[], int n) {
int tails[n], len=0;
for(int i=0;i<n;i++) {
int pos=lower_bound_lis(tails,len,arr[i]);
tails[pos]=arr[i];
if(pos==len) len++;
}
return len;
}
int main() {
int arr[]={10,9,2,5,3,7,101,18};
int n=8;
printf("Array: ");
for(int i=0;i<n;i++) printf("%d ",arr[i]);
printf("\nLIS O(n²): %d\n", lis_n2(arr,n));
printf("LIS O(nlogn): %d\n", lis_nlogn(arr,n));
return 0;
}