Come evitare un sovraccarico mentre si implementa ancora un modello di oggetto nullo?

2

Sto implementando un albero di ricerca binario in java. All'interno della classe BST ho un nodo di classe protetto (nel caso in cui voglio estendere ad un albero AVL). Il codice è sotto Ho avuto un problema, sia concettualmente che esteticamente, implementando i nodi vuoti come puntatori nulli. Così, invece, ho optato per il modello di oggetto nullo e creato un oggetto EmptyNode, il cui codice è anche sotto. Tuttavia, anche se questo mi dà un codice più grazioso e qualcosa di più concettualmente piacevole (per me), ora ho il problema che quando lavoro con un albero di altezza n, posso averne 2 ^ {n + 1} - 2 ^ {n} istanze di questa classe EmptyNode che fluttuano intorno, che occupa spazio senza una buona ragione.

Come dovrei risolvere questo codice per evitare questo sovraccarico continuando a implementare il modello di oggetto nullo? Mi sento come se il EmptyNode fosse qualcosa di concettualmente simile a un campo costante di Nodo, ma non posso implementarlo. Mi sembra anche che ci debba essere una sola copia di EmptyNode referenziata ovunque, ma non riesco a capire come implementarla. Qualsiasi aiuto o consiglio è apprezzato.

protected class Node<Key,E>{
    protected Key key;
    protected E value;
    protected Node<Key,E> left;
    protected Node<Key,E> right;
    protected int height;
    protected int balanceFactor;
    public Node(){}
    public Node(Key k, E v){
        key = k;
        value = v;
        left = new EmptyNode<Key,E>();
        right = new EmptyNode<Key,E>();
        height = 0;
        balanceFactor = 0;
    }
    public Node(Key k, E v, Node<Key,E> l, Node<Key,E> r){
        key = k;
        value = v;
        left = l;
        right = r;
        height = Math.max(l.height(), r.height())+1;
        balanceFactor = l.height() - r.height();
    }
    public Key getKey(){return key;}
    public E getValue(){return value;}
    public Node<Key,E> getLeft(){return left;}
    public Node<Key,E> getRight(){return right;}
    public int height(){return height;}
    public int balanceFactor(){return balanceFactor;}
    public void setKey(Key k){key = k;}
    public void setValue(E v){value = v;}
    public void setLeft(Node<Key,E> l){
        left = l;
        height = Math.max(l.height(), this.right.height())+1;
        balanceFactor = left.height() - this.right.height();
    }
    public void setRight(Node<Key,E> r){
        right = r;
        height = Math.max(this.left.height(), r.height())+1;
        balanceFactor = this.left.height() - right.height();
    }
    public boolean isEmpty(){return false;}
    public boolean isLeaf(){
        return (left.isEmpty()) && (right.isEmpty());
    }
    public boolean hasLeft(){
        return !left.isEmpty();
    }
    public boolean hasRight(){
        return !right.isEmpty();
    }
}
protected final class EmptyNode<Key,E> extends Node<Key,E>{
    public EmptyNode(){
        key = null;
        value = null;
        left = right = null;
        height = -1;
        balanceFactor = 0;
    }
    @Override
    public void setKey(Key k){}
    @Override
    public void setValue(E v){}
    @Override
    public void setLeft(Node<Key,E> l){}
    @Override
    public void setRight(Node<Key,E> r){}
    @Override
    public boolean isEmpty(){return true;}
    @Override
    public boolean isLeaf(){return false;}
    @Override
    public boolean hasLeft(){return false;}
    @Override
    public boolean hasRight(){return false;}
}
    
posta Robert Wolfe 24.05.2014 - 20:06
fonte

1 risposta

2

Crea una singola istanza di EmptyNode<Object, Object> e gettala su Node<Key, E> quando ti serve un nodo vuoto per una chiave e un tipo di valore specifici. Ciò funzionerà perché un nodo vuoto ha una chiave e un valore di null e il riferimento null può essere convertito in qualsiasi tipo di riferimento. Vedi codice sorgente per Collections.emptyList() per un esempio di questo approccio.

Un esempio di codice completamente funzionante:

public class NodeTest {
    protected static class Node<Key,E>{
        private static final Node<?, ?> EMPTY_NODE = new EmptyNode<>();

