Algoritmo per verificare se un albero binario è un albero di ricerca e contare i rami completi


10

Ho bisogno di creare un algoritmo ricorsivo per vedere se un albero binario è un albero di ricerca binario e contare quanti rami completi ci sono (un nodo genitore con entrambi i nodi figlio sinistro e destro) con una presunta variabile di conteggio globale. Questo è un compito per la mia classe di strutture di dati.

Finora l'ho fatto

void BST(tree T) {
   if (T == null) return
   if ( T.left and T.right) {
      if (T.left.data < T.data or T.right.data > T.data) {
        count = count + 1
        BST(T.left)
        BST(T.right)
      }
   }
}

Ma non riesco davvero a capirlo. So che questo algoritmo non risolverà il problema perché il conteggio sarà zero se la seconda istruzione if non è vera.

Qualcuno potrebbe darmi una mano su questo?


Come viene <definito l' operatore di confronto sui nodi?
Joe

Vuoi calcolare il conteggio anche se non è un albero di ricerca binario?
Joe

1
Il tuo algoritmo dovrebbe restituire qualcosa, come trueo false?
Joe

2
Forse dovresti provare a definire prima due funzioni separate: una per verificare se si tratta di un BST e una per contare i rami completi. Questo dovrebbe essere più gestibile.
sepp2k,

1
@OghmaOsiris Immagino che l'abbia detto perché la domanda è sostanzialmente "Ecco il mio codice, come faccio a farlo funzionare?". Se il codice non fosse della varietà pseudo (ish), sarebbe sicuramente una domanda SO.
sepp2k,

Risposte:


10

Come altri hanno già indicato nei commenti, hai davvero due funzioni non correlate qui: verificare se l'albero è un albero di ricerca e contare i rami completi. A meno che il compito non lo richieda specificamente, scriverei due funzioni separate.

Vediamo conteggio prima contando i rami completi. Ciò significa contare i nodi che hanno sia un figlio sinistro che un figlio destro. Quindi è necessario incrementare il contatore ( count = count + 1) quando entrambi T.lefte T.rightsono non nulli (no T.left.datae T.right.data: i dati non contano per questa attività).

if (T.left and T.right) {
    count = count + 1

Inoltre, devi esplorare la sottostruttura sinistra anche se la sottostruttura destra è vuota e devi esplorare la sottostruttura destra anche se la sottostruttura sinistra è vuota. Quindi guarda dove metti le chiamate ricorsive.

Per verificare se l'albero è un albero di ricerca, è necessario ispezionare i valori dei dati. Hai già qualcosa di simile al giusto confronto; non del tutto giusto. Scrivi alcuni alberi di esempio con varie forme (non molto grandi, da 2 a 5 nodi) ed esegui il tuo algoritmo su di essi per vedere cosa succede.

Hai ancora bisogno di trovare un posto dove inserire il risultato del controllo di validità. Ancora una volta, guarda dove metti le chiamate ricorsive (se fai solo questa parte, ci sono diverse soluzioni, ma a questo punto non preoccuparti se ne vedi solo una).

Infine, una volta che sei riuscito a scrivere entrambe le funzioni separatamente e le hai testate su alcuni esempi, mettile insieme attentamente (se richiesto dall'assegnazione).


grazie, ho riletto la domanda e si supponeva che fossero metodi separati.
OghmaOsiris,

7

In cose come questa, è spesso più facile pensare all'indietro, quindi prima considera ciò di cui hai bisogno. Dalla tua descrizione, elenchiamoli:

  • ricorsione
  • Validità
  • Conteggio dei nodi completi

OK, questo è un elenco abbastanza breve, questo dovrebbe essere gestibile. Cominciamo con un metodo vuoto e aggiungerò una descrizione di ciò che dovrebbe accadere.

valid_bst () {
}

Ora validità. Come si verifica la validità? Nella chat hai detto che un albero è valido "se ... tutti i bambini a sinistra sono inferiori al genitore e i bambini a destra sono maggiori del genitore". Sono sicuro che intendevi consentire anche l'uguaglianza. Quello sarebbe t.left.value <= t.value <= t.right.value.

valid_bst () {
    This node is valid if t.left.value <= t.value <= t.right.value
}

E se mancasse uno dei bambini? Da quello che hai detto, credo che tu sappia che il nodo è ancora valido se uno (o entrambi mancano). Aggiungiamo questo, ristrutturando leggermente:

valid_bst () {
    This node is valid to the left if 
        there is no left child or 
        it is no greater than the current node.
    This node is valid to the right if 
        there is no right child or 
        it is no less than the current node.
    This node is valid overall if it is valid to the left and right.
}

OK, ora sappiamo se questo nodo è valido. Come controlliamo se l'intero albero è valido? Non è in un array, quindi probabilmente non possiamo / non vogliamo scorrere su di esso in modo lineare. Il tuo compito dà la risposta: ricorsione. Ma come accumuliamo una risposta usando la ricorsione? Abbiamo accesso a tre informazioni, se questo nodo è valido e il risultato di chiamate che chiedono se i nodi sinistro e destro sono validi. Ovviamente l'albero è valido solo se tutti e tre sono veri.

valid_bst () {
    This node is valid to the left if 
        there is no left child or 
        it is no greater than the current node.
    This node is valid to the right if 
        there is no right child or 
        it is no less than the current node.
    This node is valid overall if it is valid to the left and right.
    Is the left child valid?
    Is the right child valid?
    This tree is only valid if this node and both its children are.
}

Se stai prestando attenzione, questo ci dice anche ciò che la nostra funzione deve tornare.

Ora, come possiamo integrare il conteggio? Dici ciò che conta ("un nodo genitore con entrambi i nodi figlio destro e sinistro") e non dovrebbe essere difficile da tradurre in codice reale. Controllare se tale condizione è soddisfatta e incrementare il contatore in modo appropriato. Ricorda solo che questo deve essere da qualche parte dove sarà raggiunto ogni volta che è vero.

E ovviamente ho tralasciato alcuni dettagli come la condizione di arresto della ricorsione e la verifica di null.


6

I miei tre commenti sopra sono tre suggerimenti per problemi con il tuo codice.

  1. A meno che non sia già stato definito in modo specifico come un operatore di confronto dovrebbe gestire il tipo di dati del nodo, molto probabilmente il confronto diretto tra due nodi non farà ciò che si desidera. Ciò che probabilmente intendevi era confrontare i campi memorizzati nei nodi, ad esempionode1.value < node2.value
  2. in questo momento, stai aggiungendo al conteggio solo se il terzo ifè vero, sei sicuro che sia quello che volevi fare? A proposito, potresti voler ricontrollare che if statement faccia quello che vuoi.
  3. Presumo che tu voglia restituire vero se l'albero è un BST valido e falso altrimenti. Ciò significa che devi sempre restituire vero o falso in un caso base e dovresti restituire anche i risultati delle tue chiamate ricorsive.

Per quanto riguarda il punto uno: questo è pseudo codice, giusto? Quindi finché l'intenzione è trasmessa al lettore non c'è motivo di definire cose del genere.
sepp2k,

@ sepp2k è vero, e il mio commento è probabilmente un po 'troppo pignolo per lo pseudo-codice. Immagino che il mio punto sia che dobbiamo capire cosa significa confrontare due nodi. Il tuo punto è che dovremmo già capirlo implicitamente.
Joe

Giusto, esattamente.
sepp2k,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.