C++で木(ツリー)のプリューファーコードを生成するプログラム
プリューファーコード(Prüfer Code)は、ラベル付きの木を一意な数列として表現できる符号化手法です。頂点に1からpまでのラベルが付けられた木(頂点数はユーザーが指定)を入力すると、長さ「p − 2」の数列が生成され、これによってその木を一意に識別できます。
プリューファーコードとは
n個のラベル付き頂点からなる木は、必ず長さ n−2 のプリューファーコードへ変換でき、逆にコードから元の木を復元することも可能です。この性質から、木の構造をコンパクトに扱う手段として広く知られており、有名なケイリーの公式(「n頂点のラベル付き木の総数は nn−2 通り」)の証明にも利用されています。
アルゴリズムの流れ
基本となる考え方は以下のとおりです。
- 各頂点の次数(接続されている辺の本数)を計算します。
- 次数が1の頂点(葉)の中から、ラベル番号が最小のものを探します。
- その頂点を木から取り除き、隣接していた頂点の番号をプリューファーコードに追加します。
- 頂点が2つ残るまで、この操作を繰り返します。
疑似コード
開始
i, j, ver, edg, minimum, p を整数型として宣言する
「頂点の数を入力してください」と表示する
ver の値を入力する
edg = ver - 1 で初期化する
EDG[edg][2] と DG[ver+1] を整数型として宣言し、DG[ver+1] = {0} で初期化する
「この木は ○ 個の頂点に対して ○ 本の辺を持ちます」と表示する
「入力が必要な頂点ペアは ○ 組です」と表示する
for(i = 0; i < edg; i++)
辺ごとの頂点ペアの入力を促すメッセージを表示する
V(1) の値を EDG[i][0] へ、V(2) の値を EDG[i][1] へ入力する
DG[EDG[i][0]]++ と DG[EDG[i][1]]++ で両頂点の次数を増やす
「この木のプリューファーコードは: { 」と表示する
for(i = 0; i < ver-2; i++)
minimum = 10000 で初期化する
for(j = 0; j < edg; j++)
DG[EDG[j][0]] == 1 ならば、minimum > EDG[j][0] のとき minimum と p を更新
DG[EDG[j][1]] == 1 ならば、minimum > EDG[j][1] のとき minimum と p を更新
DG[EDG[p][0]]-- と DG[EDG[p][1]]-- で選択した辺を削除する
DG[EDG[p][0]] == 0 ならば EDG[p][1] を出力、それ以外は EDG[p][0] を出力する
「}」と表示する
終了
C++による実装例
#include<iostream>
using namespace std;
int main() {
int i, j, ver, edg, minimum, p;
cout<<"頂点の数を入力してください: ";
cin>>ver;
cout<<endl;
edg = ver-1;
int EDG[edg][2], DG[ver+1] = {0};
cout<<"この木は "<<ver<<" 個の頂点に対して "<<edg<<" 本の辺を持ちます。\n";
cout<<"入力が必要な頂点ペアは "<<edg<<" 組です。\n";
for(i = 0; i < edg; i++) {
cout<<"辺 "<<i+1<<" の頂点ペアを入力してください:\n";
cout<<"V(1) の値: ";
cin>>EDG[i][0];
cout<<"V(2) の値: ";
cin>>EDG[i][1];
DG[EDG[i][0]]++; // 片方の頂点の次数を増加
DG[EDG[i][1]]++; // もう片方の頂点の次数を増加
}
cout<<"\nこの木のプリューファーコードは: { "; // 与えられた木のプリューファーコードを出力
for(i = 0; i < ver-2; i++) {
minimum = 10000;
for(j = 0; j < edg; j++) {
if(DG[EDG[j][0]] == 1) { // 次数が1の頂点(葉)を探す
if(minimum > EDG[j][0]) {
minimum = EDG[j][0];
p = j;
}
}
if(DG[EDG[j][1]] == 1) {
if(minimum > EDG[j][1]) {
minimum = EDG[j][1];
p = j;
}
}
}
DG[EDG[p][0]]--; // 選択した頂点を削除するため、次数を減らしていく
DG[EDG[p][1]]--; // 辺を削除したため、隣接頂点の次数も減らす
if(DG[EDG[p][0]] == 0)
cout<<EDG[p][1]<<" ";
else
cout<<EDG[p][0]<<" ";
}
cout<<"}";
return 0;
}
実行例
頂点の数を入力してください: 5
この木は 5 個の頂点に対して 4 本の辺を持ちます。
入力が必要な頂点ペアは 4 組です。
辺 1 の頂点ペアを入力してください:
V(1) の値: 2
V(2) の値: 3
辺 2 の頂点ペアを入力してください:
V(1) の値: 5
V(2) の値: 6
辺 3 の頂点ペアを入力してください:
V(1) の値: 7
V(2) の値: 8
辺 4 の頂点ペアを入力してください:
V(1) の値: 9
V(2) の値: 10
この木のプリューファーコードは: { 4 8 4 }
処理のポイント
このプログラムでは、配列 DG[] で各頂点の次数を管理し、配列 EDG[][] で辺(頂点ペア)を保持しています。毎回のループで「次数が1かつラベルが最小の頂点」を選び出し、その頂点を削除しながら隣接頂点の番号を出力することで、プリューファーコードを順に構築していきます。計算量は O(n²) となり、頂点数がそこまで多くない場合には十分に実用的です。
-
C++で木構造の祖先・子孫関係を判定するクエリ処理の実装方法
問題概要 頂点数 N の木と、Q 個のクエリが与えられます。各クエリは 2 つの整数 i と j から構成され、木の中でノード i がノード j の祖先にあたるかどうかを判定するのが課題です。 具体例で動きを確認してみましょう。 入力 Q = 2, query[][] = {{3, 5}, {1, 6}} 出力 No Yes 解説 i = 3, j = 5 : ノード 3 はノード 5 の祖先ではない → No i = 1, j = 6 : ノード 1 はノード 6 の祖先である → Yes 解法のアプローチ 最も単純な方法は、DFS(深さ優先探索)によってノード i からすべての子孫をた
-
sin(x)とcos(x)の値を計算するC++プログラムの解説
sin(x)とcos(x)の値を計算するC++プログラム 本記事では、角度を入力として受け取り、その角度に対応するsin(x)(正弦)とcos(x)(余弦)の値を計算して結果を表示するC++プログラムを解説します。ライブラリ関数に頼らず、テイラー展開(マクローリン展開)を用いて数値を近似する手法を紹介します。 sin(x)とは sin(x)は三角関数の一つで、角度xに対する正弦の値を求めるために使用されます。直角三角形では、斜辺に対する対辺の比として定義されます。 $$\sin (x) = \displaystyle\sum\limits_{k=0}^\infty \frac{(-1)^{k