C++でバイナリ行列の重複行を効率的に検出する方法(Trie活用)
2値行列(バイナリ行列)を想定してみましょう。本記事では、行列の中から重複する行を効率よく見つける方法を解説します。例として、次のような行列を考えます。
| 1 | 1 | 0 | 1 | 0 | 1 |
| 0 | 0 | 1 | 0 | 0 | 1 |
| 1 | 0 | 1 | 1 | 0 | 0 |
| 1 | 1 | 0 | 1 | 0 | 1 |
| 0 | 0 | 1 | 0 | 0 | 1 |
| 0 | 0 | 1 | 0 | 0 | 1 |
この行列では、0始まりで数えて位置3、4、5の行が互いに重複しています(1行目と4行目、2行目・5行目・6行目がそれぞれ同じ内容です)。
解決のアプローチ:Trie(トライ木)を活用する
この問題を解くには、Trie(トライ木)と呼ばれるデータ構造を利用します。Trieは、扱う値の種類が少ないデータの格納と検索に適した効率的な構造で、検索の計算量はキーの長さに対してほぼ最適になります。バイナリ行列の場合、各ノードが持つ子は「0」と「1」の2つだけなので、Trieとの相性は抜群です。
アルゴリズムの流れはシンプルです。まず空のTrieを用意し、行列の各行を先頭から順に挿入していきます。挿入の際に、その行がすでにTrie内に登録済みであれば、その行は重複行であると判定できます。この方法なら行列全体を1回走査するだけで済み、時間計算量はO(行数×列数)に収まります。
サンプルコード
#include<iostream>
using namespace std;
const int MAX = 100;
class Trie {
public:
bool leaf_node;
Trie* children[2];
};
Trie* getNode() {
Trie* node = new Trie;
node->children[0] = node->children[1] = NULL;
node->leaf_node = false;
return node;
}
bool insert(Trie*& head, bool* arr, int N) {
Trie* curr = head;
for (int i = 0; i < N; i++) {
if (curr->children[arr[i]] == NULL)
curr->children[arr[i]] = getNode();
curr = curr->children[arr[i]];
}
if (curr->leaf_node)
return false;
return (curr->leaf_node = true);
}
void displayDuplicateRows(bool matrix[][MAX], int M, int N) {
Trie* head = getNode();
for (int i = 0; i < M; i++)
if (!insert(head, matrix[i], N))
cout << "重複する行の位置: " << i << endl;
}
int main() {
bool mat[][MAX] = {
{1, 1, 0, 1, 0, 1},
{0, 0, 1, 0, 0, 1},
{1, 0, 1, 1, 0, 0},
{1, 1, 0, 1, 0, 1},
{0, 0, 1, 0, 0, 1},
{0, 0, 1, 0, 0, 1},
};
displayDuplicateRows(mat, 6, 6);
}
出力
重複する行の位置: 3 重複する行の位置: 4 重複する行の位置: 5
計算量のまとめ
この手法の時間計算量はO(M×N)(Mは行数、Nは列数)で、空間計算量も最悪の場合O(M×N)となります。各行を文字列化してソートやハッシュ集合で比較する方法でも同じ結果は得られますが、Trieを使えば挿入しながら重複を即座に検出できるため、1回の走査で処理が完結する点が大きな利点です。
-
C++で二分木の重複する部分木を検出する方法
問題の概要二分木が与えられたとき、その中に存在する重複する部分木(duplicate subtrees)をすべて見つける問題を考えてみましょう。ここでいう「重複」とは、構造とノードの値が完全に一致する部分木が2つ以上存在することを意味します。各種類の重複部分木について、代表としてどれか1つの根ノードを返せばよいことになっています。たとえば、次のような二分木があるとします。この木に含まれる重複する部分木は、以下の2つです。値 4 を持つ単一ノードの部分木(2か所に出現)根が 2 で、子に 4 を持つ部分木(2か所に出現)解法のアプローチ:部分木のシリアライズこの問題を効率的に解く鍵となるのは、部
-
C++で二分木内の重複するサブツリーをすべて検出する方法
問題の概要二分木が与えられたとき、その中に重複するサブツリー(部分木)が存在するかどうかを判定する問題を考えてみましょう。例として、次のような二分木を取り上げます。この木には、サイズ2の同一のサブツリーが2つ存在します。さらに、それぞれのサブツリー内のDに注目すると、BDとBEもまた重複するサブツリーになっています。解決のアプローチ:木のシリアライズとハッシュこの問題は、木のシリアライズ(直列化)とハッシュテーブルを組み合わせることで効率的に解決できます。基本的な考え方は以下のとおりです。各サブツリーを間順走査(inorder traversal)で文字列としてシリアライズする空のノードには開