21

まず第一に、これは宿題ではないことを誓います、それは私がインタビューで尋ねられた質問です。私はそれを台無しにしたと思います(解決策には再帰が必要であることに気づきましたが)。ここに質問があります:

ツリー内のノードの数を返すcount()メソッドを実装します。ノードに左または右の子がない場合、関連するgetXXChild()メソッドはnull

class Tree {

  Tree getRightChild() {
    // Assume this is already implemented
  }

  Tree getLeftChild() {
    // Assume this is already implemented
  }

  int count() {
    // Implement me
  }
}

質問をする私の理由は、正しい解決策を見て、それによって私のものがどれほど悪かったかを測定することに興味があります。

乾杯、トニー

4

15 に答える 15

32
int count() {
  Tree right = getRightChild();
  Tree left = getLeftChild();
  int c = 1;                                      // count yourself!
  if ( right != null ) c += right.count();        // count sub trees
  if ( left != null ) c += left.count();          // ..
  return c;
}
于 2009-02-13T20:55:28.063 に答える
19

些細な再帰的な解決策:

int count() {
   Tree l = getLeftTree();
   Tree r = getRightTree();
   return 1 + (l != null ? l.count() : 0) + (r != null ? r.count() : 0);
}

それほど自明ではない非再帰的なもの:

int count() {
    Stack<Tree> s = new Stack<Tree>();
    s.push(this);
    int cnt = 0;
    while (!s.empty()) {
        Tree t = s.pop();
        cnt++;
        Tree ch = getLeftTree();
        if (ch != null) s.push(ch); 
        ch = getRightTree();
        if (ch != null) s.push(ch); 
    }
    return cnt;
}

後者は、再帰をスタックと反復に置き換えるため、おそらくメモリ効率がわずかに高くなります。また、おそらくより高速ですが、測定なしで判断するのは困難です。主な違いは、再帰的なソリューションではスタックを使用するのに対し、非再帰的なソリューションではヒープを使用してノードを格納することです。

編集:これは、スタックの使用量が少ない反復ソリューションのバリアントです。

int count() {
    Tree t = this;
    Stack<Tree> s = new Stack<Tree>();
    int cnt = 0;
    do {
        cnt++;
        Tree l = t.getLeftTree();
        Tree r = t.getRightTree();
        if (l != null) {
            t = l;
            if (r != null) s.push(r);
        } else if (r != null) {
            t = r;
        } else {
            t = s.empty() ? null : s.pop();
        }
    } while (t != null);
    return cnt;
}

より効率的なソリューションが必要か、より洗練されたソリューションが必要かは、当然、ツリーのサイズと、このルーチンを使用する頻度によって異なります。Hoare が言ったことを思い出してください。「時期尚早の最適化は諸悪の根源です。」

于 2009-02-13T20:54:46.833 に答える
11

次のように書かれているので、私はこれが好きです。

左の戻りカウント + 右のカウント + 1

  int count() {
      return  countFor( getLeftChild() ) + countFor( getRightChild() ) + 1;
  }
  private int countFor( Tree tree )  { 
       return tree == null ? 0 : tree.count();
  }

文芸的プログラミングに向けてもう少し。

ところで、Java で一般的に使用されている getter/setter 規則は好きではありません。代わりにleftChild()を使用する方がよいと思います。

  return countFor( leftChild() ) + countFor( rightChild() ) + 1;

Hoshua Bloch がここで説明しているようにhttp://www.youtube.com/watch?v=aAb7hSCtvGw at min. 32:03

あなたがそれを正しく理解すれば、あなたのコードは...

しかし、get/set 規則が言語のほとんどの一部になっていることは認めざるを得ません。:)

他の多くの部分では、この戦略に従うと、自己文書化コードが作成されます。これは良いことです。

Tony: インタビューで何と答えたのかしら。

于 2009-02-13T21:16:19.433 に答える
4
return (getRightChild() == null ? 0 : getRightChild.count()) + (getLeftChild() == null ? 0 : getLeftChild.count()) + 1;

またはそのようなもの。

于 2009-02-13T20:55:56.173 に答える
4

このようなものが動作するはずです:

int count()
{
    int left = getLeftChild() == null ? 0 : getLeftChild().count();
    int right = getRightChild() == null ? 0 : getRightCHild().count();

    return left + right + 1;
}
于 2009-02-13T20:55:04.100 に答える
4
class Tree {

