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

C++で前置記法(プレフィックス記法)の式から式木を構築するプログラム

式木(Expression Tree)は、数式を表現するために用いられる二分木の一種です。式木では、内部ノードが演算子に対応し、葉ノードがオペランド(被演算子)に対応します。この記事では、前置記法(プレフィックス記法)で与えられた式から式木を構築し、中間順(インオーダー)、前置順(プレオーダー)、後置順(ポストオーダー)の3種類の走査で出力するC++プログラムを紹介します。

式木とは?

例えば、前置記法の式「++7*626」は、次のような二分木として表現できます。

          +
        /   \
       +     6
      / \
     7   *
        / \
       6   2

根および内部ノードには演算子(+, -, *, / など)が配置され、その子としてオペランド(数字)が接続されます。同じ木でも、走査する順序を変えることで、前置記法・中間記法・後置記法それぞれの式を取り出せるのが大きな特徴です。

アルゴリズム

開始
    以下のメンバ関数を持つクラス ExpressionTree を定義する:

    関数 push():ノードをスタックに積む
    もしスタックが空なら
        ノードを最初の要素としてプッシュする
    そうでなければ
        ノードをプッシュし、それを先頭(top)にする

    関数 pop():ノードをスタックから取り出す
    もしスタックが空なら
        「アンダーフロー」を出力する
    そうでなければ
        ノードを取り出して返し、top を更新する

    関数 insert():文字を挿入する
    もし数字なら
        新しいノードを作成してプッシュする
    そうでなく演算子なら
        2つのノードをポップし、演算子ノードの子として連結してプッシュする
    それ以外なら
        「無効な式です」と出力する

    関数 postOrder():後置順走査
    木が空でなければ
        postOrder(ptr->l)
        postOrder(ptr->r)
        ptr->d を出力する

    関数 inOrder():中間順走査
    木が空でなければ
        inOrder(ptr->l)
        ptr->d を出力する
        inOrder(ptr->r)

    関数 preOrder():前置順走査
    木が空でなければ
        ptr->d を出力する
        preOrder(ptr->l)
        preOrder(ptr->r)
終了

C++サンプルコード

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
using namespace std;

class TreeN { // ノードの宣言
public:
    char d;
    TreeN *l, *r;
    TreeN(char d) {
        this->d = d;
        this->l = NULL;
        this->r = NULL;
    }
};

class StackNod { // スタックの宣言
public:
    TreeN *treeN;
    StackNod *n;
    StackNod(TreeN *treeN) { // コンストラクタ
        this->treeN = treeN;
        n = NULL;
    }
};

class ExpressionTree {
private:
    StackNod *top;
public:
    ExpressionTree() {
        top = NULL;
    }
    void clear() {
        top = NULL;
    }

    void push(TreeN *ptr) {
        if (top == NULL)
            top = new StackNod(ptr);
        else {
            StackNod *nptr = new StackNod(ptr);
            nptr->n = top;
            top = nptr;
        }
    }

    TreeN *pop() {
        if (top == NULL) {
            cout << "アンダーフロー" << endl;
        } else {
            TreeN *ptr = top->treeN;
            top = top->n;
            return ptr;
        }
    }

    TreeN *peek() {
        return top->treeN;
    }

    void insert(char val) {
        if (isDigit(val)) {
            TreeN *nptr = new TreeN(val);
            push(nptr);
        } else if (isOperator(val)) {
            TreeN *nptr = new TreeN(val);
            nptr->l = pop();
            nptr->r = pop();
            push(nptr);
        } else {
            cout << "無効な式です" << endl;
            return;
        }
    }

    bool isDigit(char ch) {
        return ch >= '0' && ch <= '9';
    }

    bool isOperator(char ch) {
        return ch == '+' || ch == '-' || ch == '*' || ch == '/';
    }

    int toDigit(char ch) {
        return ch - '0';
    }

    void buildTree(string eqn) {
        // 文字列を右端から左端へ向かって1文字ずつ処理する
        for (int i = eqn.length() - 1; i >= 0; i--)
            insert(eqn[i]);
    }

    void postfix() {
        postOrder(peek());
    }

    void postOrder(TreeN *ptr) {
        if (ptr != NULL) {
            postOrder(ptr->l);
            postOrder(ptr->r);
            cout << ptr->d;
        }
    }

    void infix() {
        inOrder(peek());
    }

    void inOrder(TreeN *ptr) {
        if (ptr != NULL) {
            inOrder(ptr->l);
            cout << ptr->d;
            inOrder(ptr->r);
        }
    }

    void prefix() {
        preOrder(peek());
    }

    void preOrder(TreeN *ptr) {
        if (ptr != NULL) {
            cout << ptr->d;
            preOrder(ptr->l);
            preOrder(ptr->r);
        }
    }
};

int main() {
    string s;
    ExpressionTree et;
    cout << "\n前置記法で式を入力してください: ";
    cin >> s;
    et.buildTree(s);
    cout << "\nPrefix(前置順): ";
    et.prefix();
    cout << "\n\nInfix(中間順): ";
    et.infix();
    cout << "\n\nPostfix(後置順): ";
    et.postfix();
}

実行結果

前置記法で式を入力してください: ++7*626
Prefix(前置順): ++7*626
Infix(中間順): 7+6*2+6
Postfix(後置順): 762*+6+

プログラムのポイント

  • 右から左へ処理する: 前置記法の式は演算子が先頭に来るため、buildTree() では文字列を末尾(右端)から先頭(左端)へ向かって1文字ずつ読み込みます。
  • スタックで部分木を管理: 数字はそのままノードとしてスタックにプッシュされ、演算子を読み込んだ時点で2つのノードをポップして子として連結し、完成した部分木を再びスタックに戻します。
  • 1つの木から3つの記法を取得: 走査の順序(根を先に訪問するか・後に訪問するか)を変えるだけで、前置・中間・後置の各式が得られます。

このように、スタックと再帰的な木の走査を組み合わせることで、前置記法の式を効率的に解析し、さまざまな形式の式へ変換することができます。コンパイラや電卓アプリなど、数式を扱うプログラムの基礎となる重要なテクニックなので、ぜひマスターしておきましょう。

  1. 二分法を用いて方程式の根を求めるC++プログラム

    関数f(x)と2つの数a、bが与えられ、f(a)・f(b)<0を満たし、関数f(x)が区間[a, b]内に存在するとします。ここでの課題は、二分法(バイセクション法)を用いて、関数f(x)の区間aとbの間に存在する根の値を求めることです。 二分法とは? 二分法とは、「a」と「b」で定義された範囲内において、関数f(x)の根の値を求めるための数値計算手法の一つです。関数の根とは、その値を代入したときにf(x)=0となるような値xのことです。 例 方程式 F(x) = x^3 − 8 を考える この方程式は、x = 2 のとき F(x) = 2^3 − 8 = 0 となります。 したがって

  2. Pythonで式ツリー(式木)を構築する方法:後置記法の式から式木を作るプログラム

    式ツリー(Expression Tree)とは、二分木の一種で、葉ノードには演算の対象となる値(オペランド)が格納され、内部ノードには演算子が格納されるデータ構造です。 例:「4 + ((7 + 9) * 2)」という式は、次のような式ツリーで表現できます。 問題を解くためのアプローチ 与えられた式から式ツリーを構築する際には、一般的にスタックというデータ構造を使用します。まず、与えられた後置記法(ポストフィックス記法)の式を走査しながら、以下の手順を実行していきます。 式の中にオペランドが現れた場合は、それをノードとして作成し、スタックにプッシュします。 演算子が現れた場合は、その演算