中序式轉後序式

December 12, 2021

人們平常使用的運算式,是將運算元放在運算子兩旁,例如 a + b / d 這樣的式子,這稱為中序(infix)表示式;然而電腦剖析運算式時,為了有效率地判斷運算的順序,可將中序表示式轉換為後序(postfix)或前序(prefix)表示式。

解法思路

後序表示式又稱為逆向波蘭表示式(reverse polish notation),是由波蘭的數學家盧卡謝維奇提出,例如 (a + b) * (c + d),表示為後序表示式時是 a b + c d + *。

想以人工轉換計算後序式的話,可以使用括號法,將運算子兩旁的運算元,依先後順序全部括號起來,然後將右括號取代為左邊最接近的運算子(從最內層括號開始),最後去掉全部的左括號就可以完成。例如:

  1. a + b * d + c / d
  2. ((a + (b * d)) + (c / d))
  3. a b d * + c d / +

另一個方式是堆疊法,使用迴圈取出中序式的元素,遇運算元直接輸出,遇到運算子與左括號進行堆疊,堆疊中運算子優先順序若大於等於讀入的運算子優先順序,直接輸出堆疊中的運算子,再將讀入的運算子置入堆疊;遇右括號輸出堆疊中的運算子至左括號。

以下是虛擬碼的運算法,\0 表示中序式讀取完畢:

Procedure Postfix(infix) [
    Loop [
        op = infix(i) 
        case [
            :x = '\0': 
                while (stack not empty) 
                     // output all elements in stack 
                end 
                return 
             :x = '(': 
                 // put it into stack 
             :x is operator: 
                  while (priority(stack[top]) >= 
                         priority(op)) [
                       // out a element from stack 
                  ]
                  // save op into stack 
             :x = ')': 
                   while ( stack(top) != '(' ) [
                       // out a element from stack 
                   ]
                   top = top - 1  // not out '( 
             :else: 
                   // output current op 
        ]
        i++; 
    ]
] 

例如 (a + b) * (c + d),依演算法的輸出過程如下:

