前順序、後順序による二分木のスレッド化
a. 問題分析
二分木の前順序スレッディングを実装する必要があります。スレッディングとは、二分リンクリストの空ポインタ領域を、特定の走査順序における前駆ノードまたは後続ノードを指すように変更する方法です。これにより、前順序、中順序、または後順序のいずれかのノードから開始することができ、根ノードからだけではなくなります。
b. アルゴリズム設計
私たちのアルゴリズムはまず二分木を作成し、その後それをスレッド化します。スレッド化のプロセスは再帰関数によって実装され、この関数は各ノードを走査し、その左右の子ノードが存在するかどうかをチェックします。存在しない場合、その左/右ポインタを前/次のノードに指向させます。最後に、スレッド化が成功したかどうかを確認するため、中間順走査を行います。
以下はスレッド化のコードスニペットです:
// スレッドを作成する関数
void createThread(Node* p) {
if (p == NULL) { // ノードが空の場合、直接戻る
return;
}
createThread(p->left); // 左部分木を再帰的に処理
if (!p->left) { // 左子ノードが空の場合、左ポインタを前のノードに指向させ、左スレッドフラグを1に設定
p->left = pre;
p->ltag = 1;
}
if (pre && !pre->right) { // 前のノードの右子ノードが空の場合、その右ポインタを現在のノードに指向させ、右スレッドフラグを1に設定
pre->right = p;
pre->rtag = 1;
}
pre = p; // 前のノードを更新
createThread(p->right); // 右部分木を再帰的に処理
}
c. データ構造設計
二分木のノードを表現するために、構造体を使用します。この構造体には、データフィールドと2つのポインタフィールドが含まれており、それぞれ左子ノードと右子ノードを指します。さらに、左右のポインタがスレッド化されているかどうかを示す2つのマークフィールドを追加しました。
以下はデータ構造のコードスニペットです:
// 二分木ノードの構造体を定義
struct Node {
int data; // ノードのデータ
Node* left; // 左子ノード
Node* right; // 右子ノード
int ltag, rtag; // 左右スレッドマーク
};
d. デバッグプロセス
まず二分木を作成し、それをスレッド化します。その後、中順走査を行い、スレッド化が成功したかどうかを確認します。走査結果が期待通りの結果と一致すれば、スレッド化は成功したと判断できます。
以下はデバッグプロセスのコードスニペットです:
int main() {
// 二分木を作成
Node n1, n2, n3, n4, n5;
n1 = {1, &n2, &n3, 0, 0};
n2 = {2, &n4, &n5, 0, 0};
n3 = {3, NULL, NULL, 0, 0};
n4 = {4, NULL, NULL, 0, 0};
n5 = {5, NULL, NULL, 0, 0};
// 二分木をスレッド化
createThread(&n1);
// スレッド化された二分木を中順走査
inOrder(&n1);
return 0;
}
e. 出力結果
プログラムの出力結果は二分木の中間順走査結果であるべきです。この例では、出力結果は 4 2 5 1 3 となるはずです。作成した二分木の中間順走査結果がこのシーケンスになります。
f. ソースコード
#include<iostream>
using namespace std;
// 二分木ノードの構造体を定義
struct Node {
int data; // ノードのデータ
Node* left; // 左子ノード
Node* right; // 右子ノード
int ltag, rtag; // 左右のスレッドマーク
};
// 前のノードを保存するためのグローバル変数preを定義
Node* pre = NULL;
// スレッドを作成する関数
void createThread(Node* p) {
if (p == NULL) { // ノードが空の場合、直接戻る
return;
}
createThread(p->left); // 左部分木を再帰的に処理
if (!p->left) { // 左子ノードが空の場合、左ポインタを前のノードに指向し、左スレッドマークを1に設定
p->left = pre;
p->ltag = 1;
}
if (pre && !pre->right) { // 前のノードの右子ノードが空の場合、その右ポインタを現在のノードに指向し、右スレッドマークを1に設定
pre->right = p;
pre->rtag = 1;
}
pre = p; // 前のノードを更新
createThread(p->right); // 右部分木を再帰的に処理
}
// スレッド二分木を中順走査する関数
void inOrder(Node* p) {
while (p) {
while (!p->ltag) { // 最も左のノードを見つける
p = p->left;
}
cout << p->data << " "; // ノードデータを出力
while (p->rtag) { // 右ポインタがスレッドの場合、直接後続ノードにジャンプ
p = p->right;
cout << p->data << " ";
}
p = p->right; // 右部分木を処理
}
}
int main() {
// 二分木を作成
Node n1, n2, n3, n4, n5;
n1 = {1, &n2, &n3, 0, 0};
n2 = {2, &n4, &n5, 0, 0};
n3 = {3, NULL, NULL, 0, 0};
n4 = {4, NULL, NULL, 0, 0};
n5 = {5, NULL, NULL, 0, 0};
// 二分木をスレッド化
createThread(&n1);
// スレッド二分木を中順走査
inOrder(&n1);
return 0;
}
グラフの隣接行列と隣接リストの記憶
a. 問題分析
私たちの目標は、C++でグラフの隣接行列と隣接リストの保存を実装することです。これには、配列(隣接行列用)とリンクリスト(隣接リスト用)という2つの異なるデータ構造が関わってきます。
b. アルゴリズム設計
隣接行列
二次元配列adj[MAX][MAX]を使用してグラフの隣接行列を格納します。各要素adj[i][j]は、ノードiからノードjへの辺が存在するかどうかを示します。存在する場合、adj[i][j] = 1、そうでない場合はadj[i][j] = 0となります。
for (i = 1; i <= max_edges; i++) {
cin >> origin >> destin;
if ((origin == -1) && (destin == -1))
break;
if (origin >= n || destin >= n || origin < 0 || destin < 0) {
i--;
} else {
adj[origin][destin] = 1;
}
}
隣接リスト
連結リストの配列list<int> *adjを使用してグラフの隣接リストを格納します。各要素adj[i]は、ノードiに隣接するすべてのノードを格納する連結リストです。
void Graph::addEdge(int v, int w) {
adj[v].push_back(w);
}
c. データ構造設計
隣接行列
二次元配列int adj[MAX][MAX]を使用して隣接行列を格納します。MAXはグラフ内のノードの最大数です。
隣接リスト
リンクリストの配列list<int> *adjを使用して隣接リストを格納します。Vはグラフ内のノードの数です。
d. デバッグプロセス
コードの実装とデバッグの過程で、まず入力された辺が有効であることを確認しました。入力された辺が無効な場合(例えば、存在しないノードを参照している場合)、ユーザーに通知し、再入力を促します。
隣接行列または隣接リストに辺を追加する際には、配列やリンクリストの範囲外インデックスにアクセスしようとしないように、エラーチェックを使用しました。
e. 出力結果
最後に、隣接行列または隣接リストを出力して、コードが正しいかどうかを確認できます。隣接リストの場合、各ノードのリンクリストを走査し、すべての隣接ノードを出力します。
void Graph::printGraph() {
for (int v = 0; v < V; v++) {
cout << "\n Adjacency list of vertex " << v << "\n head ";
for (auto x : adj[v])
cout << "-> " << x;
printf("\n");
}
}
f. ソースコード
隣接行列による格納
#include<iostream>
#define MAX 20
using namespace std;
int adj[MAX][MAX];
int n;
void create_graph() {
int i, max_edges, origin, destin;
cout << "Enter number of nodes : ";
cin >> n;
max_edges = n * (n - 1);
for (i = 1; i <= max_edges; i++) {
cout << "Enter edge " << i << " (-1 -1 to quit) : ";
cin >> origin >> destin;
if ((origin == -1) && (destin == -1))
break;
if (origin >= n || destin >= n || origin < 0 || destin < 0) {
cout << "Invalid edge!\n";
i--;
} else {
adj[origin][destin] = 1;
}
}
}
隣接リストによる格納
#include<iostream>
#include<list>
using namespace std;
class Graph {
int V;
list<int> *adj;
public:
Graph(int V);
void addEdge(int v, int w);
void printGraph();
};
Graph::Graph(int V) {
this->V = V;
adj = new list<int>[V];
}
void Graph::addEdge(int v, int w) {
adj[v].push_back(w);
}
void Graph::printGraph() {
for (int v = 0; v < V; v++) {
cout << "\n Adjacency list of vertex " << v << "\n head ";
for (auto x : adj[v])
cout << "-> " << x;
printf("\n");
}
}

いつまた一杯の酒を飲み、細かい論文を議論するのか。