  Tree getRightChild() {
    // Assume this is already implemented
  }

  Tree getLeftChild() {
    // Assume this is already implemented
  }

  int count() {
   return 1 
      + getRightChild() == null? 0 : getRightChild().count()
      + getLeftChild() == null? 0 : getLeftChild().count();
  }
}
于 2009-02-14T00:44:12.227 に答える
2

さまざまな方法でトラバースすることで、ツリーを数えることができます。トラバーサルを事前注文するだけで、コードは(定義した関数に基づいて)次のようになります。

int count() {
    count = 1;
    if (this.getLeftChild() != null)
        count += this.getLeftChild().count();
    if (this.getRightChild() != null)
        count += this.getRightChild().count();
    return count;
}
于 2009-02-13T20:57:29.463 に答える
2

メソッドを実装します。

public static int countOneChild(Node root)
{
    ...
}

これは、1 つの子を持つ二分木の内部ノードの数を数えます。tree.java関数をプログラムに追加します。

于 2011-12-09T08:31:24.690 に答える
0
class Tree {

  Tree getRightChild() {
    // Assume this is already implemented
  }

Tree getLeftChild() {
    // Assume this is already implemented
 }

 int count() {
    if(this.getLeftChild() !=null && this.getRightChild()!=null) 
        return 1 + this.getLeftChild().count() + this.getRightChild().count();
    elseif(this.getLeftChild() !=null && this.getRightChild()==null)
        return 1 + this.getLeftChild().count();
    elseif(this.getLeftChild() ==null && this.getRightChild()!=null)
        return 1 + this.getRightChild().count();
    else return 1;//left & right sub trees are null ==> count the root node
  }
}
于 2016-04-06T05:34:53.050 に答える
0
int count()

{
   int retval = 1;
    if(null != getRightChild()) retval+=getRightChild().count();
    if(null != getLeftChild()) retval+=getLeftChild().count();
    return retval;

}

神よ、私が間違いを犯さなかったことを願っています。

編集:私は実際にしました。

于 2009-02-13T20:58:59.927 に答える
0

これは標準的な再帰問題です:

count():
    cnt = 1 // this node
    if (haveRight) cnt += right.count
    if (haveLeft)  cnt += left.count
return cnt;

非常に非効率的で、ツリーが非常に深い場合はキラーですが、それはあなたにとって再帰です...

于 2009-02-13T20:55:12.993 に答える
0

面接では二分木に関する質問が予想されます。次のインタビューの前に時間をかけて、このリンクにアクセスしてください. 約 14 の問題が解決されています。どのように解決されたかを確認できます。これにより、将来的に二分木の問題に取り組む方法がわかります。

あなたの質問はcountメソッドに固有のものであることは知っています。これは、私が提供したリンクにも実装されています

于 2011-12-09T12:17:42.473 に答える
0

私の最初の試みは、追加する新しいものは何もありませんでしたが、その後、再帰の深さと、最新の Java コンパイラの末尾呼び出しの最適化機能を利用するためにコードを再配置できるかどうかについて考え始めました。主な問題は null テストでした。これは NullObject を使用して解決できます。TCO が両方の再帰呼び出しを処理できるかどうかはわかりませんが、少なくとも最後の呼び出しを最適化する必要があります。

static class NullNode extends Tree {

    private static final Tree s_instance = new NullNode();

    static Tree instance() {
        return s_instance;
    }

    @Override
    Tree getRightChild() {  
        return null;
    }  

    @Override
    Tree getLeftChild() {  
        return null;
    }  

    int count() {  
        return 0;
    }
}

int count() {      
    Tree right = getRightChild();      
    Tree left  = getLeftChild();      

    if ( right == null ) { right = NullNode.instance(); }
    if ( left  == null ) { left  = NullNode.instance(); }

    return 1 + right.count() + left.count();
}   

NullNode の正確な実装は、Tree で使用される実装に依存します。Tree が null の代わりに NullNode を使用する場合、子アクセス メソッドは null を返す代わりに NullPointerException をスローする必要があります。とにかく、主なアイデアは、TCO から利益を得ようとするために NullObject を使用することです。

于 2010-08-05T19:13:53.030 に答える