元素 堆疊 輸出
( ( -
a ( a
+ (+ a
b (+ ab
) - ab+
* * ab+
( *( ab+
c *( ab+c
+ *(+ ab+c
d *(+ ab+cd
) * ab+cd+
- - ab+cd+*

若要用堆疊法將中序式轉為前序式,使用迴圈由後往前取出中序式的字元,遇運算元直接輸出;遇運算子與右括號進行堆疊;堆疊中運算子優先順序若大於讀入的運算子優先順序,直接輸出堆疊中的運算子,再將讀入的運算子置入堆疊;遇左括號輸出堆疊中的運算子至右括號。

程式實作

#include <stdio.h> 
#include <stdlib.h> 

#define MAX 80

void inToPostfix(char*, char*); // 中序轉後序 
int priority(char); // 運算子優先權

int main(void) { 
    char infix[MAX] = {'\0'}; 
    char postfix[MAX]= {'\0'}; 

    printf("中序運算式:"); 
    scanf("%s", infix); 
    inToPostfix(infix, postfix);
    
    int i;
    for(i = 0; postfix[i] != '\0'; i++) {
        printf("%c", postfix[i]); 
    }

    return 0; 
} 

void inToPostfix(char* infix, char* postfix) { 
    char stack[MAX] = {'\0'};
    int i, j, top;
    for(i = 0, j = 0, top = 0; infix[i] != '\0'; i++) switch(infix[i]) { 
        case '(':              // 運算子堆疊 
            stack[++top] = infix[i]; 
            break; 
        case '+': case '-': case '*': case '/': 
            while(priority(stack[top]) >= priority(infix[i])) { 
                postfix[j++] = stack[top--];
            } 
            stack[++top] = infix[i]; // 存入堆疊 
            break; 
        case ')': 
            while(stack[top] != '(') { // 遇 ) 輸出至 ( 
                postfix[j++] = stack[top--];
            } 
            top--;  // 不輸出 ( 
            break; 
        default:  // 運算元直接輸出 
            postfix[j++] = infix[i];
    }
    while(top > 0) { 
        postfix[j++] = stack[top--];
    }
} 

int priority(char op) { 
    switch(op) { 
        case '+': case '-': return 1;
        case '*': case '/': return 2;
        default:            return 0;
    } 
} 
import java.util.*;
import static java.lang.System.out;

public class Infix {
    private static int priority(char c) {
        return c == '+' || c == '-' ? 1 : c == '*' || c == '/' ? 2 : 0;
    }
    
    private static String toPostfix(String infix, boolean isPost) {
        String expr = isPost ? 
            infix : new StringBuffer(infix).reverse().toString();
        LinkedList<Character> stack = new LinkedList<>();
        StringBuilder output = new StringBuilder();
        char toStack = isPost ? '(' : ')';
        char toOutput = isPost ? ')' : '(';
        for(char c : expr.toCharArray()) {
            if(c == toStack) { stack.add(c); }
            else if("+-*/".indexOf(c) != -1) {
                while(!stack.isEmpty() && 
                       priority(stack.getLast()) >= priority(c)) { 
                    output.append(stack.removeLast());
                } 
                stack.add(c);
            }
            else if(c == toOutput) {
                while(stack.getLast() != toStack) { 
                    output.append(stack.removeLast());
                } 
                stack.removeLast();
            }
            else { output.append(c); }
        }
        
        while(!stack.isEmpty()) { output.append(stack.removeLast()); }
        
        return isPost ? output.toString() : output.reverse().toString();
    }
    
    public static String toPostfix(String infix) {
        return toPostfix(infix, true);
    }
    
    public static String toPrefix(String infix) {
        return toPostfix(infix, false);
    }
    
    public static void main(String[] args) {
        String infix = "(a+b)*(c+d)";
        out.println(toPostfix(infix));
        out.println(toPrefix(infix));
    }
}  
def priority(op):
    return 1 if op in "+-" else 2 if op in "*/" else 0
    
def toPostfix(infix, isPost = True):
    toStack, toOutput = ('(', ')') if isPost else (')', '(')
    
    def procOpt(c, stack, output):
        if stack == "" or priority(stack[-1]) < priority(c):
            return (stack + c, output)
        else:
            return procOpt(c, stack[0:-1], output + stack[-1])
    
    def procPhs(stack, output):
        if stack[-1] == toStack:
            return (stack[0:-1], output)
        else:
            return procPhs(stack[0:-1], output + stack[-1])
            
    def procExpr(expr, stack = "", output = ""):
        if expr == "":
            return output + stack[::-1]
        elif expr[0] == toStack:
            return procExpr(expr[1:], stack + expr[0], output)
        elif expr[0] in "+-*/":
            return procExpr(expr[1:], *procOpt(expr[0], stack, output))
        elif expr[0] == toOutput:
            return procExpr(expr[1:], *procPhs(stack, output))
        else:
            return procExpr(expr[1:], stack, output + expr[0])
    
    output = procExpr(infix if isPost else infix[::-1])
    return output if isPost else output[::-1]

def toPrefix(infix):
    return toPostfix(infix, False)
    
infix = "(a+b)*(c+d)"
print(toPostfix(infix))
print(toPrefix(infix))
def priority(op: Char) = op match {
    case '+' | '-' => 1
    case '*' | '/' => 2
    case _         => 0
}

def toPostfix(infix: String, isPost: Boolean = true) = {
    val (toStack, toOutput) = if(isPost) ('(', ')') else (')', '(')
    
    def procOpt(c: Char, stack: String, output: String): (String, String) = {
        if(stack == "" || priority(stack.last) < priority(c)) {
            (stack + c, output)
        } else procOpt(c, stack.init, output + stack.last)
    }
    
    def procPhs(stack: String, output: String): (String, String) = {
        if(stack.last == toStack) (stack.init, output)
        else procPhs(stack.init, output + stack.last)
    }
    
    def procExpr(expr: String, so: (String, String) = ("", "")): String = {
        val (stack, output) = so
        if(expr == "") { 
            output + stack.reverse
        } else if(expr.head == toStack) {
            procExpr(expr.tail, (stack + expr.head, output))
        } else if("+-*/".contains(expr.head)) {
            procExpr(expr.tail, procOpt(expr.head, stack, output))
        } else if(expr.head == toOutput) { 
            procExpr(expr.tail, procPhs(stack, output))
        } else procExpr(expr.tail, (stack, output + expr.head))
    }
    
    val output = procExpr(if(isPost) infix else infix.reverse)
    if(isPost) output else output.reverse
}
    
def toPrefix(infix: String) = toPostfix(infix, false)

val infix = "(a+b)*(c+d)"
println(toPostfix(infix))
println(toPrefix(infix))
def priority(op)
    "+-".include?(op) ? 1 : "*/".include?(op) ? 2 : 0
end

def toPostfix(infix, isPost = true)
    toStack, toOutput = isPost ? ['(', ')'] : [')', '(']
    
    procOpt = ->(c, stack, output) {
        if stack == "" || priority(stack[-1]) < priority(c)
            [stack + c, output]
        else
            procOpt.call(c, stack[0...-1], output + stack[-1])
        end
    }
    
    procPhs = ->(stack, output) {
        if stack[-1] == toStack
            [stack[0...-1], output]
        else
            procPhs.call(stack[0...-1], output + stack[-1])
        end
    }
    
    procExpr = ->(expr, stack = "", output = "") {
        if expr == ""
            output + stack.reverse
        elsif expr[0] == toStack
            procExpr.call(expr[1..-1], stack + expr[0], output)
        elsif "+-*/".include? expr[0]
            procExpr.call(expr[1..-1], *procOpt.call(expr[0], stack, output))
        elsif expr[0] == toOutput
            procExpr.call(expr[1..-1], *procPhs.call(stack, output))
        else
            procExpr.call(expr[1..-1], stack, output + expr[0])
        end
    }
    
    output = procExpr.call(isPost ? infix : infix.reverse)
    isPost ? output : output.reverse
end

def toPrefix(infix)
    toPostfix(infix, false)
end
    
infix = "(a+b)*(c+d)"
puts(toPostfix(infix))
puts(toPrefix(infix))
Array.prototype.getLast = function() {
    return this[this.length - 1];
};

Array.prototype.isEmpty = function() {
    return this.length == 0;
};

function priority(c) {
    return c === '+' || c === '-' ? 1 : c === '*' || c === '/' ? 2 : 0;
}

function toPostfix(infix, isPost) {
    isPost = isPost === undefined ? true : false;
    var expr = isPost ? infix.split('') : infix.split('').reverse();
    var stack = [];
    var output = [];
    var toStack = isPost ? '(' : ')';
    var toOutput = isPost ? ')' : '(';
    expr.forEach(function(c) {
        if(c === toStack) { stack.push(c); }
        else if('+-*/'.indexOf(c) !== -1) {
            while(!stack.isEmpty() && 
                   priority(stack.getLast()) >= priority(c)) {
                output.push(stack.pop());
            }
            stack.push(c);
        }
        else if(c === toOutput) {
            while(stack.getLast() !== toStack) {
                output.push(stack.pop());
            }
            stack.pop();
        }
        else { output.push(c); }
    });
    
    while(!stack.isEmpty()) { output.push(stack.pop()); }
    
    return isPost ? output.join('') : output.reverse().join('');
}

function toPrefix(infix) {
    return toPostfix(infix, false);
}

var infix = "(a+b)*(c+d)";
print(toPostfix(infix));
print(toPrefix(infix));
priority op = if op `elem` "+-" then 1
              else if op `elem` "*/" then 2
              else 0
    
toPfix inf isPost = if isPost then output else reverse output
    where
        (toStack, toOutput) = if isPost then ('(', ')') else (')', '(')
        output = procExpr (if isPost then inf else reverse inf) ("", "")
        procOpt c stack output =        
            if stack == "" || (priority $ last stack) < priority c 
                then (stack ++ [c], output)
            else procOpt c (init stack) (output ++ [last stack])
        procPhs stack output =
            if last stack == toStack then (init stack, output)
            else procPhs (init stack) (output ++ [last stack])
        procExpr expr so =
            if expr == "" 
                then output ++ (reverse stack)
            else if head == toStack 
                then procExpr tail (stack ++ [head], output)
            else if head `elem` "+-*/" 
                then procExpr tail $ procOpt head stack output
            else if head == toOutput 
                then procExpr tail $ procPhs stack output
            else procExpr tail (stack, output ++ [head])
            where (stack, output) = so
                  head:tail = expr
                  
toPostfix inf = toPfix inf True
toPrefix inf = toPfix inf False

main = do
    let inf = "(a+b)*(c+d)"
    putStrLn $ toPostfix inf
    putStrLn $ toPrefix inf