すべてのキーが一意である二分探索木を考えてみましょう。ノードには親ポインタがありません。
最大 n/2 のマークされたノードがあります。
O(n 2 ) 時間でそれらすべてを削除できます (postorder traversal を使用し、O(n) でマークされたノードの削除に遭遇した場合)。しかし、それは不適切です。マークされたすべてのノードをO(n)時間で削除するアルゴリズムが必要です。ありがとう。
EDIT削除後、ノードの順序を変更しないようにする必要があります。
EDIT-2したがって、通常の削除(左のサブツリーで最も右のノードを見つけて、削除するノードと交換する)を使用して、マークされた各ノードを削除したように見えるはずです。
3 に答える
多くの方法がありますが、これは正しく理解しやすく、副作用として完全にバランスの取れたツリーを提供する方法です。ただし、線形の余分なスペースが必要です。
- サイズNからマークされたノードの数を引いた配列を作成します(マークされたノードの数がわからず、チェックしたくない場合はN)。
- マークされた要素をスキップして、順序どおりにトラバーサルすることにより、要素を配列内に順番に配置します。これには線形の時間がかかり、スタックスペースは木の高さに比例します。
- アレイを使用してツリーをトップダウンで再構築します。配列のmid要素が新しいルートになり、左サブ配列のmid要素が新しい左子になり、右サブ配列のmid要素が新しい右サブ子になります。再帰的に繰り返します。これには、線形時間と対数スタックスペースが必要です。
更新:マークされた要素をスキップすると言うのを忘れましたが、それは明らかでしたね?;)
私はそれを行う方法を見つけました!
- 順序のないトラバーサルを使用します。
- 関数を再帰的に呼び出す前に、ノードを削除する必要があるかどうかを確認します。
- 削除するノードが見つかったら、フラグtoFindMaxを立てて、左側のサブツリーの右端のノードを検索します。
- 削除する別のノードが見つかった場合は、すべての変数をスタックにプッシュし、ノードが削除されたときにそれらをポップします。
- 左側のサブツリーで最大値が見つかったら、そのサブツリーとその親への参照を保存します。
そして、削除する最初のノードに再帰的に戻ると、それを削除します(最大値と交換します)。
static class LinearDeletion {
public static Node MIN_VALUE = new Node(Integer.MIN_VALUE);;
boolean toFindMax = false;
Node parentOfMax = null;
Node max = MIN_VALUE;
Stack<Object> stack = new Stack<>();
public void perform(Node x, Node parent) {
if (x.isToDelete) {
stack.push(toFindMax);
stack.push(parentOfMax);
stack.push(max);
toFindMax = true;
parentOfMax = null;
max = MIN_VALUE;
if (x.left != null) {
perform(x.left, x);
}
if (x.left == null) { //deletion of the node
if (parent.left == x) {
parent.left = x.right;
} else {
parent.right = x.right;
}
} else {
if (x.right == null) {
if (parent.left == x) {
parent.left = x.left;
} else {
parent.right = x.left;
}
} else {
x.key = max.key;
x.isToDelete = max.isToDelete;
if (parentOfMax != x) {
parentOfMax.right = max.left;
} else {
x.left = max.left;
}
}
} // end of deletion
max = (Node) stack.pop();
parentOfMax = (Node) stack.pop();
toFindMax = (boolean) stack.pop();
if (toFindMax) { // check if the current node is the maximum
if (x.key > max.key) {
max = x;
parentOfMax = parent;
}
}
if (x.right != null) {
perform(x.right, x);
}
} else {
if (x.left != null) {
perform(x.left, x);
}
if (toFindMax) {
if (x.key > max.key) {
max = x;
parentOfMax = parent;
}
}
if (x.right != null) {
perform(x.right, x);
}
}
}
}
postorder トラバーサルが O(n 2 )になる理由がわかりません。単一ノードの削除が O(n) である理由は、ノードを見つけるためにツリーをトラバースする必要があるためです。これは O(n) 操作です。ただし、ノードが見つかったら、O(1) 時間で削除できます。*したがって、O(n) 時間の 1 回のトラバーサルで、O(n) 個のマークされたすべてのノードを削除できます。
*バランスの取れたツリーを維持する必要がある場合を除きます。ただし、それを要件としてリストしていません。
編集@njlarsson がコメントで正しく指摘しているように、削除操作は通常、ノードが見つかった後でも O(1) ではありません。ただし、削除するノードにアクセスする前に左右のサブツリーがトラバースされるため、サブツリーのトラバース中に追加コストなしでサブツリーの最小 (または最大) 要素を取得できます。これにより、O(1) 削除が可能になります。