Scrittura di funzioni ricorsive

5

Ho molti problemi a scrivere funzioni ricorsive legate agli alberi. Non posso usare google per queste funzioni perché non sono generali e non potrò usare google in un'impostazione di esame!

C'è qualche "trucco" per riuscire a creare un algoritmo / scrivere psuedocode con successo per le funzioni ricorsive? C'è un modo in cui dovrei pensare / avvicinarmi a questo?

Esempio : scrivi una funzione ricorsiva che determina se un dato albero binario ha a struttura coerente con un albero AVL valido.

Soluzione prevista :

template <typename Type>
bool is_avl (const Binary_node<Type> * tree) {
    if (tree == NULL) {
        return true;
    }
    return is_bst (tree)
      && is_avl (tree->left())
      && is_avl (tree->right())
      && std::abs(height(tree->left()) - height(tree->right())) <= 1;
}
    
posta rrazd 22.02.2012 - 19:19
fonte

2 risposte

15

Sei fortunato! Lì (ordinamento) è!

Quello che spesso si vuole fare è identificare i casi 2/3:

  1. Il caso base
  2. Il caso ricorsivo
  3. Il caso di uscita (a volte facoltativo)

Cioè:

  1. Che cosa vuoi fare
  2. Dove è necessario continuare
  3. Al termine

Pensa ad un esempio (DFS su un albero di ricerca binario):

bool DFS(Node currentNode, T searchValue)
{
    // base case
    if (currentNode.Value == searchValue) 
        return true;

    // recursive case and exit case
    if (curentNode.Left != null && DFS(currentNode.Left, searchValue)) 
        return true;

    // recursive case and exit case
    if (curentNode.Right != null && DFS(currentNode.Right, searchValue))
        return true;

    return false;
}

Quindi, ecco:

  1. Caso di base: se abbiamo trovato il nostro valore
  2. Casi ricorsivi: eseguire DFS nei nodi figlio
  3. Caso di uscita: restituisce true se DFS sui nodi figlio ha trovato il valore

Quindi ora pensa al traversal in ordine dello stesso albero:

  1. Caso di base: stampa il nodo
  2. Caso ricorsivo:
    • visita il figlio sinistro
    • visita il figlio giusto
  3. Exit case (s): esiste il nodo?

Nel caso di attraversamenti interni sembra che:

void InOrder (Node currentNode)
{
    // 3
    if (currentNode == null)
        return;

    // 2
    InOrder(currentNode.Left);
    // 1
    print(currentNode.Value);
    // 2
    InOrder(currentNode.Right);
}

Quasi tutte le funzioni ricorsive avranno questi elementi. Identificarli e metterli nel giusto ordine è la chiave.

    
risposta data 22.02.2012 - 20:02
fonte
1

C'è qualche "trucco" per riuscire a creare un algoritmo / scrivere psuedocode per funzioni ricorsive?

Assolutamente! Quando scrivi una funzione ricorsiva, stai descrivendo esplicitamente l'induction che stai preformando sul dato datastructure. Pertanto, quando scrivi la tua funzione, il "trucco" è duplice:

  • Copri tutte le diverse forme rappresentate dalla tua infrastruttura (IE, hai casi per i leaf AND di un albero, o le celle AND empty-lists in un elenco collegato, o numeri positivi AND zero se stai ricorrendo ai numeri.
  • Quando hai a che fare con il caso contenitore (cella in un elenco, nodo in un albero), riduci il problema in sottoproblemi e trova un modo per combinarli, se necessario.

Ad esempio, ecco una funzione ricorsiva per contare tutti i nodi in un albero:

def TreeCount(Tree):
    if Tree.isLeaf: # we can't go down any further
        return 1 
    else: # break the problem into sub-problems we can solve with this function
        return 1 + TreeCount(Tree.left) + TreeCount(Tree.right) 

Come puoi vedere, ho diviso la funzione sul tipo di albero che stavamo guardando (Leaf vs Node) e nel caso in cui avessi a che fare con un nodo, l'ho elaborato in termini di ricorsioni sui suoi sottoalberi.

    
risposta data 22.02.2012 - 19:59
fonte

Leggi altre domande sui tag