[C]B-TREE(3)
次に、ノードのマージと削除の手順を見てみましょう.
鍵を削除する演算が実行されても、B−TREE属性の1つである「すべてのノードにt(最小回数)−1つ以上の鍵が必要」である.満たさなければならない.したがって、削除操作を実行するノードの鍵数がt−1である場合、ノードの鍵数をt個以上に増やす必要がある.
このとき,ノード鍵の数を増やす方法は2つある.
(1隣接する兄弟ノードから鍵を取得するか、(2)隣接する兄弟ノードとマージする.
この2つの方法のどちらを使用するかは、兄弟ノードの鍵の数に依存します.
(1)兄弟ノードの鍵の個数がt個より大きい場合,1個だけ取得しても鍵の個数はt−1個を満たすので,鍵の取得方法を選択する.
(2)ただし,兄弟ノードの鍵数がt−1個である場合,鍵を1個入力すると,前述した最小鍵数を満たすことができない.したがって、2つのノードを結合します.
マージ時には、取得した親ノードの1つの鍵がマージされ、マージされたターゲットノードの個数がt-1であるため、2つのノードをマージして親ノードの1つの鍵を取得しても、キーの個数は、マージの場合にノードが持つ最大鍵の個数を超えない.
(1)兄弟ノードから鍵をインポート
まず,隣接する兄弟ノードから鍵のコードを取得する.この場合、左側の兄弟ノードからインポートするか、右側の兄弟ノードからインポートすることができます.
兄弟ノードから鍵を取得する場合、兄弟ノードの鍵を直接取得するのではなく、兄弟ノードから取得する鍵と親ノードを2つのサブノードの基準とする鍵を変更し、親ノードから基準鍵を取得します.
下図のように.
このとき、親の身長を下げる理由は、兄弟の身長をそのまま持ってくると、身長を基準に子どもを並べ替えるB-TREEの性質に反するためだ.
次のプロセスでは、兄弟ノードから直接12が導入されると、14と17の間のサブノードは12を含み、これはB−TREEの性質に反する.
マージを行うと、親はマージする2つのサブノードを区切る基準となるキーを置いて、2つのノードをマージするようにマージします.
このとき親の身長を下げるのもB-TREEの性格に背かないためです.
下図のように.
このとき親の身長17を下げて合併しなければ順位も正しくなく、親の身長は2個、子供も2個となってB-TREEの性格に反する.
まず、削除演算の一般的な手順は次のとおりです.
削除する鍵が見つかった後、出会ったノードの鍵数がt-1個の場合、兄弟ノードから鍵を取得するか、結合鍵をt個以上にして降格させる.
キーをt個以上に設定しておくのは、削除や再アップロードの回数を最小限に抑えるためです.
例えば、削除鍵を含むノードの鍵数がt−1個であり、隣接する兄弟ノードの鍵数もt−1個である場合、削除のためにマージする必要がある.
ただし、親ノードの鍵数もt-1個であると、親ノードから鍵を取得できないため、親ノードの鍵数をt個以上に再調整する必要がある.
従って、下から出会ったノードの鍵数をt個以上に繰り上げて調整すれば、上記の例のように親ノードに戻ることを回避することができる.
削除演算は2つのケースで行われます.
(1)リーフノードから削除,(2)内部ノードから削除.
このとき,内部ノードとはリーフノード以外のすべてのノードを指す.
(1)リーフノードから削除
リーフノードから削除する場合は,鍵を削除するだけでよい.
上から降りて出会ったすべてのノードの鍵の個数をt個以上に調整したので,直接削除しても鍵の最小個数t−1個を満たす.
(2)内部ノードからの削除
内部ノードから削除する場合も、左、右のサブノードのキーがtより大きいか、tでないかの2つに分けられます.
(2)-1左側または右側のサブノードのキーはtより大きい.
左サブキーの個数がtより大きい場合は、先頭キー(Inorder Predeccessor)が検索され、右サブキーの個数がtより大きい場合は、後ガイド(Inorder Successor)が検索され、削除されたキーに格納され、次に、見つかった先頭キー、後ガイドキーが削除演算される.
このとき、リーフノードには、プリアンブル、ポストガイドが存在する.
下図のように.
1.内部ノードでプリアンブルを検索して10を削除します.このとき,10のプリアンブルは18であり,リーフノード上に存在する.
2.次に10を18に変更し、リーフノード上の18を削除演算する.
このとき、前キーと後キーを検索するコードは以下のようになります.
プリアンブルキーを検索するには、削除するキーの左側のサブノードにドロップし、リーフノードに遭遇するまでそのノードの一番右側のサブノードにドロップします.リーフノードに到達すると、そのノード上の最大のキーがプリアンブルとなる.
後続のキーを探すときは、下がる方向が逆になるだけで、方法は同じです.
この場合、隣接する2つのサブノードを結合し、内部ノードの鍵を削除できます.
下図のように.
次に、削除演算に使用するコードを示します.
上記の手順に従って、ルートノードの鍵がマージまたは削除プロセスによってすべて消えた場合は、ルートの更新に注意するだけでよい.
DFSとして実装される.
フィードバックを歓迎します.
-終了-
3.ノードを結合して鍵を取得する
鍵を削除する演算が実行されても、B−TREE属性の1つである「すべてのノードにt(最小回数)−1つ以上の鍵が必要」である.満たさなければならない.したがって、削除操作を実行するノードの鍵数がt−1である場合、ノードの鍵数をt個以上に増やす必要がある.
このとき,ノード鍵の数を増やす方法は2つある.
(1隣接する兄弟ノードから鍵を取得するか、(2)隣接する兄弟ノードとマージする.
この2つの方法のどちらを使用するかは、兄弟ノードの鍵の数に依存します.
(1)兄弟ノードの鍵の個数がt個より大きい場合,1個だけ取得しても鍵の個数はt−1個を満たすので,鍵の取得方法を選択する.
(2)ただし,兄弟ノードの鍵数がt−1個である場合,鍵を1個入力すると,前述した最小鍵数を満たすことができない.したがって、2つのノードを結合します.
マージ時には、取得した親ノードの1つの鍵がマージされ、マージされたターゲットノードの個数がt-1であるため、2つのノードをマージして親ノードの1つの鍵を取得しても、キーの個数は、マージの場合にノードが持つ最大鍵の個数を超えない.
(1)兄弟ノードから鍵をインポート
まず,隣接する兄弟ノードから鍵のコードを取得する.この場合、左側の兄弟ノードからインポートするか、右側の兄弟ノードからインポートすることができます.
兄弟ノードから鍵を取得する場合、兄弟ノードの鍵を直接取得するのではなく、兄弟ノードから取得する鍵と親ノードを2つのサブノードの基準とする鍵を変更し、親ノードから基準鍵を取得します.
下図のように.
このとき、親の身長を下げる理由は、兄弟の身長をそのまま持ってくると、身長を基準に子どもを並べ替えるB-TREEの性質に反するためだ.
次のプロセスでは、兄弟ノードから直接12が導入されると、14と17の間のサブノードは12を含み、これはB−TREEの性質に反する.
void move_key_right_to_left(node *left_child, node *right_child, int *parent_key) {
left_child->key[MIN_DEGREE] = *parent_key; //부모의 키를 가져오고
left_child->key_count += 1; //왼쪽 노드의 키의 개수 증가
*parent_key = right_child->key[1]; //부모의 키를 오른쪽 형제노드에서 가져온 키로 바꿔주기
right_child->key_count -= 1; //오른쪽 노드의 키의 개수를 감소
left_child->linker[MIN_DEGREE + 1] = right_child->linker[1];//오른쪽노드에서 자식 가져오기
//오른쪽 노드에서 가장 앞에 있던 키가 없어졌으므로 키와 자식 한칸씩 당겨주기
for (int j = 1; j <= right_child->key_count; j++) {
right_child->key[j] = right_child->key[j + 1];
}
//리프노드이면 자식이 없기 때문에 리프노드가 아닐 때만 자식을 옮겨준다.
if (!left_child->is_leaf) {
for (int j = 1; j <= (right_child->key_count) + 1; j++ ) {
right_child->linker[j] = right_child->linker[j + 1];
}
}
}
void move_key_left_to_right(node *left_child, node *right_child, int *parent_key) {
for (int j = right_child->key_count; j >= 1; j--) {
right_child->key[j + 1] = right_child->key[j];
}
int left_sibling_key_count = left_child->key_count;
if (!right_child->is_leaf){
for (int j = (right_child->key_count) + 1; j >= 1; j--) {
right_child->linker[j + 1] = right_child->linker[j];
}
right_child->linker[1] = left_child->linker[left_sibling_key_count + 1];
}
right_child->key_count += 1;
right_child->key[1] = *parent_key;
*parent_key = left_child->key[left_sibling_key_count];
left_child->key_count -= 1;
}
(2)兄弟ノードのマージマージを行うと、親はマージする2つのサブノードを区切る基準となるキーを置いて、2つのノードをマージするようにマージします.
このとき親の身長を下げるのもB-TREEの性格に背かないためです.
下図のように.
このとき親の身長17を下げて合併しなければ順位も正しくなく、親の身長は2個、子供も2個となってB-TREEの性格に反する.
void merge_node(node *parent, node *left_child, node *right_child, int index) {
//왼쪽 자식의 끝에 부모의 키를 내린다.
left_child->key[left_child->key_count + 1] = parent->key[index];
//부모의 키를 내렸으니 부모의 키와 자식을 한칸씩 당겨준다.
for (int j = index; j < parent->key_count; j++) {
parent->key[j] = parent->key[j + 1];
}
for (int j = index + 1; j <= parent->key_count; j++) {
parent->linker[j] = parent->linker[j + 1];
}
parent->key_count -= 1;
//오른쪽 자식의 키를 모두 왼쪽 자식으로 옮겨준다.
for (int j = 1; j <= right_child->key_count; j++) {
left_child->key[MIN_DEGREE + j] = right_child->key[j];
}
if (!left_child->is_leaf) {
for (int j = 1; j <= (right_child->key_count) + 1; j++) {
left_child->linker[MIN_DEGREE + j] = right_child->linker[j];
}
}
//왼쪽 자식의 키의 개수 증가시킨다. 이때 왼쪽자식의 키의 개수는 항상 2t - 1개이다.
left_child->key_count += (right_child->key_count + 1);
//오른쪽 자식은 더이상 사용하지 않기 때문에 메모리를 해제한다.
free(right_child);
}
4.削除
まず、削除演算の一般的な手順は次のとおりです.
削除する鍵が見つかった後、出会ったノードの鍵数がt-1個の場合、兄弟ノードから鍵を取得するか、結合鍵をt個以上にして降格させる.
キーをt個以上に設定しておくのは、削除や再アップロードの回数を最小限に抑えるためです.
例えば、削除鍵を含むノードの鍵数がt−1個であり、隣接する兄弟ノードの鍵数もt−1個である場合、削除のためにマージする必要がある.
ただし、親ノードの鍵数もt-1個であると、親ノードから鍵を取得できないため、親ノードの鍵数をt個以上に再調整する必要がある.
従って、下から出会ったノードの鍵数をt個以上に繰り上げて調整すれば、上記の例のように親ノードに戻ることを回避することができる.
削除演算は2つのケースで行われます.
(1)リーフノードから削除,(2)内部ノードから削除.
このとき,内部ノードとはリーフノード以外のすべてのノードを指す.
(1)リーフノードから削除
リーフノードから削除する場合は,鍵を削除するだけでよい.
上から降りて出会ったすべてのノードの鍵の個数をt個以上に調整したので,直接削除しても鍵の最小個数t−1個を満たす.
(2)内部ノードからの削除
内部ノードから削除する場合も、左、右のサブノードのキーがtより大きいか、tでないかの2つに分けられます.
(2)-1左側または右側のサブノードのキーはtより大きい.
左サブキーの個数がtより大きい場合は、先頭キー(Inorder Predeccessor)が検索され、右サブキーの個数がtより大きい場合は、後ガイド(Inorder Successor)が検索され、削除されたキーに格納され、次に、見つかった先頭キー、後ガイドキーが削除演算される.
このとき、リーフノードには、プリアンブル、ポストガイドが存在する.
下図のように.
1.内部ノードでプリアンブルを検索して10を削除します.このとき,10のプリアンブルは18であり,リーフノード上に存在する.
2.次に10を18に変更し、リーフノード上の18を削除演算する.
このとき、前キーと後キーを検索するコードは以下のようになります.
プリアンブルキーを検索するには、削除するキーの左側のサブノードにドロップし、リーフノードに遭遇するまでそのノードの一番右側のサブノードにドロップします.リーフノードに到達すると、そのノード上の最大のキーがプリアンブルとなる.
後続のキーを探すときは、下がる方向が逆になるだけで、方法は同じです.
int PRED (node *pred_child) {
if (pred_child->is_leaf) {
return pred_child->key[pred_child->key_count];
} else {
return PRED(pred_child->linker[(pred_child->key_count) + 1]);
}
}
int SUCC (node *succ_child) {
if (succ_child->is_leaf) {
return succ_child->key[1];
} else {
return SUCC(succ_child->linker[1]);
}
}
(2)-2隣接するサブノードの鍵はすべてt-1である.この場合、隣接する2つのサブノードを結合し、内部ノードの鍵を削除できます.
下図のように.
次に、削除演算に使用するコードを示します.
上記の手順に従って、ルートノードの鍵がマージまたは削除プロセスによってすべて消えた場合は、ルートの更新に注意するだけでよい.
void b_tree_delete(node *sub_root, node **root, int k) {
if ((*root)->key_count == 0) {
if ((*root)->is_leaf) {
printf("tree is empty\n");
return;
}
}
node_delete(sub_root, k);
if ((*root)->key_count == 0) {
if ((*root)->is_leaf) {
printf("tree is empty\n");
free(*root);
b_tree_create(root);
return;
}
else {
node *old_root = *root;
*root = (*root)->linker[1];
free(old_root);
return;
}
}
}
void node_delete(node *sub_root, int k) {
if (sub_root->is_leaf){
int original_key_count = sub_root->key_count;
for (int i = 1; i <= sub_root->key_count; i ++) {
if (sub_root->key[i] == k){
for (int j = i; j < sub_root->key_count; j ++) {
sub_root->key[j] = sub_root->key[j + 1];
}
sub_root->key_count -= 1;
}
}
if (original_key_count == sub_root->key_count) {
printf("%d not in b-tree\n", k);
}
return;
}
int i = 1;
while(sub_root->key[i] < k && i <= sub_root->key_count) {
i += 1;
}
if (sub_root->key[i] == k && i <= sub_root->key_count) {
node *left_child = sub_root->linker[i];
node *right_child = sub_root->linker[i + 1];
if (left_child->key_count >= MIN_DEGREE) {
int pred = PRED(left_child);
sub_root->key[i] = pred;
node_delete(left_child, pred);
return;
}
else if (right_child->key_count >= MIN_DEGREE) {
int succ = SUCC(right_child);
sub_root->key[i] = succ;
node_delete(right_child, succ);
return;
} else {
merge_node(sub_root, left_child, right_child, i);
node_delete(left_child, k);
return;
}
return;
}
if (i == sub_root->key_count + 1) {
node *left_child = sub_root->linker[i - 1];
node *right_child = sub_root->linker[i];
if (sub_root->linker[i]->key_count >= MIN_DEGREE) {
node_delete(sub_root->linker[i], k);
return;
}
if (sub_root->linker[i - 1]->key_count >= MIN_DEGREE) {
move_key_left_to_right(left_child, right_child, &(sub_root->key[i - 1]));
node_delete(right_child, k);
return;
}
else {
merge_node(sub_root, left_child, right_child, i - 1);
node_delete(left_child, k);
return;
}
return;
}
else {
node *left_child = sub_root->linker[i];
node *right_child = sub_root->linker[i + 1];
if (sub_root->linker[i]->key_count >= MIN_DEGREE) {
node_delete(sub_root->linker[i], k);
return;
}
if (sub_root->linker[i + 1]->key_count >= MIN_DEGREE) {
move_key_right_to_left(left_child, right_child, &(sub_root->key[i]));
node_delete(left_child, k);
return;
}
else {
merge_node(sub_root, left_child, right_child, i);
node_delete(sub_root->linker[i], k);
return;
}
return;
}
}
次に、実装されたツリーを出力するコードを示します.DFSとして実装される.
void display(node *cur_node, int blanks) {
int i;
if (cur_node->key_count == 0) {
printf("tree is empty\n");
return;
}
if (cur_node->is_leaf) {
for (i = 1; i <= cur_node->key_count; i++) {
for (int j = 1; j <= blanks; j ++)
printf("---!");
printf("%d\n", cur_node->key[i]);
}
return;
}
for (i = 1; i <= cur_node->key_count; i++) {
display(cur_node->linker[i], blanks + 1);
for (int j = 1; j <= blanks; j ++)
printf("---!");
printf("%d\n", cur_node->key[i]);
}
display(cur_node->linker[i], blanks + 1);
return;
}
今週のテーマはとても面白くて、時間がどのように過ぎたのか分かりません.フィードバックを歓迎します.
-終了-
Reference
この問題について([C]B-TREE(3)), 我々は、より多くの情報をここで見つけました https://velog.io/@piopiop/C-B-TREE를-구현해보자-3-teuwua7fテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol