応用情報技術者 2013年 春期 午後 問02
一般的な表記法の数式を逆ポーランド表記法に変換するアルゴリズムに関する次の記述を読んで、設問1〜3に答えよ。
逆ポーランド表記法とは、演算子を二つの演算対象の後ろに配置することによって、数式を表現する表記法である。例えば、一般的な表記法の数式 1+2×3 を、逆ポーランド表記法では 1 2 3 × + と表記する。
逆ポーランド表記法で表記した数式は、数値や演算子を左から順に処理すればよく、括弧を使う必要もないので、コンピュータが数式を取り扱うのに都合が良い。
一般的な表記法の数式から逆ポーランド表記法への変換は、スタックを用いることで容易に実現できる。
〔逆ポーランド表記法への変換アルゴリズム〕
一般的な表記法の数式を逆ポーランド表記法に変換するアルゴリズムでは、まず変換前の数式を、数値、演算子及び括弧の要素(以下、演算要素という)に分解する。演算子には二項演算子 +、−、×、÷ を用い、括弧には “ ( ” と “ ) ” を用いる。数値は 0〜9 の 1 桁の数とする。それぞれの演算要素には、優先度を定義する。
変換の処理には、変換前配列、スタック及び変換後配列を用いる。初期状態では、変換前配列には変換前の数式の演算要素が順に入っており、スタックと変換後配列は空の状態である。変換後には、変換後配列に逆ポーランド表記法に変換した結果が入る。例えば、変換前の数式が 1+2×3 の場合、初期状態は表1のようになる。

逆ポーランド表記法への変換は、次の(1)〜(4)の手順で行う。
(1) 変換前配列の先頭から順に、演算要素を1個参照する。
参照する演算要素がない場合は(4)に進む。
(2) スタック上に演算要素がある場合は、スタックの先頭にある演算要素の優先度を参照し、(1)の演算要素の優先度以上なら、スタックの先頭演算要素をポップし、変換後配列の末尾に追加する。これを、スタックの先頭の演算要素の優先度が、(1)の演算要素の優先度未満になるまで繰り返す。ただし、スタックの先頭の演算要素が“ ( ”の場合は、そこで繰り返しを終了する。
(3) (1)で参照した演算要素が“ ) ”なら、それを破棄し、その際スタックの先頭にあるはずの“ ( ”もポップして破棄した後 (1)に戻る。(1)で参照した演算要素が“ ) ”以外なら、その演算要素をスタックにプッシュし、(1)に戻る。
(4) スタック上にある全ての演算要素を順番にポップし、変換後配列の末尾に追加する。
演算要素の優先度を表2に、数式 1+2×3 を変換するときの処理過程を図1に示す。なお、図1中の丸で囲った演算要素は、(1)の手順で参照した演算要素である。


〔逆ポーランド表記法への変換プログラム〕
逆ポーランド表記法への変換プログラムを作成した。プログラム中で使用する主な変数、配列及び関数を表3に、作成したプログラムを図2に示す。
図2のプログラムでは、スタックの取扱いを容易にするために、ダミーの演算要素nullを定義し、プログラム開始直後にスタックにプッシュしている。
nullがスタックからポップされることがないようにするために、その優先度をオと定義する。こうすることで、プログラム中、手順②の処理を行う部分でカの判定処理を記述する必要がなくなり、プログラムが簡潔になる。


〔エラーチェックの追加〕
図2のプログラムの①の箇所において、i が 2 以上のとき、GetElement(i−1) の優先度と、GetElement(i) の優先度を比較することによって、簡易的な入力エラーチェックを追加することができる。例えば、数値の演算要素が 2 個以上連続する場合や、“ ) ” の直後に “ ( ” が続く場合など、四則演算の式として不正なものがあった場合はエラーとする。
入力エラーとする条件を表4に示す。GetElement(i−1) の優先度を、GetElement(i)の優先度について、表4に従って評価をした結果、“OK”の場合は、そのまま処理を続行する。評価した結果が“Err”の場合は、入力された数式が誤っていると判断して処理を中断する。

設問1:逆ポーランド表記法への変換について、(1)、(2)に答えよ。
(1)数式(2+3)×4を逆ポーランド表記法に変換した結果を答えよ。
模範解答
23+4×
解説
解答の論理構成
-
演算要素に分解
対象式は “( 2 + 3 ) × 4”。【問題文】の「まず変換前の数式を、数値、演算子及び括弧の要素…に分解する」という説明に従い、
① “(” ② “2” ③ “+” ④ “3” ⑤ “)” ⑥ “×” ⑦ “4”
の 7 要素となります。 -
優先度の確認
【問題文】表2「演算要素の優先度」によれば
“(” は 5、数値は 4、“+” は ア=「+」、イ=「−」で優先度 3、“×” は ウ=「×」、エ=「÷」で優先度 2、“)” は 1 です。 -
スタック操作 (手順(1)〜(4))
・“(” をスタックにプッシュ。
・“2” は数値なので直接変換後配列へ → 2
・“+” はスタック先頭が “(” で優先度が低いためプッシュ。
・“3” は数値なので変換後配列へ → 2 3
・“)” が来たら手順(3)で “)” を破棄し、“(” もポップして破棄。スタックには何も残りません。
・“×” をプッシュ。
・“4” は数値なので変換後配列へ → 2 3 4
・入力終了後、手順(4)でスタックの “×” をポップし変換後配列末尾へ追加。 -
完成した逆ポーランド表記
変換後配列は左から “2 3 + 4 ×”。空白を詰めて「23+4×」と書くのが慣例です。
誤りやすいポイント
- “(” と “)” は結果に出力しない
優先度処理だけに使い、最終式には残りません。 - “×” と “+” の優先度を逆に覚える
【問題文】表2で“+”は 3、“×”は 2。「数が小さい方が低い」ので注意。 - スタックを空にするタイミング
入力が終わっても必ず手順(4)でポップし切ることが必要です。
FAQ
Q: 数値が複数桁の場合は同じ手順ですか?
A: はい。数値を 1 要素として扱えば手順(1)〜(4)はそのまま適用できます。
A: はい。数値を 1 要素として扱えば手順(1)〜(4)はそのまま適用できます。
Q: “-” (単項マイナス) はどう処理しますか?
A: 本問題は二項演算子のみを想定しています。単項演算子を扱う場合は優先度を追加定義し、手順(2)の比較条件に組み込む必要があります。
A: 本問題は二項演算子のみを想定しています。単項演算子を扱う場合は優先度を追加定義し、手順(2)の比較条件に組み込む必要があります。
Q: スタックにダミー要素 “null” を入れる理由は?
A: 【問題文】「nullがスタックからポップされることがないように…プログラムが簡潔になる」とある通り、スタック空判定の特別処理を避けるためです。
A: 【問題文】「nullがスタックからポップされることがないように…プログラムが簡潔になる」とある通り、スタック空判定の特別処理を避けるためです。
関連キーワード: スタック, 逆ポーランド表記, 演算子優先度, 二分演算子, 式解析
設問1:逆ポーランド表記法への変換について、(1)、(2)に答えよ。
(2)表2及び表4中のア〜エに入れる適切な二項演算子を、+、−、×、÷の中から一つずつ選んで、表を完成させよ(アとイは順不同、ウとエは順不同)。
模範解答
ア:×
イ:÷
ウ:+
エ:−
解説
解答の論理構成
-
演算子集合の確認
【問題文】には「演算子には二項演算子 +、−、×、÷ を用い」とあります。したがって空欄 ア〜エ にはこの4つを重複なく配置します。 -
優先度テーブルの読み取り
【問題文】の「表2 演算要素の優先度」より、空欄は次の2段階に分かれています。
・優先度 3 … ア, イ
・優先度 2 … ウ, エ
同じ表には「注記 値が大きいほど優先度が高い。」と記載されています。 -
一般的な四則演算の規則との対応づけ
一般に「×」「÷」は「+」「−」より高い優先度を持ちます。これは【問題文】冒頭の例「一般的な表記法の数式 1+2×3 を、逆ポーランド表記法では 1 2 3 × + と表記する」からも読み取れます。
・先に「×」を評価するためにスタックに残り、後から「+」が出力されている=「×」のほうが高優先度。 -
優先度 3 と優先度 2 への割当
よって、優先度 3 には「×」「÷」、優先度 2 には「+」「−」を入れれば演算規則と矛盾しません。
「アとイは順不同、ウとエは順不同」とあるため、具体的な配置は
ア:× イ:÷ ウ:+ エ:−
で成立します。
誤りやすいポイント
- 「値が大きいほど優先度が高い」という注記を読み飛ばし、数字が小さい方が高いと思い込む。
- 「+」「−」と「×」「÷」の4つすべてに異なる優先度があると誤解し、5段階に分けようとする。
- 例「1+2×3」の動作を確認せず、直感だけで割当て順を決定してしまう。
- 「÷」の優先度を「−」と同じにしてしまい、変換テストをするとスタック制御が破綻する。
FAQ
Q: 「×」と「÷」の優先度は同じで良いのですか?
A: はい。四則演算では乗算と除算は同一レベルで評価します。したがって表では両方とも優先度 3 に割り当てます。
A: はい。四則演算では乗算と除算は同一レベルで評価します。したがって表では両方とも優先度 3 に割り当てます。
Q: 「+」と「−」の優先度も同じにする根拠は?
A: 加算と減算は互いに逆演算の関係であり、標準的な算術規則で同一レベルに分類されます。そのため表ではともに優先度 2 に置きます。
A: 加算と減算は互いに逆演算の関係であり、標準的な算術規則で同一レベルに分類されます。そのため表ではともに優先度 2 に置きます。
Q: 優先度が同じ演算子同士の結合順序(左結合/右結合)は考慮しなくて良い?
A: 今回のアルゴリズムはスタックから「優先度以上」でポップすると規定しているため、同じ優先度なら左側(先に現れた方)が先に出力されます。これは四則演算の左結合規則に一致します。
A: 今回のアルゴリズムはスタックから「優先度以上」でポップすると規定しているため、同じ優先度なら左側(先に現れた方)が先に出力されます。これは四則演算の左結合規則に一致します。
関連キーワード: インフィクス記法, オペランドスタック, オペレータ優先度, スタックアルゴリズム
設問2:〔逆ポーランド表記法への変換プログラム〕について、(1)〜(3)に答えよ。
(1)本文中のオに入れる適切な数値を答えよ。
模範解答
オ:0
解説
解答の論理構成
- 問題文には「nullがスタックからポップされることがないようにするために、その優先度をオと定義する。」とあります。
- スタックから要素をポップする条件は、アルゴリズム手順(2)の説明と同じくプログラム中でも「スタックの先頭にある演算要素の優先度 ≥ 現在処理中の演算要素の優先度」のときです。
- 表2によれば既存の優先度の最小値は “)” の「1」。したがって null を決してポップさせないためには、null の優先度を「1」よりも小さく設定すればよいことになります。
- 非負整数でかつ最小値にしたいので「0」が最適です。
- 以上より、オ には「0」が入ります。
誤りやすいポイント
- null の役割を「ダミー要素」ではなく「番兵」として理解しないと、優先度を大きくしてしまう誤答が生じやすいです。
- 「1 未満にすれば安全」と覚えながらも負の値を想定する受験者がいますが、GetPriority は「非負の整数」を返す仕様なので負値は選択肢に入りません。
- 表2の優先度を暗記していても、「値が大きいほど優先度が高い」という注記を読み落とし、大小関係を逆に判断するミスも頻発します。
FAQ
Q: null をスタックに最初から入れる利点は何ですか?
A: スタックが常に少なくとも 1 要素を持つ状態になるため、空スタック例外の判定処理を書かずに済み、プログラムが簡潔になります。
A: スタックが常に少なくとも 1 要素を持つ状態になるため、空スタック例外の判定処理を書かずに済み、プログラムが簡潔になります。
Q: null の優先度を「0」以外の正の値にしても、最後に while で取り除くので問題ないのでは?
A: 途中の手順(2)で「≥」比較を行うため、null の優先度が大きいと途中でポップされ、変換後配列に混入してしまいます。最小値「0」に設定することが安全策です。
A: 途中の手順(2)で「≥」比較を行うため、null の優先度が大きいと途中でポップされ、変換後配列に混入してしまいます。最小値「0」に設定することが安全策です。
Q: 負の値を使ってはダメですか?
A: GetPriority の戻り値は「非負の整数」と定義されています。負数は仕様違反になるため使用できません。
A: GetPriority の戻り値は「非負の整数」と定義されています。負数は仕様違反になるため使用できません。
関連キーワード: スタック, 優先度比較, 番兵, 逆ポーランド変換
設問2:〔逆ポーランド表記法への変換プログラム〕について、(1)〜(3)に答えよ。
(2)本文中のカに入れる適切な字句を20字以内で答えよ。
模範解答
カ:「スタック上に演算要素があるか否か」
または
「スタックが空であるか否か」
解説
解答の論理構成
- 【問題文】には次の記述があります。
「プログラム中で…ダミーの演算要素 null を定義し、プログラム開始直後にスタックにプッシュしている。
null がスタックからポップされることがないようにするために、その優先度を オ と定義する。こうすることで、プログラム中、手順②の処理を行う部分で カ の判定処理を記述する必要がなくなり…」 - 手順②は【逆ポーランド表記法への変換アルゴリズム】で
「(2) スタック上に演算要素がある場合は…」
と説明されており、通常はスタックが空かどうかを判定してからトップ要素を参照します。 - しかし null をあらかじめプッシュしておけば、スタックが空になるケースは存在しません。
その結果、手順②で「スタックが空かどうか」を毎回判定するコードが不要になります。 - よって カ は
「スタック上に演算要素があるか否か」
(同義語「スタックが空であるか否か」でも可)
が正答となります。
誤りやすいポイント
- ダミー要素 null の目的を「演算子優先度の比較を簡単にするため」とだけ覚え、スタック空判定の省略につながっている事実を見落とす。
- 手順②を「演算子AとBの優先度比較」と短絡的に理解し、そもそも“スタックが空のときトップ要素は存在しない”という前提を忘れる。
- 「優先度が最も高いからポップされない」という部分だけに注目し、空判定を省けるロジック全体を説明できない。
FAQ
Q: null の優先度 オ はいくつに設定されていますか?
A: 【表2】では最大値が 5 ですが、それよりも大きい値(例えば 6 以上)が オ に設定されています。こうすることで他のいかなる演算要素よりも高優先度となり、ポップ対象になりません。
A: 【表2】では最大値が 5 ですが、それよりも大きい値(例えば 6 以上)が オ に設定されています。こうすることで他のいかなる演算要素よりも高優先度となり、ポップ対象になりません。
Q: null を最後の while でポップしないのはなぜですか?
A: 最後の while ループは「キ が null でない間繰り返す」と条件付けされており、null に到達した時点でループを終了するためです。null は結果配列に出力されません。
A: 最後の while ループは「キ が null でない間繰り返す」と条件付けされており、null に到達した時点でループを終了するためです。null は結果配列に出力されません。
Q: 実装を配列ではなくリンクリストで書き換える場合、null は不要ですか?
A: データ構造を変えても「スタックが空にならない」という利点は共通なので、ダミー要素を入れておく設計は有効です。ただし空判定を安全に行えるなら省略も可能です。
A: データ構造を変えても「スタックが空にならない」という利点は共通なので、ダミー要素を入れておく設計は有効です。ただし空判定を安全に行えるなら省略も可能です。
関連キーワード: スタック, 逆ポーランド表記法, 演算子優先度, ダミー要素, 中置記法
設問2:〔逆ポーランド表記法への変換プログラム〕について、(1)〜(3)に答えよ。
(3)図2中のキ〜ケに入れる適切な字句を答えよ。
模範解答
キ:stack[stackCount]
ク:GetPriority(stackTop)がelementPriority以上
ケ:GetElement(i)が“)”である
解説
解答の論理構成
-
【問題文】の手順(2)は
“スタックの先頭にある演算要素の優先度を参照し、(1)の演算要素の優先度以上なら、スタックの先頭演算要素をポップし…”
と指示しています。コードではスタック先頭を変数 stackTop に取り込みます。先頭要素は配列 stack[] の末尾、すなわち添字 stackCount に格納されているので、stack[stackCount] が最適です。
➡︎ キ=stack[stackCount] -
手順(2)の繰返し条件は
“スタックの先頭の演算要素の優先度が、(1)の演算要素の優先度未満になるまで繰り返す”
です。すなわち「まだ優先度が高い(または同じ)ならポップを続行」という意味なので、コードの while 条件は
GetPriority(stackTop) が elementPriority 以上
となります。
➡︎ ク=GetPriority(stackTop)がelementPriority以上 -
手順(3)の冒頭には
“参照した演算要素が“ ) ”なら、それを破棄し…”
とあります。コード側の if 判定はこの条件に対応するため、
GetElement(i) が “)” である
が入ります。
➡︎ ケ=GetElement(i)が“)”である
以上より、
キ:stack[stackCount]
ク:GetPriority(stackTop)がelementPriority以上
ケ:GetElement(i)が“)”である
キ:stack[stackCount]
ク:GetPriority(stackTop)がelementPriority以上
ケ:GetElement(i)が“)”である
誤りやすいポイント
- スタックの先頭を stack[1] と勘違いする
- 本プログラムは「右端が先頭」方式です。stackCount が現在のトップであることを忘れやすいので注意。
- while 条件を「>」と書いてしまう
- 手順(2)は“以上ならポップ”なので「=」も含めて比較する必要があります。
- “ ) ” 判定を stackTop に対して行うミス
- 手順(3)はあくまで“(1)で参照した演算要素”の判定です。スタックトップではありません。
FAQ
Q: null を最初にプッシュする理由は何ですか?
A: スタックを空にする特別な判定を省くためです。null の優先度を最低にしておけば、誤ってポップされることがありません。
A: スタックを空にする特別な判定を省くためです。null の優先度を最低にしておけば、誤ってポップされることがありません。
Q: 優先度表で“ア〜エ”に具体的な演算子が示されていませんが影響しますか?
A: 本設問は変数名の補完が目的なので、具体的な演算子は必要ありません。優先度整数だけで処理できます。
A: 本設問は変数名の補完が目的なので、具体的な演算子は必要ありません。優先度整数だけで処理できます。
Q: “ ) ” を検出したらスタックから “ ( ” を取り除くのはなぜ?
A: 括弧は逆ポーランド表記に現れないためです。“ ( ” から “ ) ” までの演算子は既にポップ済みなので、括弧自体は破棄します。
A: 括弧は逆ポーランド表記に現れないためです。“ ( ” から “ ) ” までの演算子は既にポップ済みなので、括弧自体は破棄します。
関連キーワード: スタック, 優先度比較, 逆ポーランド記法, パーサ, アルゴリズム
設問3:
表4中のコ〜スに入れる適切な字句を答えよ。
模範解答
コ:Err
サ:OK
シ:OK
ス:Err
解説
解答の論理構成
- 【問題文】では、入力エラーの判定基準として「数値の演算要素が 2 個以上連続する場合」や「“ ) ” の直後に “ ( ” が続く場合」を例示しています。これは「同じ種類の演算要素が連続する」「演算子同士が連続する」など、数学的に項が不足するパターンを全て排除する方針だと分かります。
- 表4の行「4 (数値)」×列「4 (数値)」は “数値 → 数値” の並びです。先述の連続数値禁止より【コ】は Err となります。
- 同じ行の列「2 (ウ, エ)」は “数値 → 加減算演算子” の並びです。演算子が後ろに来るのは正しい式の形なので【サ】は OK になります。
- 行「3 (ア, イ)」×列「4 (数値)」は “乗除演算子 → 数値” の並びです。演算子の直後に数値が来るのは正常なので【シ】は OK です。
- 同じ行の列「2 (ウ, エ)」は “乗除演算子 → 加減算演算子” という “演算子 → 演算子” の並びに該当します。被演算子が欠けているため【ス】は Err になります。
以上より
コ:Err
サ:OK
シ:OK
ス:Err
コ:Err
サ:OK
シ:OK
ス:Err
誤りやすいポイント
- 「数値の後ろには必ず演算子か “ ) ” が来る」と覚えていないと、4 → 4 を誤って OK としてしまう。
- 「演算子の後ろには必ず数値か “ ( ” が来る」ルールを忘れると、演算子 → 演算子 のケースを見落としやすい。
- 優先度の大小に気を取られ、そもそもの“式としての並び”を確認し忘れると判断を誤る。
FAQ
Q: 優先度が高いか低いかはエラーチェックに影響しますか?
A: いいえ、表4では“並びの種類”がポイントです。同じ「演算子 → 演算子」でも優先度が違ってもエラーになります。
A: いいえ、表4では“並びの種類”がポイントです。同じ「演算子 → 演算子」でも優先度が違ってもエラーになります。
Q: “数値 → “)”” の並びは OK なのですか?
A: はい。閉じ括弧は直前の項をまとめる記号なので、“数値 → “)”” は正しい並びと判定しています(表4の該当セルは OK)。
A: はい。閉じ括弧は直前の項をまとめる記号なので、“数値 → “)”” は正しい並びと判定しています(表4の該当セルは OK)。
Q: “( → 数値” が OK なのはなぜ?
A: “(” は数値や “(” を開始できるので、“( → 数値” は通常の括弧開始として正しいため OK です。
A: “(” は数値や “(” を開始できるので、“( → 数値” は通常の括弧開始として正しいため OK です。
関連キーワード: 逆ポーランド, スタック, 演算子優先度, 構文解析, 入力検証


