まず、これは宿題ではないことを誓います。これは、インタビューで尋ねられた質問です。私はそれをめちゃくちゃにしたと思います(解決策には再帰が必要であることはわかっていましたが)。ここに質問があります:
ツリー内のノードの数を返すcount()メソッドを実装します。ノードに左または右の子がない場合、関連するgetXXChild()
メソッドはnull
を返します
class Tree {
Tree getRightChild() {
// Assume this is already implemented
}
Tree getLeftChild() {
// Assume this is already implemented
}
int count() {
// Implement me
}
}
質問をする私の理由は、正しい解決策を見て、それによって私の鉱山がどれほど悪かったかを測定することに興味があるだけです。
乾杯、トニー
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;
}
ささいな再帰的な解決策:
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;
}
より効率的なソリューションとよりエレガントなソリューションのどちらが必要かは、当然、ツリーのサイズと、このルーチンを使用する頻度に依存します。ホアレが言ったことをレンベマーは:「時期尚早の最適化はすべての悪の根源です。」
それが読むので、私はこれがより好きです:
左の戻りカウント+リグのカウント+ 1
int count() {
return countFor( getLeftChild() ) + countFor( getRightChild() ) + 1;
}
private int countFor( Tree tree ) {
return tree == null ? 0 : tree.count();
}
少し文芸的なプログラミングに向けて。
ところで、私はJavaで一般的に使用されているゲッター/セッター規約が好きではないので、代わりにleftChild()を使用する方が良いと思います。
return countFor( leftChild() ) + countFor( rightChild() ) + 1;
Hoshua Blochがここで説明するのと同じように http://www.youtube.com/watch?v=aAb7hSCtvGw at min。 32:03
正確に取得できた場合、コードは次のようになります...
しかし、私はget/set規約がほとんど言語の一部となったことを認めなければなりません。 :)
他の多くの部分では、この戦略に従うと自己文書化コードが作成されます。これは良いことです。
トニー:インタビューであなたの答えは何だったのでしょうか。
このようなものはうまくいくはずです:
int count()
{
int left = getLeftChild() == null ? 0 : getLeftChild().count();
int right = getRightChild() == null ? 0 : getRightCHild().count();
return left + right + 1;
}
return (getRightChild() == null ? 0 : getRightChild.count()) + (getLeftChild() == null ? 0 : getLeftChild.count()) + 1;
またはそのようなもの。
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();
}
}
メソッドを実装します。
public static int countOneChild(Node root)
{
...
}
これは、1つの子を持つバイナリツリーの内部ノードの数をカウントします。関数をtree.Java
プログラムに追加します。
多くの ways をたどることでツリーを数えることができます。単にトラバーサルを事前注文すると、コードは(定義した関数に基づいて)次のようになります。
int count() {
count = 1;
if (this.getLeftChild() != null)
count += this.getLeftChild().count();
if (this.getRightChild() != null)
count += this.getRightChild().count();
return count;
}
プレオーダー再帰でやった。 localRootを使用することによるインタビュー形式には厳密には従いませんが、アイデアは理解できたと思います。
private int countNodes(Node<E> localRoot, int count) {
if (localRoot == null)
return count;
count++; // Visit root
count = countNodes(localRoot.left, count); // Preorder-traverse (left)
count = countNodes(localRoot.right, count); // Preorder-traverse (right)
return count;
}
public int countNodes() {
return countNodes(root, 0);
}
これは標準の再帰問題です:
count():
cnt = 1 // this node
if (haveRight) cnt += right.count
if (haveLeft) cnt += left.count
return cnt;
非常に非効率的で、ツリーが非常に深い場合はキラーですが、それは再帰です...
もちろん、カウントするときにツリー内のすべてのノードにアクセスすることを避けたい場合、処理時間はメモリよりも価値がある場合は、ツリーを構築するときにカウントを作成することで不正行為を行うことができます。
各ノードにintカウントがあり、1に初期化されます。これは、そのノードをルートとするサブツリーのノード数を表します。
ノードを挿入するときは、再帰的挿入ルーチンから戻る前に、現在のノードでカウントを増やします。
つまり.
public void insert(Node root, Node newNode) {
if (newNode.compareTo(root) > 1) {
if (root.right != null)
insert(root.right, newNode);
else
root.right = newNode;
} else {
if (root.left != null)
insert(root.left, newNode);
else
root.left = newNode;
}
root.count++;
}
次に、任意のポイントからカウントを取得するには、node.countをルックアップするだけです。
int count()
{
int retval = 1;
if(null != getRightChild()) retval+=getRightChild().count();
if(null != getLeftChild()) retval+=getLeftChild().count();
return retval;
}
神様私が間違えなかったことを望みます。
編集:私は実際にやった。
二分木に関連する質問はインタビューで期待されるべきです。私は次のインタビューの前に時間をかけて、 this リンクをたどるといいでしょう。解決される問題は約14あります。ソリューションの概要と方法を確認できます。これにより、将来のバイナリツリーの問題に対処する方法がわかります。
あなたの質問はカウント方法に固有のものであることを知っています。それは私が提供したリンクにも実装されています
最初の試みで追加するものは何もありませんでしたが、その後、再帰の深さと、最新の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を使用することです。
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
}
}