C++
 Computer >> コンピューター >  >> プログラミング >> C++

【C++】二分探索木の回転操作を徹底解説!AVL木のバランス調整プログラム

二分探索木(BST)とは

二分探索木(Binary Search Tree:BST)は、すべてのノードが次の性質を満たすように整列された二分木です。

  • 右部分木の条件: ノードの右部分木に含まれるキーは、必ずその親ノードのキーより大きい。
  • 左部分木の条件: ノードの左部分木に含まれるキーは、必ずその親ノードのキー以下である。
  • 子の数の制限: 各ノードが持てる子は最大2つまで。

木の回転(Tree Rotation)とは

木の回転とは、二分木の要素の順序(ソート順)を崩すことなく木の構造だけを変更する操作です。回転を行うと、あるノードが1段階上へ移動し、別のノードが1段階下へ移動します。

この操作は主に次の目的で使用されます。

  • 木の形状を変えて、高さを低く抑える
  • 小さい部分木を下へ、大きい部分木を上へ移動することで、探索・挿入などの各種操作のパフォーマンスを向上させる

回転の方向(左回転か右回転か)は、ノードがどちら側にシフトされるか、言い換えればどの子が根(ルート)の位置に来るかによって決まります。

本記事では、C++を使って二分探索木に対する回転操作(AVL木としての自己平衡化を含む)を実行するプログラムを紹介します。

アルゴリズム

Begin
    構造体 avl を作成し、データ d、左ポインタ l、右ポインタ r を宣言する。
    クラス avl_tree を宣言し、以下の関数を定義する:
    height()     :max関数を使って木の高さを計算する。
    difference() :左右の部分木の高さの差(バランス係数)を計算する。
    rr_rotat()   :右右(Right-Right)回転を行う。
    ll_rotat()   :左左(Left-Left)回転を行う。
    lr_rotat()   :左右(Left-Right)回転を行う。
    rl_rotat()   :右左(Right-Left)回転を行う。
    balance()    :バランス係数を取得して木を均衡化する。
                   差を bal_factor に格納し、
                   bal_factor > 1 なら左部分木をバランスさせ、
                   bal_factor < -1 なら右部分木をバランスさせる。
    insert()     :木に要素を挿入する。
    show()       :木の構造を表示する。
    inorder()    :中間順走査(Inorder)の結果を表示する。
    preorder()   :先行順走査(Preorder)の結果を表示する。
    postorder()  :後行順走査(Postorder)の結果を表示する。
    main() 内では switch 文により選択肢に応じて各関数を呼び出す。
End.

C++サンプルコード

以下は、AVL木として4種類の回転(LL・RR・LR・RL)を実装し、挿入時に自動的にバランスを保つ完全なサンプルプログラムです。

#include<iostream>
#include<cstdio>
#include<sstream>
#include<algorithm>
#define pow2(n) (1 << (n))
using namespace std;
struct avl {
    int d;
    struct avl *l;
    struct avl *r;
}*r;
class avl_tree {
    public:
        int height(avl *);
        int difference(avl *);
        avl *rr_rotat(avl *);
        avl *ll_rotat(avl *);
        avl *lr_rotat(avl*);
        avl *rl_rotat(avl *);
        avl * balance(avl *);
        avl * insert(avl*, int);
        void show(avl*, int);
        void inorder(avl *);
        void preorder(avl *);
        void postorder(avl*);
        avl_tree() {
            r = NULL;
        }
};
int avl_tree::height(avl *t) {
    int h = 0;
    if (t != NULL) {
        int l_height = height(t->l);
        int r_height = height(t->r);
        int max_height = max(l_height, r_height);
        h = max_height + 1;
    }
    return h;
}
int avl_tree::difference(avl *t) {
    int l_height = height(t->l);
    int r_height = height(t->r);
    int b_factor = l_height - r_height;
    return b_factor;
}
avl *avl_tree::rr_rotat(avl *parent) {
    avl *t;
    t = parent->r;
    parent->r = t->l;
    t->l = parent;
    cout<<"Right-Right Rotation";
    return t;
}
avl *avl_tree::ll_rotat(avl *parent) {
    avl *t;
    t = parent->l;
    parent->l = t->r;
    t->r = parent;
    cout<<"Left-Left Rotation";
    return t;
}
avl *avl_tree::lr_rotat(avl *parent) {
    avl *t;
    t = parent->l;
    parent->l = rr_rotat(t);
    cout<<"Left-Right Rotation";
    return ll_rotat(parent);
}
avl *avl_tree::rl_rotat(avl *parent) {
    avl *t;
    t= parent->r;
    parent->r = ll_rotat(t);
    cout<<"Right-Left Rotation";
    return rr_rotat(parent);
}
avl *avl_tree::balance(avl *t) {
    int bal_factor = difference(t);
    if (bal_factor > 1) {
       if (difference(t->l) > 0)
           t = ll_rotat(t);
       else
           t = lr_rotat(t);
    }
    else if (bal_factor < -1) {
       if (difference(t->r) > 0)
           t= rl_rotat(t);
       else
           t = rr_rotat(t);
    }
    return t;
}
avl *avl_tree::insert(avl *r, int v) {
    if (r == NULL) {
       r= new avl;
       r->d = v;
       r->l = NULL;
       r->r= NULL;
       return r;
    }
    else if (v< r->d) {
       r->l= insert(r->l, v);
       r = balance(r);
    }
    else if (v >= r->d) {
       r->r= insert(r->r, v);
       r = balance(r);
    }
    return r;
}
void avl_tree::show(avl *p, int l) {
    int i;
    if (p != NULL) {
       show(p->r, l+ 1);
       cout<<" ";
       if (p == r)
          cout << "Root -> ";
       for (i = 0; i < l&& p != r; i++)
          cout << " ";
       cout << p->d;
       show(p->l, l + 1);
    }
}
void avl_tree::inorder(avl *t) {
    if (t == NULL)
       return;
    inorder(t->l);
    cout << t->d << " ";
    inorder(t->r);
}
void avl_tree::preorder(avl *t) {
    if (t == NULL)
       return;
    cout << t->d << " ";
    preorder(t->l);
    preorder(t->r);
}
void avl_tree::postorder(avl *t) {
    if (t == NULL)
       return;
    postorder(t ->l);
    postorder(t ->r);
    cout << t->d << " ";
}
int main() {
    int c, i;
    avl_tree avl;
    while (1) {
       cout << "1.Insert Element into the tree" << endl;
       cout << "2.show Balanced AVL Tree" << endl;
       cout << "3.InOrder traversal" << endl;
       cout << "4.PreOrder traversal" << endl;
       cout << "5.PostOrder traversal" << endl;
       cout << "6.Exit" << endl;
       cout << "Enter your Choice: ";
       cin >> c;
       switch (c) {
          case 1:
            cout << "Enter value to be inserted: ";
            cin >> i;
            r= avl.insert(r, i);
         break;
          case 2:
            if (r == NULL) {
               cout << "Tree is Empty" << endl;
               continue;
            }
            cout << "Balanced AVL Tree:" << endl;
            avl.show(r, 1);
            cout<<endl;
         break;
          case 3:
            cout << "Inorder Traversal:" << endl;
            avl.inorder(r);
            cout << endl;
         break;
          case 4:
            cout << "Preorder Traversal:" << endl;
            avl.preorder(r);
            cout << endl;
         break;
          case 5:
            cout << "Postorder Traversal:" << endl;
            avl.postorder(r);
            cout << endl;
         break;
          case 6:
            exit(1);
         break;
          default:
          cout << "Wrong Choice" << endl;
       }
    }
    return 0;
}