        protected Key key;
        protected E value;
        protected Node<Key,E> left;
        protected Node<Key,E> right;
        protected int height;
        protected int balanceFactor;
        public Node() {
            this(null, null);
        }
        public Node(Key k, E v){
            this(k, v, Node.<Key, E>emptyNode(), Node.<Key, E>emptyNode());
        }
        public Node(Key k, E v, Node<Key,E> l, Node<Key,E> r){
            this(k, v, l, r, Math.max(l.height(), r.height())+1, l.height() - r.height());
        }
        private Node(Key k, E v, Node<Key, E> l, Node<Key, E> r, int h, int bf) {
            key = k;
            value = v;
            left = l;
            right = r;
            height = h;
            balanceFactor = bf;
        }
        public Key getKey(){return key;}
        public E getValue(){return value;}
        public Node<Key,E> getLeft(){return left;}
        public Node<Key,E> getRight(){return right;}
        public int height(){return height;}
        public int balanceFactor(){return balanceFactor;}
        public void setKey(Key k){key = k;}
        public void setValue(E v){value = v;}
        public void setLeft(Node<Key,E> l){
            left = l;
            height = Math.max(l.height(), this.right.height())+1;
            balanceFactor = left.height() - this.right.height();
        }
        public void setRight(Node<Key,E> r){
            right = r;
            height = Math.max(this.left.height(), r.height())+1;
            balanceFactor = this.left.height() - right.height();
        }
        public boolean isEmpty(){return false;}
        public boolean isLeaf(){
            return (left.isEmpty()) && (right.isEmpty());
        }
        public boolean hasLeft(){
            return !left.isEmpty();
        }
        public boolean hasRight(){
            return !right.isEmpty();
        }

        /**
         * @return empty node. This method can be useful for subclasses
         */
        @SuppressWarnings("unchecked")
        protected static final <Key, E> Node<Key, E> emptyNode() {
            return (Node<Key, E>) EMPTY_NODE;
        }
    }

    private static final class EmptyNode<Key, E> extends Node<Key, E> {
        private EmptyNode() {
            super(null, null, null, null, -1, 0);
        }
        @Override
        public void setKey(Key k){throw new UnsupportedOperationException();}
        @Override
        public void setValue(E v){throw new UnsupportedOperationException();}
        @Override
        public void setLeft(Node<Key,E> l){throw new UnsupportedOperationException();}
        @Override
        public void setRight(Node<Key,E> r){throw new UnsupportedOperationException();}
        @Override
        public boolean isEmpty(){return true;}
        @Override
        public boolean isLeaf(){return false;}
        @Override
        public boolean hasLeft(){return false;}
        @Override
        public boolean hasRight(){return false;}
    }

    public static void main(String[] args) {
        Node<String, Integer> node = new Node<>("hello", 5);
        System.out.println(node.isEmpty()); // prints false
        System.out.println(node.isLeaf());  // prints true
        System.out.println(node.getLeft().isEmpty()); // prints true
        System.out.println(node.getRight().isEmpty()); // prints true
    }
}

La mia precedente soluzione era cattiva. La nuova soluzione è migliore: più breve e riutilizzo di più del tuo codice. Le mie modifiche del codice sono state le seguenti.

  • Ha instradato tutte le chiamate del costruttore al costruttore privato che inizializza tutti i campi ( key , value , left , right , balanceFactor ). In questo modo, se desideri espandere i campi trattenuti da Node , devi modificare le istruzioni di assegnazione in un solo costruttore, riducendo il potenziale di errore.
  • Implementato il costruttore pubblico senza argomenti per un Node (il tuo non ha inizializzato i sottoalberi sinistro e destro per essere vuoto, erano null ). Ora crea effettivamente un nodo modificabile con la chiave e il valore iniziale null .
  • Esteso Node per ottenere EmptyNode . Il costruttore EmptyNode chiama solo super per creare un nodo con null chiave, valore, sottoalberi sinistro e destro, height di -1 e balanceFactor di 0 .
  • Aggiunto il metodo emptyNode() , che restituisce un'istanza EmptyNode correttamente castata (vedi la descrizione di questa tecnica all'inizio della mia risposta). Questo metodo può essere utilizzato da sottoclassi di Node per ottenere l'istanza di EmptyNode condivisa anziché crearne una propria.
  • Crea setKey() , setValue() , setLeft() e setRight() di EmptyNode gira UnsupportedOperationException , segnalando che il nodo vuoto è immutabile.
  • Crea Node e EmptyNode classi statiche. Credo che la maggior parte delle classi interne dovrebbero essere static a meno che tu assolutamente non debba usare i loro campi e metodi della loro classe contenitiva.
risposta data 24.05.2014 - 20:28
fonte

Leggi altre domande sui tag