C++で二分木を双方向リンクリストに変換する方法(セット1)
はじめに
このチュートリアルでは、二分木(バイナリツリー)を双方向リンクリスト(ダブリーリンクリスト)に変換するC++プログラムについて解説します。
変換対象の二分木が与えられ、それを双方向リンクリストへ変換するのが課題です。具体的には、二分木の各ノードが持つ左ポインタ(left)と右ポインタ(right)を、リンクリストの前ポインタ(prev)と次ポインタ(next)としてそのまま再利用します。さらに、変換後のリンクリストのノードの並び順は、元の二分木の通り順(中順・inorder)トラバーサルと一致していなければなりません。
アプローチ
この問題には非常にシンプルな手法が使えます。二分木を通り順で走査しながら、同時に双方向リンクリストのノードを構築していきます。走査の過程で、各ノードのleftポインタを直前に訪問したノード(prev)に、rightポインタを次に訪問するノードに設定することで、自然にリンクリストが完成します。
アルゴリズムの手順
- ノードがNULLの場合は何もせずに戻る。
- 再帰的に左部分木を変換する。
- prevがNULLの場合、現在のノードはリストの先頭(head)となる。
- prevがNULLでない場合は、現在のノードのleftをprevに、prevのrightを現在のノードに設定して双方向にリンクを張る。
- prevを現在のノードに更新する。
- 再帰的に右部分木を変換する。
実装例
#include <iostream>
using namespace std;
//二分木のノード構造体
struct node{
int data;
node* left;
node* right;
};
//走査しながら双方向リンクリストのノードを構築する
void binarytodll(node *root, node **head){
if (root == NULL)
return;
static node* prev = NULL;
//左部分木を変換
binarytodll(root->left, head);
if (prev == NULL)
*head = root;
else {
root->left = prev;
prev->right = root;
}
prev = root;
//右部分木を変換
binarytodll(root->right, head);
}
//新しいノードを生成する
node* newNode(int data) {
node* new_node = new node;
new_node->data = data;
new_node->left = new_node->right = NULL;
return (new_node);
}
//双方向リンクリストのノードを出力する
void print_dll(node *node){
while (node!=NULL) {
cout << node->data << " ";
node = node->right;
}
}
int main(){
node *root = newNode(10);
root->left = newNode(12);
root->right = newNode(15);
root->left->left = newNode(25);
root->left->right = newNode(30);
root->right->left = newNode(36);
node *head = NULL;
binarytodll(root, &head);
print_dll(head);
return 0;
}
出力
25 12 30 10 36 15
コードのポイント
この実装の鍵となっているのは、関数内で宣言されたstatic変数のprevです。通り順トラバーサルでは直前に訪問したノードが常に分かるため、このprevを使って現在のノードと前のノードを双方向にリンクできます。最初に訪問されるノード(最も左側のノード)はprevがNULLの状態で処理されるため、リストの先頭(head)として記録されます。
サンプルコードでは、10を根とする二分木を構築しています。通り順トラバーサルの結果である「25 12 30 10 36 15」が、そのまま双方向リンクリストの並び順になります。
計算量については、すべてのノードを一度ずつ訪問するため時間計算量はO(n)、再帰呼び出しのスタック深さは木の高さに依存するため空間計算量はO(h)(hは木の高さ)となります。
-
C++で二分木をリンクリストにフラット化(平坦化)する方法
二分木が与えられたとき、それをその場(in-place)でリンクリストへフラット化(平坦化)することを考えます。具体的には、すべてのノードを右ポインタで連結し、左ポインタを null にした、一本の連結リストのような構造へ変換します。例えば、次のような二分木があるとします。これをフラット化すると、出力は次のようになります。アルゴリズムの手順この問題は、逆後順走査(右 → 左 → 根)を利用することで効率的に解けます。手順は以下の通りです。prev を null で初期化します。ルートを引数にとる再帰関数 solve() を定義します。root が null の場合は、そのまま戻ります。まず r
-
C++で二分木がSumTree(総和木)かどうかを判定する方法
ここでは、与えられた二分木が「SumTree(総和木)」であるかどうかを判定する方法を解説します。まずは、SumTreeとはどのような木なのかを確認しておきましょう。 SumTreeとは SumTreeとは、すべての内部ノードが「左の子と右の子の値の合計」を保持する特殊な二分木です。木の根(ルート)には、それより下位に存在する全要素の合計値が格納されます。なお、葉ノードのみからなる木や空の木も、定義上はSumTreeとみなされます。以下はSumTreeの一例です。 例えば上図の木では、根の値26が左部分木(10 + 4 + 6 = 20)と右部分木(3 + 3 = 6)の合計と一致しており