実行結果

このプログラムを実行すると、メニュー形式で操作を選択できます。値を挿入すると、必要に応じて自動的に回転(LL回転やRR回転など)が実行され、木のバランスが保たれます。以下は実際の実行例です。

1.Insert Element into the tree
2.show Balanced AVL Tree
3.InOrder traversal
4.PreOrder traversal
5.PostOrder traversal
6.Exit
Enter your Choice: 1
Enter value to be inserted: 13
...
Enter your Choice: 1
Enter value to be inserted: 4
Left-Left Rotation
...
Enter your Choice: 3
Inorder Traversal:
4 5 8 10 11 13 15 16
Enter your Choice: 4
Preorder Traversal:
10 5 4 8 13 11 15 16
Enter your Choice: 5
Postorder Traversal:
4 8 5 11 16 15 13 10
...
Enter your Choice: 1
Enter value to be inserted: 52
Right-Right Rotation
...
Enter your Choice: 6

まとめ

本プログラムでは、二分探索木の基本的な性質を保ちながら、AVL木の手法を用いて挿入時に自動的に回転処理を行うことで、木の偏りを防ぎ、常にバランスの取れた状態を維持しています。これにより、最悪の場合でも O(log n) の計算量で探索・挿入操作が可能となり、データ構造として高い性能を発揮します。

  1. C++でAVL木(AVLツリー)を実装する方法:回転操作とサンプルコードを徹底解説

    AVL木とは AVL木(AVL Tree)は、自己平衡型二分探索木(Self-balancing Binary Search Tree)の一種です。すべてのノードにおいて、左部分木と右部分木の高さの差が「1以下」に保たれるという性質を持っています。この平衡条件により、木が片側に偏って成長することを防ぎ、検索・挿入・削除といった操作を常に効率的(O(log n))に行うことができます。 木の回転(Tree Rotation)とは 木の回転とは、要素の順序(ソート順)を崩すことなく木の構造を変更する操作のことです。あるノードを一段上へ移動させ、別のノードを一段下へ移動させることで実現されます。 回

  2. C++で二分探索木(AVL木)の左回転を実装するプログラム

    二分探索木とは二分探索木(Binary Search Tree)とは、すべてのノードが次の性質を満たすソート済みの二分木です。ノードの右部分木には、親ノードのキーより大きいキーがすべて格納されるノードの左部分木には、親ノードのキーより小さいキーがすべて格納される各ノードが持てる子ノードは最大2つまで木の回転(Tree Rotation)とは木の回転とは、二分木の要素の順序(ソート順)を崩すことなく木の構造を変更する操作です。回転では、あるノードを1つ上へ、別のノードを1つ下へ移動させます。回転は木の形状を変えるために使われ、小さな部分木を下へ、大きな部分木を上へ移動することで木の高さを抑えられ