質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

4回答

3995閲覧

Javaの二分木探索の挿入がうまくいかない

ryo1729_k

総合スコア7

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2017/09/29 06:42

編集2017/09/29 08:59

###前提・実現したいこと
Java言語の初心者です.

クラスの再帰を利用した2分木をで挿入・表示を行おうとしているのですが, 挿入がうまくできません.

C言語では, 2分木を扱ったことがあるのですがあっちはポインタなので新しく勉強中です.

###発生している問題・エラーメッセージ
Java, 環境はEclipse Neon 4.6

まず, サイトを参考にして手作業で木を作成し(コメントで除外した部分)、表示させることは出来ました.

しかし, ランダムに表れた数字を2分木に格納しようとしたときに問題が生じました.
ソースコードの【問題箇所】とコメントしているメソッドとfor文に問題があります.
メソッドを再帰させtreeを辿って, 該当箇所に新しくクラスをインスタンス化し, コンストラクタを使用してtree.dataに数字を代入しています.

本当なら, 表示させたときに入れた数字がすべて表示されるはずなのですが上手くいきません. 恐らく, 正しく木を生成できていないと思います.
2時間ほど考えたのですが全くわからないので質問させていただきました.

###該当のソースコード

java

1import java.util.Random; 2 3/* 4 * Javaによる二分木の実装例 5 */ 6 7class TreeNode{ 8 //中身の宣言 9 int data; 10 TreeNode left; 11 TreeNode right; 12 13 //コンストラクタ(生成時に引数とする初期化方法) 14 TreeNode (int data, TreeNode left, TreeNode right){ 15 this.data = data; 16 this.left = left; 17 this.right = right; 18 } 19} 20 21public class TreeTraverse { 22 public static void main (String[] args) { 23 24 //Randomクラスのインスタンス化 25 Random Rnd = new Random(); 26 27 //ダミーの初期化 28 int DUMMY = 5; 29 30 //頑張って格納 31 32 TreeNode tree = new TreeNode(DUMMY, null, null); 33 34 /*除外 35 tree.right = new TreeNode(6, null, null); 36 tree.right.left = new TreeNode(2, null, null); 37 tree.right.right = new TreeNode(3, null, null); 38 tree.right.left.left = new TreeNode(4, null, null); 39 tree.right.left.right = new TreeNode(5, null, null); 40 tree.right.right.left = new TreeNode(6, null, null); 41 tree.right.right.right = new TreeNode(7, null, null); 42 tree.right.left.left.left = new TreeNode(8, null, null); 43 tree.right.left.left.right = new TreeNode(9, null, null); 44 */ 45 46 //【問題箇所】ダミーを5に設定し、0~10までの数字をランダムに10つ二分木に格納してみる 47 int N = 10; 48 int num = 0; 49 for(int i = 0;i<N;i++){ 50 num = Rnd.nextInt(10); 51 System.out.println(num + "\n"); 52 input(tree, num); 53 } 54 55 System.out.print("Preorder: "); 56 preorder(tree.right); 57 System.out.println(); 58 System.out.print("Inorder: "); 59 inorder(tree.right); 60 System.out.println(); 61 System.out.print("Postorder: "); 62 postorder(tree.right); 63 System.out.println(); 64 } 65 66//【問題箇所】 67static void input(TreeNode tree, int num){ 68 if(tree == null){ 69 tree = new TreeNode(num, null, null); 70 System.out.println(tree.data + " put\n"); 71 }else if(tree.data <= num){ 72 System.out.println("moved right.\n"); 73 input(tree.right, num); 74 }else if(tree.data >= num){ 75 System.out.println("moved left.\n"); 76 input(tree.left, num); 77 } 78} 79 80static void preorder (TreeNode tree) { 81 if (tree != null) { 82 System.out.print(" " + tree.data); 83 preorder(tree.left); 84 preorder(tree.right); 85 } 86} 87 88static void inorder (TreeNode tree) { 89 if (tree != null) { 90 inorder(tree.left); 91 System.out.print(" " + tree.data); 92 inorder(tree.right); 93 } 94} 95 96static void postorder (TreeNode tree) { 97 if (tree != null) { 98 postorder(tree.left); 99 postorder(tree.right); 100 System.out.print(" " + tree.data); 101 } 102 } 103} 104

###試したこと
デバッグするために, mainメソッドからtree.right.dataなどを表示させてみたのですがそもそも最初のダミー部分以外, インスタンス化できていないようです.
しかし, その理由がいくら考えてもわかりません.

###補足情報(言語/FW/ツール等のバージョンなど)
より詳細な情報

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

swordone

2017/09/29 08:00

コードのマークダウンをお願いします。まず、質問編集画面でコードの部分を選択し、編集枠上部にある<code>というボタンを押します。そうするとコードがバッククォートで囲まれるとともに「ここに言語を入力」という文字が出ます。その部分を「java」に書き換えて投稿してください。
ryo1729_k

2017/09/29 09:20

ご指摘の箇所を修正致しました、ありがとうございます。
guest

回答4

0

inputメソッドの中で、ローカル変数がnewされてるだけだからだと思います。
それはforループの中でrightleftを表示するとnullなので、そこから想定できます。

ボクもあまり詳しく説明できるほど知識はないんですが、
Cで言うポインタという概念はあまり表層化してないみたいです。
この辺りが参考になるでしょうか。。

あくまで値渡しという風に書いてあるので、

java

1// 【問題箇所】 2static TreeNode input(TreeNode tree, int num) { 3 if (tree == null) { 4 tree = new TreeNode(num, null, null); 5 } else if (tree.data <= num) { 6 tree.right = input(tree.right, num); 7 } else if (tree.data >= num) { 8 tree.left = input(tree.left, num); 9 } 10 return tree; 11}

こんな風に書くと期待値に近づくでしょうか。
(実行はできましたが、詳しく見てないので違うかもしれません。)
treeで明示的に値を渡してあげてるイメージです。

あとソースはバッククォート3つで囲うときれいに表示されます。
ご修正を。

投稿2017/09/29 08:05

szk.

総合スコア1400

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ryo1729_k

2017/09/30 04:46

回答ありがとうございます. 非常にわかりやすい説明でした. URLも読みました, 参照渡しと値渡しの違いについてしっかり理解してゆこうと思います. 最後にreturnをまとめることでとてもきれいな記述になっていますね!
guest

0

ベストアンサー

inputメソッドで例えば引数treeがnullの場合、新たなノードインスタンスを生成してinputメソッドの仮引数treeに代入していますね。メソッドの仮引数はメソッド内部を実行中でのみ存在しておりメソッド完了時に消滅します(Cでも同様)。仮引数に代入していた値は仮引数が消滅すると「どこからも参照されない宙ぶらりんのインスタンス」となり、inputメソッド呼び出し元からみると「せっかく生成したTreeNodeのインスタンスはどこにも結果が残らない」ことになります。それが期待通りに動かない原因です。(しかしおそらくJavaの参照型の値や変数への代入がどういうことかピンときていなければ以上の説明はやはりピンとこないと思います)

まず結論からいえば、とりあえず、こういうふうに書けばよいです。

java

1TreeNode input(TreeNode tree, int num) { 2 if (tree == null) { 3 return new TreeNode(num, null, null); 4 } else if (tree <= tree.data) { 5 tree.left = input(tree.left, num); 6 return tree; 7 } else { 8 tree.right = input(tree.right, num); 9 return tree; 10 } 11} 12... 13TreeNode root = null; // 必ずしもDUMMYノードは必要ない 14root = input(root, 1); 15root = input(root, 2); 16...

inputメソッドは「引数に指定したツリー(のルート)に値を挿入し、新たに構築されたツリー(のルート)を返す」のようにし、inputを使う側では常に戻り値をしかるべき変数へ代入して結果をその都度更新するのです。

ちなみに、上記のjavaのコードが実際にどう動くかのイメージを(厳密ではありませんが※)Cで書くと次のようになります。

例1

C

1#include <malloc.h> 2 3typedef struct _TreeNode *TreeNode; 4struct _TreeNode { 5 int data; 6 TreeNode left; 7 TreeNode right; 8}; 9 10TreeNode newTreeNode(int num, TreeNode left, TreeNode right) { 11 TreeNode node = (TreeNode)malloc(sizeof(struct _TreeNode)); 12 node->data = num; 13 node->left = left; 14 node->right = right; 15 return node; 16} 17 18TreeNode input(TreeNode tree, int num) { 19 if (tree == NULL) { 20 return newTreeNode(num, NULL, NULL); 21 } else if (num <= tree->data) { 22 tree->left = input(tree->left, num); 23 return tree; 24 } else { 25 tree->right = input(tree->right, num); 26 return tree; 27 } 28} 29 30int main(int argc, const char* argv[]) { 31 ... 32 TreeNode root = NULL; // やはり必ずしもDUMMYノードは必要ない 33 root = input(root, 1); 34 root = input(root, 2); 35 ... 36}

おそらく質問者さんは上記とはちょっと違うような実装をCでやっていたのではないでしょうか。

例2

C

1void input(TreeNode* tree, int num) { 2 if (*tree == NULL) { 3 *tree = newTreeNode(num, NULL, NULL); 4 } else if (num <= (*tree)->data) { 5 input(&(*tree)->left, num); 6 } else { 7 input(&(*tree)->right, num); 8 } 9} 10 11... 12 13TreeNode root = NULL; 14input(&root, 1); 15input(&root, 2); 16...

Javaではポインターがありませんが、実のところ参照型(TreeNodeもそう)の値は実質的にはCのポインター相当のように扱われています。

ただし、Cではポインター変数pに対して*p = ...と書けるのでinputメソッドの引数をstruct _TreeNodeのポインターのポインターにしておけば、input関数に戻り値は必要なく、関数内部で構造の変更を完全に記述できます。

しかしjavaではそういう書き方がありません。*tree = ...のように書けないわけです。tree = ...としか書けないのですから参照型変数への代入はC言語で言うとポインター変数に別のポインターを代入することのみしかできないと言えます。故にCで表現した場合の例2のようなインターフェースにはできず例1のようにしか実装できません。このように理解しておくと、Cとの対比でjavaでどう書かなければならないか見えてくるのではないでしょうか?

おそらくCでポインターを使った実装に慣れているとJavaで若干の不便さを感じるかも知れませんが、そのあたりは慣れと思います。


※:厳密ではないとは
javaでの参照型変数はCでのポインターと書きましたが、厳密には違います。Cでヒープからmallocした領域は決してアドレスが変化しませんが、javaではガベージコレクターが自動的に場所を移動(再配置)する点が違っています。しかし本件ではとりあえずjavaのメモリーの再配置については気にせず、javaの参照型変数がCのポインター変数相当と思って理解してもかまわないと思います。

投稿2017/09/29 08:18

KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ryo1729_k

2017/09/30 03:56

詳しい解説ありがとうございます. 今回の質問を例にとれば, treeというクラス型変数がrightを参照し, rightもまたクラス型変数なので参照できる. そして, 参照すべき実体を作るためにnewメソッドを使ってインスタンス化しているのだと理解しました. そして, 参照している実体のアドレスはCとは違い再配置されるものなのですね. 記憶に留めておきます. Cでの詳しい解説もありがとうございます!! 僕はずっと, 下の例のようにポインタのポインタにしていたので同じような気分でvoidメソッドにしていたようです... Cとjavaの対比については, これからしっかり理解していけたらと思います.
guest

0

説明は有識者の方がばっちりしてくれたので、
私だったらこうするを書きます。

Java

1static void input(TreeNode tree, int num){ 2 // treeにnullを渡したらおとなしく落ちろ・・・ 3 if(tree.data <= num){ 4 if (tree.right == null) { 5 tree.right = new TreeNode(num, null, null); 6 } else { 7 input(tree.right, num); 8 } 9 } else if(tree.data >= num){ 10 if (tree.left== null) { 11 tree.left = new TreeNode(num, null, null); 12 } else { 13 input(tree.left, num); 14 } 15 } 16}

投稿2017/09/29 08:40

abs123

総合スコア1280

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ryo1729_k

2017/09/30 04:36

回答ありがとうございます! これって, 新たなノードインスタンスを仮引数treeに代入しているだけなので, 一見実引数の方に代入しないと反映されないように思われるのですがどのような仕組みになっているのでしょうか? よろしければ教えていただけると幸いです.
abs123

2017/10/02 01:18

参照型を介した操作は、 参照型が指し示すインスタンスに対する操作になります。 仮引数と実引数で見ているものが同じであれば、 その見ているものに対して何らかの操作をした結果も、 仮引数と実引数で同じように見えるはずです。 Cで言うと、ポインタに対しアロー演算子を使った操作をしているといった感じでしょうか?
guest

0

回答してくださった方々、本当にありがとうございます。
仮引数を変更しても、実引数は変更されないことを失念しておりました。それはCでも定番ですね汗
回答者へのコメントに関しては、もう少し調べて理解出来たら返信させていただきます。

9/30追記
BAを付けなければならないところを、自己解決でコメントしてしまいました。
そのため、今回回答して頂いた方々にご迷惑をおかけしました。
お詫び申し上げます。
ただいま修正しました

投稿2017/09/29 09:41

編集2017/09/29 16:41
ryo1729_k

総合スコア7

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

swordone

2017/09/29 10:28

回答者の回答が参考になったなら、そちらにベストアンサーを付けるべきです。
ryo1729_k

2017/09/29 16:42

すみません。 自己解決の使い方を誤っていたようです。 ご指摘いただきありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問