# Alberi - parte 2 ## Somma Nodi Contenuta in un Altro Albero Dati due alberi binari, scrivere una funzione che restituisce 1 se la somma di tutti i nodi foglia di uno è uguale al valore di uno dei nodi dell’altro, o viceversa. ```c= // Funzione per calcolare la somma dei nodi foglia di un albero int sommaFoglie(Tree root) { // Caso base: albero vuoto if (root == NULL) return 0; // Caso foglia: aggiungo il valore alla somma if (root->left == NULL && root->right == NULL) return root->data; // Sommo ricorsivamente le foglie dei sottoalberi return sommaFoglie(root->left) + sommaFoglie(root->right); } // Funzione per verificare se un valore è presente come nodo in un albero int trovaNodo(Tree root, int value) { // Caso base: albero vuoto if (root == NULL) return 0; // Se trovo il valore, restituisco 1 if (root->data == value) return 1; // Cerco ricorsivamente nei sottoalberi return trovaNodo(root->left, value) || trovaNodo(root->right, value); } // Funzione principale per verificare la condizione int verificaSommaUgualeNodo(Tree root1, Tree root2) { // Calcolo la somma dei nodi foglia del primo albero int somma_foglie1 = sommaFoglie(root1); // Controllo se la somma dei nodi foglia del primo albero // corrisponde a uno dei nodi del secondo albero if (trovaNodo(root2, somma_foglie1)) { return 1; } // Calcolo la somma dei nodi foglia del secondo albero int somma_foglie2 = sommaFoglie(root2); // Controllo se la somma dei nodi foglia del secondo albero // corrisponde a uno dei nodi del primo albero if (trovaNodo(root1, somma_foglie2)) { return 1; } return 0; } ``` --- ## Numero di Elementi Uguali Si scriva una funzione che, dato un albero e un intero k, restituisce 1 se l’albero ha esattamente k elementi uguali tra loro. Si implementino due versioni, una senza utilizzare strutture ausiliarie e un'altra che utlizzi delle strutture ausiliarie. ### Soluzione SENZA Strutture Ausiliarie ```c= // Funzioni int contaOccorrenze(Tree root, int value) { // Caso base: albero vuoto if (root == NULL) return 0; // Conta il nodo corrente se il valore coincide int count = 0; if(root->data == value) count = 1; // Somma le occorrenze nei sottoalberi return count + contaOccorrenze(root->left, value) + contaOccorrenze(root->right, value); } int verificaKElementi(Tree current_root, int k, Tree original_root) { // Caso base: albero vuoto if (current_root == NULL) return 0; // Conta quante volte il valore del nodo corrente appare nell'albero int occorrenze = contaOccorrenze(original_root, current_root->data); // Se il valore appare esattamente k volte, restituisco 1 if (occorrenze == k) return 1; // Altrimenti, continuo a verificare nei sottoalberi // Nota: ignoro i nodi già verificati int res_sx = verificaKElementi(current_root->left, k, original_root); int res_dx = verificaKElementi(current_root->right, k, original_root); return res_sx || res_dx; } ``` ==:warning: La soluzione presentata a lezione, come fatto notare da due vostri colleghi, non analizzava un caso chiave, ovvero quando i nodi sono frammentati in due sottoalberi diversi. La soluzione presentata riportata qui, invece, risolve il problema contando le occorrenze a partire sempre dalla radice originale. Per fare ciò, bisogna passare tra una chiamata e l'altra la radice originale.== ### Soluzione con Struttura di Appoggio Uso una lista compressa ausiliaria: ```c= // Nodo per la lista di conteggio typedef struct CountNode { int value; int count; struct CountNode *next; } CountNode; typedef CountNode *pCountNode; ``` Con le seguenti funzioni di gestione: ```c= pCountNode aggiornaLista(pCountNode head, int value) { pCountNode curr = head; // Cerca il valore nella lista while (curr != NULL) { if (curr->value == value) { curr->count++; return head; // Aggiornato } curr = curr->next; } // Valore non trovato, aggiungi nuovo nodo pCountNode new_node = (pCountNode)malloc(sizeof(CountNode)); if (new_node == NULL) { printf("Errore di allocazione.\n"); return head; } new_node->value = value; new_node->count = 1; new_node->next = head; return new_node; } void liberaLista(pCountNode head) { pCountNode curr; while (head != NULL) { curr = head; head = head->next; free(curr); } } int verificaKOccorrenze(pCountNode head, int k) { pCountNode curr = head; while (curr != NULL) { if (curr->count == k) { return 1; // Trovato } curr = curr->next; } return 0; // Non trovato } ``` La soluzione salva i nodi dell’albero all’interno della lista compressa che viene popolata con la ricorsione, la lista quindi avrà in ogni nodo un valore associato a quante volte appare nell'albero. ```c= pCountNode popolaLista(Tree root, pCountNode head) { // Albero vuoto if (root == NULL) return head; // Aggiorna con il nodo corrente head = aggiornaLista(head, root->data); // Scorro i sottoalberi head = popolaLista(root->left, head); head = popolaLista(root->right, head); return head; } int verificaKElementi(Tree root, int k){ // Popolo la lista pCountNode head = NULL; head = popolaLista(root, head); // Verifico che ci siano k elementi uguali int res = verificaKOccorrenze(head, k); // Svuoto la lista liberaLista(head); return res; } ``` --- ## Albero Isobato Si scriva una funzione che prende in input un albero e restituisce 1 se l’albero è isobato, 0 altrimenti. Un albero si dice isobato se tutti i cammini dalla radice alle foglie hanno la stessa lunghezza. ### Soluzione 1 ```c= // Funzione per calcolare il cammino di lunghezza massima int maxDepth(Tree root){ if(root == NULL) return 0; int depth_sx, depth_dx; depth_sx = maxDepth(root->left); depth_dx = maxDepth(root->right); if(depth_sx > depth_dx) return depth_sx + 1; else return depth_dx + 1; } // Funzione per calcolare il cammino di lunghezza minima int minDepth(Tree root){ if(root == NULL) return 0; int depth_sx, depth_dx; depth_sx = minDepth(root->left); depth_dx = minDepth(root->right); if(depth_sx == 0) return depth_dx + 1; if(depth_dx == 0) return depth_sx + 1; if(depth_sx < depth_dx) return depth_sx + 1; else return depth_dx + 1; } int alberoIsobato(Tree root){ return maxDepth(root) == minDepth(root); } ``` ==**Nota:** le righe 20 e 21 servono a gestire correttamente il caso in cui un nodo ha un solo figlio, altrimenti si restituisce al chiamante la `min_depth` sbagliata. Ad esempio, nel caso cerchiato nella figura sottostante, si restituisce correttamente al chiamante 2, altrimenti si restituirebbe 1.== ![Albero](https://hackmd.io/_uploads/SyW-ptx4yl.png) ### Soluzione 2 ```c= // Funzione ausiliaria per calcolare la profondità e verificare l'isobatia int verificaProfondita(Tree root, int *isIsobato) { // Caso base: albero vuoto if (root == NULL) return 0; // Calcolo ricorsivo della profondità dei sottoalberi int profondita_sx = verificaProfondita(root->left, isIsobato); int profondita_dx = verificaProfondita(root->right, isIsobato); // Verifica se i sottoalberi hanno la stessa profondità if (profondita_sx != profondita_dx) { *isIsobato = 0; // Non è isobato } // Restituisce la profondità attuale del sottoalbero int profondita_corrente; if (profondita_sx > profondita_dx) { profondita_corrente = profondita_sx; } else { profondita_corrente = profondita_dx; } return profondita_corrente + 1; } int alberoIsobato(Tree root) { // Variabile per tracciare se l'albero è isobato int isIsobato = 1; verificaProfondita(root, &isIsobato); return isIsobato; } ``` --- ## Alberi Ternari Isobati Scrivere una funzione che dice se un albero ternario è isobato oppure no. Si consideri la seguente definizione: ```c= typedef struct NodeTree { int data; struct NodeTree * left; struct NodeTree * right; struct NodeTree * center; } NodeTree; typedef NodeTree * Tree; ``` ```c= // Funzione ausiliaria int verificaIsobato(Tree root, int profondita_corrente, int *profondita_foglia) { // Caso base: nodo vuoto if (root == NULL) { return 1; // Nodo vuoto non influisce sull'isobatia } // Caso base: foglia if (root->left == NULL && root->right == NULL && root->center == NULL) { // Se è la prima foglia incontrata, salvo la profondità if (*profondita_foglia == -1) { *profondita_foglia = profondita_corrente; return 1; // Fin qui l'albero è isobato } // Controllo che la profondità della foglia corrente sia uguale alla profondità salvata return *profondita_foglia == profondita_corrente; } // Caso ricorsivo int is_left_isobato = verificaIsobato(root->left, profondita_corrente + 1, profondita_foglia); int is_right_isobato = verificaIsobato(root->right, profondita_corrente + 1, profondita_foglia); int is_center_isobato = verificaIsobato(root->center, profondita_corrente + 1, profondita_foglia); return is_left_isobato && is_right_isobato && is_center_isobato; } // Funzione principale int alberoIsobato(Tree root) { int profondita_foglia = -1; // Per salvare la profondità della prima foglia incontrata return verificaIsobato(root, 0, &profondita_foglia); } ``` --- ## Alberi Artussiani Si scriva una funzione che prende in input un albero e restituisce 1 se tale albero è artussiano. Un albero si dice artussiano se e solo se è composto da: - nodi foglia - nodi con un solo figlio - nodi con due figli aventi lo stesso numero di discendenti ![Screenshot 2024-12-05 alle 11.16.54](https://hackmd.io/_uploads/rJYUJGyVyl.png) ![Screenshot 2024-12-05 alle 11.17.29](https://hackmd.io/_uploads/Bk6vkGyE1l.png) ### Soluzione 1 ```c= int contaNodi(Tree root){ if(root == NULL) return 0; return contaNodi(root->left) + contaNodi(root->right) + 1; } int alberoArtussiano(Tree root){ // Caso base: albero vuoto if(root == NULL) return 1; // Caso base: foglia if(root->left == NULL && root->right == NULL) return 1; // Analizzo il branch di destra if(root->left == NULL) return alberoArtussiano(root->right); // Oppure analizzo il branch di sinistra if(root->right == NULL) return alberoArtussiano(root->left); // Se ho entrambi i branch disponibili, devo verificare che: // Entrambi i branch hanno gli stessi nodi // Entrambi i sottoalberi sono artussiani if(contaNodi(root->left) == contaNodi(root->right) && alberoArtussiano(root->left) && alberoArtussiano(root->right)) return 1; return 0; } ``` ### Soluzione 2 ```c= // Funzione helper per calcolare numero di nodi e verificare se l'albero è artussiano int alberoArtussianoHelper(Tree root, int *nodi) { // Caso base: albero vuoto if (root == NULL) { *nodi = 0; return 1; } // Variabili per il conteggio dei nodi dei sottoalberi int nodi_sx = 0, nodi_dx = 0; // Verifica se i sottoalberi sono artussiani int sx_artussiano = alberoArtussianoHelper(root->left, &nodi_sx); int dx_artussiano = alberoArtussianoHelper(root->right, &nodi_dx); // Calcola il numero di nodi totali *nodi = nodi_sx + nodi_dx + 1; // Verifica la condizione artussiana return sx_artussiano && dx_artussiano && (nodi_sx == nodi_dx); } // Funzione principale int alberoArtussiano(Tree root) { int nodi = 0; return alberoArtussianoHelper(root, &nodi); } ``` # Ordinamento Si scrivano le funzioni iterative per implementare selection sort e bubble sort per ordinare un array di interi. Si scriva la funzione ricorsiva merge sort per ordinare un array di interi ```clike= // Librerie #include <stdio.h> #define N 10 // Prototipi void stampaArray(int[], int); void selectionSort(int[], int); void bubbleSort(int[], int); void mergeSort(int[], int, int); void merge(int[], int, int, int); void copiaArray(int[], int[], int); // Main int main(){ int v[N] = {2,4,1,6,5,9,5,10,4,2}; int t[N]; printf("Array: "); stampaArray(v, N); printf("Selection Sort: "); copiaArray(v, t, N); selectionSort(t, N); stampaArray(t, N); printf("Bubble Sort: "); copiaArray(v, t, N); bubbleSort(t, N); stampaArray(t, N); printf("Merge Sort: "); copiaArray(v, t, N); mergeSort(t, 0, N-1); stampaArray(t, N); return 0; } // Funzioni void selectionSort(int v[], int dim){ for(int i = 0; i < dim; i++){ for(int j = i + 1; j < dim; j++){ if(v[i] > v[j]){ int tmp = v[j]; v[j] = v[i]; v[i] = tmp; } } } } void bubbleSort(int v[], int dim){ int i, j, n_swap,tmp; for(i = 0; i < dim - 1; i++){ n_swap = 0; for(j = 0; j < dim - i - 1; j++){ if(v[j] > v[j+1]){ tmp = v[j]; v[j] = v[j+1]; v[j+1] = tmp; n_swap++; } } if(n_swap == 0) break; } } void mergeSort(int v[], int start, int end){ int mid; if(start < end){ mid = (start + end)/2; mergeSort(v, start, mid); mergeSort(v, mid + 1, end); merge(v, start, end, mid); } } void merge(int v[], int start, int end, int mid){ int i=start, j=mid+1, k=start, copy[N]; // faccio il merge fino alla fine di una delle due porzioni while((i <= mid) && (j <= end)){ if(v[i] > v[j]){ copy[k] = v[j]; j++; } else{ copy[k] = v[i]; i++; } k++; } // finisco la copia della prima porzione (se necessario) while(i <= mid){ copy[k] = v[i]; k++; i++; } // OPPURE finisco la copia della seconda porzione (se necessario) while(j <= end){ copy[k] = v[j]; k++; j++; } // copio "copy" nell'array di partenza, ma solo nella porzione interessata for(i=start; i <= end; i++){ v[i] = copy[i]; } } void stampaArray(int v[], int dim){ printf("["); for(int i = 0; i<dim; i++) printf("%d,", v[i]); printf("]\n"); } void copiaArray(int src[], int dest[], int dim){ for(int i = 0; i < dim; i++){ dest[i] = src[i]; } } ```