![](http://oshiete.xgoo.jp/images/v2/pc/qa/question_title.png?e8efa67)
A 回答 (5件)
- 最新から表示
- 回答順に表示
No.5
- 回答日時:
さて、#3だと「一旦1〜10を順番に追加して」偏った木を一気にバランスの良い木に変換する、って手法を紹介しましたが。
そうじゃなくって、順次値を追加する度に木を更新する、って手段だとどうなるか。その一つを紹介しようと思います。
またもやWikipediaからアスキーアートならぬAA木と言うアルゴリズムを。
AA木:
https://ja.wikipedia.org/wiki/AA%E6%9C%A8
これは
> 順序のあるデータを効率的に格納し
と言うので、例によって「1〜10の値を順番に追加」していくんだけど、リアルタイムに木を弄ってバランスの良い木に変換する手法の模様です。
ただし、一般的に平衡二分探索木の場合、
> 平衡二分探索木を常に最小の高さに保つのは高くつくので、いつも正確に平衡している必要はない。
ので、このアルゴリズムを適用しても、完全平衡木に毎回必ずしもなる、ってワケではない様です。
ではまずはノードの設計から。
詳しい内情はWikipediaに譲りますが、この場合のノードには通常ノードに求められる情報に加えて、「レベル」と言う情報があります。木が末葉から数えて何段目に存在するのか、と言う情報ですね。
また、ここではWikipediaに書かれてる「削除」と言うメソッドは実装していません。今回の題意から言うと不必要だから、ですね。
ただ、Wikipediaには疑似コードが載ってますし、疑似コードをどうやってJavaのコードとして書き換えるのか、と言うのは一回自分でやってみれば良い、と思ってるんで、サンプル程度としてそれ以外を提示しておけば自分で実装可能な範疇だ、と思っています。
// TreeNode.java
public class TreeNode {
int value;
int level;
TreeNode left;
TreeNode right;
TreeNode (int value, int level, TreeNode left, TreeNode right) {
this.value = value;
this.level = level;
this.left = left;
this.right = right;
}
static TreeNode skew(TreeNode T) {
if (T == null) {
return null;
} else if (T.left == null) {
return T;
} else if (T.left.level == T.level) {
// 水平左リンクの木を入れ替える。
TreeNode L = T.left;
T.left = L.right;
L.right = T;
return L;
} else {
return T;
}
}
static TreeNode split(TreeNode T) {
if (T == null) {
return null;
} else if ((T.right == null) || (T.right.right == null)) {
return T;
} else if (T.level == T.right.right.level) {
// 水平右リンクが2つある場合。真ん中を持ち上げて、それを返す。
TreeNode R = T.right;
T.right = R.left;
R.left = T;
R.level += 1;
return R;
} else {
return T;
}
}
public TreeNode insert(int X, TreeNode T) {
// まず、通常の2分木の挿入操作を行う。新たなノードが作成されたか
// 部分木の根が変わったバイ、再帰呼出しの結果を正しい子ノードに設定する。
if (T == null) {
// X に対応する新たな葉ノードを生成
return new TreeNode(X, 1, null, null);
} else if (X < T.value) {
T.left = insert(X, T.left);
} else if (X > T.value) {
T.right = insert(X, T.right);
}
// X == T.value の場合がない点に注意。その場合、挿入は行われない。
// 場合によっては、違う動作が望ましいこともある。
// skew を行い、次いで split を行う。実際に回転をするかどうかは
// 上掲のようにこれら手続き内で判断する。
return split(skew(T));
}
// ノードの出力用メソッド
public void print() {
print("", this, false);
}
public void print(String prefix, TreeNode n, boolean isLeft) {
if (n != null) {
System.out.println(prefix + (isLeft ? "|-- ": "\\-- ") + n.value);
print(prefix + (isLeft ? "| " : " "), n.left, true);
print(prefix + (isLeft ? "| " : " "), n.right, false);
}
}
}
// ここまで
次にMain.javaを書きます。こっちは短い。
// Main.java
public class Main {
public static void main(String[] args) {
int DUMMY = 0;
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
TreeNode tree = new TreeNode(DUMMY, 0, null, null);
for (int val: array) {
tree.insert(val, tree);
tree.print();
}
}
}
// ここまで
コンパイルして実行してみれば、arrayで定義してるソート済みのデータが先頭から取り込まれてどう「回転」して平衡二分木を作っていくのか、その流れが分かると思います。
\-- 0
\-- 1
↓
\-- 0
\-- 1
\-- 2
↓
\-- 0
\-- 2
|-- 1
\-- 3
↓
\-- 0
\-- 2
|-- 1
\-- 3
\-- 4
↓
\-- 0
\-- 2
|-- 1
\-- 4
|-- 3
\-- 5
↓
\-- 0
\-- 2
|-- 1
\-- 4
|-- 3
\-- 5
\-- 6
↓
\-- 0
\-- 4
|-- 2
| |-- 1
| \-- 3
\-- 6
|-- 5
\-- 7
↓
\-- 0
\-- 4
|-- 2
| |-- 1
| \-- 3
\-- 6
|-- 5
\-- 7
\-- 8
↓
\-- 0
\-- 4
|-- 2
| |-- 1
| \-- 3
\-- 6
|-- 5
\-- 8
|-- 7
\-- 9
↓
\-- 0
\-- 4
|-- 2
| |-- 1
| \-- 3
\-- 6
|-- 5
\-- 8
|-- 7
\-- 9
\-- 10
まぁ、ダミーのルート0が残ってますが、レベル0は操作の影響を受けないので、事実上4をルートとした上のような平衡木(ただし完全ではない)が生成されて行くのが分かります。
No.3
- 回答日時:
ああクソ、メンドクセェな、Javaは。
それでも最近慣れてきた、とか慢心してたんだけど、やっぱJavaは手こずります。
まず、ノードの設計から。
// TreeNode.java
public class TreeNode {
int data;
TreeNode left;
TreeNode right;
TreeNode(int data, TreeNode left, TreeNode right) {
this.data = data;
this.left = left;
this.right = right;
}
// 与えられた配列をそのまま基本に沿って二分木化する
public TreeNode insert(int[] arr, TreeNode root, int i) {
if (i < arr.length) {
TreeNode temp = new TreeNode(arr[i], null, null);
root = temp;
if (root.data > arr[i]) {
root.left = insert(arr, root.left, i + 1);
} else {
root.right = insert(arr, root.right, i + 1);
}
}
return root;
}
// ノードの出力用関数
public void print() {
print("", this, false);
}
public void print(String prefix, TreeNode n, boolean isLeft) {
if (n != null) {
System.out.println(prefix + (isLeft ? "|-- ": "\\-- ") + n.data);
print(prefix + (isLeft ? "| " : " "), n.left, true);
print(prefix + (isLeft ? "| " : " "), n.right, false);
}
}
}
// ここまで
で、Main.javaファイルを作る。
// Main.java
public class Main{
public static void main(String[] args) {
int DUMMY = 0;
TreeNode tree = new TreeNode(DUMMY, null, null);
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
tree = tree.insert(arr, tree, 0);
tree.print();
int nodeCount = getNodeCount(tree);
tree = balanceVine(tree, nodeCount);
tree.print();
}
static TreeNode leftRotate(TreeNode n) {
if (n.right != null) {
TreeNode rightChild = n.right;
n.right = rightChild.right;
rightChild.right = rightChild.left;
rightChild.left = n.left;
n.left = rightChild;
int temp = n.data;
n.data = rightChild.data;
rightChild.data = temp;
}
return n;
}
static int getNodeCount(TreeNode root) {
if (root == null) {
return 0;
}
int i = 1;
while (root.right != null) {
root = root.right;
i++;
}
return i;
}
public static int log2(int x) {
return (int) (Math.log(x) / Math.log(2));
}
static TreeNode balanceVine(TreeNode root, int nodeCount) {
int times = log2(nodeCount);
TreeNode newRoot = root;
for (int i = 0; i < times; i++) {
newRoot = leftRotate(newRoot);
root = newRoot.right;
for (int j = 0; j < nodeCount / 2 - 1; j++) {
root = leftRotate(root);
root = root.right;
}
nodeCount >>= 1;
}
return newRoot;
}
}
// ここまで
WikipediaのDSWアルゴリズムよりマシなアルゴリズム紹介がねぇか、って国内サイト探したんですが、無いカンジだったんで、海外サイトを調べてなんとかでっち上げてみました。
要するに配列が、例えば
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
とあったとして、ノードのデータに最初の値をセット。次に来る値がノードのデータより小さかったら左に、大きかったら右にデータをセットしつつ新しくノードを連結していくのが二分木作成の基本戦略なんですが。
提示の問題のように、ソート済みの配列を対象とすると、先に見たように蔦(あるいは背骨、って言い方もするみたいですが・笑)になってしまう。
こういう状態ですね。
\-- 1
\-- 2
\-- 3
\-- 4
\-- 5
\-- 6
\-- 7
\-- 8
\-- 9
\-- 10
この状態の二分木に定義したbalanceVineを適用すると、次のような二分木に変換されます。
\-- 8
|-- 4
| |-- 2
| | |-- 1
| | \-- 3
| \-- 6
| |-- 5
| \-- 7
\-- 10
|-- 9
8がルート、と前の結果と違ってますね。
前は5段だったけど、今回は4段。こっちの方が「修正論文」に従ってて、完全な平衡木になってんじゃないのかな。
とまぁ、参考までに。
No.2
- 回答日時:
これ、Javaで書くのメンドくせぇなぁ・・・・・・。
基本リファレンスだけにしとくか。
> 二分探索木で例えば1〜10の値を順番に追加したら偏った木になると思う
はい。
正攻法でそのまま二分木を作ろうとすると、次のような構造になると思うんですよね。
'(1 () (2 () (3 () (4 () (5 () (6 () (7 () (8 () (9 () 10)))))))))
全部右によって一本になっている。
この状態の(一本になってる)二分木を特に「蔦」と呼ぶらしい。英語だとvineですか。
んで、こいつをどうやってマトモなbinary-treeに変換するのか・・・・・・。
結論から言うと、Wikipediaに解決策が載ってました。その名もDSWアルゴリズム、です。
DSWアルゴリズム:
https://ja.wikipedia.org/wiki/DSW%E3%82%A2%E3%83 …
上のページにDSWアルゴリズムが載ってて、基本的に3つの関数(Javaだとメソッドか)を書けばバランスの良い木になるらしい。
そこで紹介されている3つは
・tree-to-vine ・・・・・・ バランスの悪い木を蔦へ変換する
・vine-to-tree ・・・・・・蔦をバランスの良い木へ変換する
・compress・・・・・・vine-to-treeの補助
なんですが、ここでの与題は、「既に蔦状態の木を」変換するのが目的なんで、単純にvine-to-treeとcompressの2つを実装すれば、そのまま解となる、って事ですね。
Pythonで試してみたら上手く行ったんで多分上手く行くんじゃないでしょうか。
>>> tree
[1, [], [2, [], [3, [], [4, [], [5, [], [6, [], [7, [], [8, [], [9, [], [10, [], []]]]]]]]]]]
>>> pseudo_node
[False, [], [1, [], [2, [], [3, [], [4, [], [5, [], [6, [], [7, [], [8, [], [9, [], [10, [], []]]]]]]]]]]]
>>> vine2tree(pseudo_node, 11)
>>> pseudo_node[2]
[4, [2, [1, [], []], [3, [], []]], [7, [6, [5, [], []], []], [8, [], [9, [], [10, [], []]]]]]
>>>
つまり無事に
4
2 7
1 3 6 8
5 9
10
と言うバランスの二分木に変換されています(これが完全な平衡木かどうかは議論の余地があるだろうし、Wikipediaには後に改良版の論文が発表された、と注釈してるが、その疑似コードは残念ながら書かれてない)。
Wikipediaの疑似コードは結構分かりやすいんで、そのままプログラムしていけば一丁上がり、ですよ。
(Javaのリストは直接書き上げられないんで不便ですけどね。)
No.1
- 回答日時:
追加の順番で実現するなら
中央値を追加
上で追加した中央値より小さい値を、同じアルゴリズムで順番に追加
上で追加した中央値より大きい値を、同じアルゴリズムで順番に追加
よく使われるのは、追加自体に工夫をするもの。
詳しくは「二分探索木 平衡化」で検索
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- Excel(エクセル) Excelマクロ 差分抽出の方法が知りたいです。 2 2023/03/07 13:25
- 小学校 学校の先生はそんなに頭よくないですよね? 私は栃木県民です。 子供が小学校に行っているのですが、学校 8 2022/08/20 11:32
- 高校受験 中学3年生です!受験(高専受験)が心配になってきたのでアドバイスが欲しいです 偏差値64の高専を狙っ 2 2022/10/10 11:35
- その他(教育・科学・学問) 【 情報 ハフマン木 】 ハフマン符号化では、なぜハフマン木を用いるのですか? 例えばテキストデータ 1 2022/10/11 22:41
- アニメ 宮崎吾朗氏がスタジオジブリへ鈴木氏に無理やり連れてこられた際のエピソードについて 1 2023/06/17 16:48
- 野球 高校野球 1 2022/08/31 13:44
- 大学受験 地方に住んでます。栃木県です。 偏差値は45あたりです。 四年制大学に行く場合、この偏差値だと、都内 4 2022/11/03 10:49
- 高校受験 特別会の五木模試でふざけました 3 2022/12/25 09:36
- 農学 樹木の名前、教えてください 1 2022/10/31 19:41
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
C#で実行時にメソッドの返り値...
-
C++からC#のdllを参照する際、...
-
パソコンキーボードで時分秒を...
-
javaのプログラミングで作るRPG...
-
複数のテキストボックスに同じ...
-
C言語のポインターに関する警告
-
*で正三角形を出力
-
プログラミングの問題です。大...
-
JSPやサーブレットでSystem.out...
-
IF関数でEmpty値を設定する方法。
-
C言語の変数(LSB)の合わせ込...
-
論理演算子”||”またはの入力方法
-
行列の表示
-
1~100までの数字を表示し、か...
-
privateなフィールドは継承され...
-
戻り値を使用する呼出
-
n番目に大きな値を探索する
-
VBAで配列の計算
-
System.err. printlnとSystem.o...
-
日付型の入力値チェック
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
「配列定数は、イニシャライザ...
-
Javaで電卓を作りたい
-
式の型は配列型で int に解決済...
-
javaでカレンダー作成
-
JAVA エラー 式の開始が不正で...
-
JAVAでCの関数ポインタのような...
-
java spring でエラーが出て困...
-
6桁の数字を重複なしでランダム...
-
c# デリゲート関連の命名について
-
C++からC#のdllを参照する際、...
-
メインが含まれていません
-
(Swing)JTextFieldを半角のみ入...
-
DataSet(DataTable)の使い方
-
三目並べ(Tick-Tack-Toe)をJav...
-
JUnit4のアノテーションについて
-
初心者ですが、今javaで簡単な...
-
classを使って座標軸を求めるコ...
-
javaでcsvファイル読込時の改行...
-
C#で実行時にメソッドの返り値...
-
Java 初心者 int型の取り扱い方
おすすめ情報