天天看點

簡易電腦的實作

一.字首表達式

(一)介紹

      字首表達式又稱波蘭式,字首表達式的運算符位于操作數之前。

運算​規則:

      •對字首表達式進行從右至左依次掃描•當遇到數字時,将數字壓入堆棧,遇到運算符時,彈出棧頂的兩個數,用運算符對它們做相應的計算(棧頂元素 op 次頂元素),并将結果入棧

      •重複上述過程直到表達式最左端,最後運算得出的值即為表達式的結果

(二)運算分析

例如:中綴表達式 ( 2 + 3 ) × 4 - 5,采用字首表達式為:- × + 2 3 4 5

思路分析:

       •從右至左掃描,将5、4、3、2壓入堆棧•遇到 + 運算符,是以彈出 2 和 3( 2 為棧頂元素,3 為次頂元素,注意與字尾表達式做比較),計算出 2 + 3 的值,得 5,再将 5 入棧;•接下來是 × 運算符,是以彈出 5 和 4 ,計算出 5 × 4 = 20,将 20 入棧•最後是 - 運算符,計算出 20 - 5 的值,即 15,由此得出最終計算結果

中綴表達式轉為字首表達式:

轉換步驟如下:

•(1)初始化兩個棧:運算符棧 s1,儲存中間結果的棧 s2

•(2)從右至左掃描中綴表達式

•(3)遇到操作數時,将其壓入 s2

•(4)遇到運算符時,比較其與 s1 棧頂運算符的優先級

             •①:如果 s1 為空,或棧頂運算符為右括号 “ ) ”,則直接将此運算符入棧

             •②:否則,若優先級比棧頂運算符的較高或相等,也将運算符壓入 s1    

             •③:否則,将 s1 棧頂的運算符彈出并壓入到 s2 中,再次轉到 ( 4 - 1 ) 與 s1                    中新的棧頂運算符相比

     •(5)遇到括号時

           •①:如果是右括号“)”,則直接壓入 s1    

           •②:如果是左括号“(”,則依次彈出 s1 棧頂的運算符,并壓入 s2 ,直到遇到                          右括号為止,此時将這一對括号丢棄

      (6)重複步驟(2)至(5),直到表達式的最左邊••  

      (7)将 s1 中剩餘的運算符依次彈出并壓入 s2••

      (8)依次彈出 s2 中的元素并輸出,結果即為中綴表達式對應的字首表達式。

例如:中綴表達式 1 + (( 2 + 3 ) × 4) - 5 轉為字首表達式具體過程,如下圖

簡易電腦的實作

(三)具體代碼

package stack;

import javax.xml.transform.stream.StreamSource;

/**
 * @author Jin
 * @ Date 2022年07月2022/7/1日0:59
 * 字首表達式
 * 通過棧來實作簡單的運算器(可以進行加減乘除等基本運算)
 */
public class CalculatorStack {
    public static void main(String[] args) {
        String expression ="3+4*8-20";
        ArrayStack2 numStack=new ArrayStack2(10);
        //數棧,用來存儲運算數
        ArrayStack2 operStack=new ArrayStack2(10);
        //符棧,用來存儲運算符
        int index=0;
        int num1=0;
        int num2=0;
        int oper=0;
        int res=0;
        char ch=' ';
        String KeepNum="";
        //将每次掃描得到的字元儲存到ch
        //開始while循環的掃描expression
        while(true){
            //依次得到expression的每一個字元
            ch=expression.substring(index,index+1).charAt(0);
            //判斷ch是什麼,然後再進行相應操作
            if(operStack.isOper(ch)){
                //ch是運算符,判斷目前棧是否為空
                if(operStack.isEmpty()){
                    operStack.push(ch);
                }else{
                    //符号棧不為空
                    /*比較待插入棧中運算符的優先級<①>和符棧頂的運算符之間的優先級關系<②>
                    * (1)如果①的優先級小于等于②
                    *            從數棧中彈出兩個運算數,從符号棧中彈出棧頂運算符②,然後進行運算
                    *            将運算結果壓入數棧,将符号①壓入符棧
                    * (2)如果①的優先級大于②
                    *            将符号①壓入符号棧
                    * */
                    if(operStack.priority(ch)<= operStack.priority(operStack.getHead())){
                        num1=numStack.pop();
                        num2=numStack.pop();
                        oper=operStack.pop();
                        res=operStack.cal(num1,num2,oper);
                        numStack.push(res);
                        operStack.push(ch);
                        //将計算結果和符号分别壓入數棧和符号棧
                    }else{
                        operStack.push(ch);
                    }
                }
            }else{
               /* numStack.push(ch-48);  ---->  沒有考慮到多位數   */
                /**
                 * 多位數分析思路:
                 * (1)當處理多位數時,不能發現是一位數字就入棧
                 * (2)如果掃描的第一位是數字,再向後掃描一位,若是符号,掃描的第一位就入數字棧
                 * (3)如果第二位也是數字,使用變量字元串進行拼接,依次如此
                 * */
                //處理多位數
                 KeepNum += ch;
                 /*判斷ch是不是最後一個字元*/
                 if(index==expression.length()-1){
                     numStack.push(Integer.parseInt(KeepNum));
                 }else{
                        //判斷下一個字元是不是數字
                     if(operStack.isOper(expression.substring(index+1,index+2).charAt(0))){
                            numStack.push(Integer.parseInt(KeepNum));
                            KeepNum="";
                            //注意:一定要将KeepNum置空,否則會出現字元串越界問題
                    }
                 }
            }
            //讓 index+1,然後判斷是否掃描到expression最後
            index++;
            if(index>=expression.length()){
                break;
            }
        }
        //表達式掃描完畢,開始從兩個棧中開始取符号和數字進行運算
        while(true){
            //符号棧為空,則計算到最後的結果,數棧中隻有一個數字【結果】
            if(operStack.isEmpty()){
                break;
            }else{
                num1=numStack.pop();
                num2=numStack.pop();
                oper=operStack.pop();
                res=operStack.cal(num1,num2,oper);
                numStack.push(res);
            }
        }
        int last=numStack.pop();
        System.out.printf("表達式%s =%d",expression,last);
    }
}
/**建立一個棧*/
class ArrayStack2{
    private int maxSize;
    private int[]stack;
    private int top=-1;
    /**構造器*/
    public ArrayStack2(int maxSize){
        this.maxSize=maxSize;
        stack=new int[maxSize];
    }
    /**(1)判斷棧是否滿*/
    public boolean isFull(){
        return top==maxSize-1;
    }
    /**(2)判斷棧空*/
    public boolean isEmpty(){
        return top==-1;
    }
    /**(3)入棧(将資料壓入棧頂)*/
    public void push(int value){
        if(isFull()){
            System.out.println("棧已經滿,無法進行添加新元素!!");
        }else{
            top++;
            stack[top]=value;
        }
    }
    /**(4)出棧(将棧頂的元素取出)*/
    public int pop(){
        if(isEmpty()){
            throw new RuntimeException("棧空,沒有資料!");
        }else{
            int middle =stack[top];
            top--;
            return middle;
        }
    }
    /**(5)周遊棧中元素(從棧頂到棧底開始周遊)*/
    public void showStack(){
        if(isEmpty()){
            System.out.println("棧空,沒有資料~~");
            return;
        }
        for (int i = top; i >=0 ; i--) {
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }
    /**(6)檢視棧頂元素*/
    public int getHead(){
        return stack[top];
    }
    /**傳回運算符的優先級,優先級使用數字表示,數字越大,其優先級就越高*/
    public int priority(int oper){
        if(oper=='*'||oper=='/'){
            return 1;
        }else if(oper=='+'||oper=='-'){
            return 0;
        }else{
            return -1;
        }
    }
    /**判斷是不是運算符*/
    public boolean isOper(char val){
        return val=='*'||val=='/'||val=='+'||val=='-';
    }
    /**計算方法*/
    public int cal(int num1,int nums2,int oper){
       int res=0;
       //res用于存儲計算的結果
        switch(oper){
            case'+': res=num1+nums2 ;break;
            case'-': res=nums2-num1 ;break;
            case'*':res=num1*nums2;break;
            case'/':res=nums2/num1;break;
            default:break;
        }
        return res;
    }
}      

二.字尾表達式

(一)介紹

     字尾表達式又稱逆波蘭式,字尾表達式的運算符位于操作數之後。

規則:

       從左到右周遊表達式的每個數字和符号,遇到是數字就進棧,遇到是符号,就将處于棧頂兩個數字出棧,進行運算,運算結果進棧,一直到最終獲得結果。

 (二)運算分析

(三)具體代碼

package stack;

import java.util.*;
import java.util.Stack;
/**
 * @author Jin
 * @ Date 2022年07月2022/7/1日13:26
 * 功能:
 *   (1)将中綴表達式轉化為字尾表達式
 *   (2)求解字尾表達式
 */
public class PolandNotation {
    public static void main(String[] args) {
        /*完成一個将中綴表達式轉化為字尾表達式的功能
        * 具體實作步驟如下:
        *   (1)  1+((2+3)*4)-5   ->  1 2 3 + 4 * + 5 -
        *   (2) 為了便于操作,将中綴表達式字元串存儲在對應的list中(通過toInfixExpression()方法)
        *           即[1,+,(,(,2,+,3,),*,4,),-,5]
        *   (3) 将中綴表達式對應的list =>字尾表達式
        *   (4) 通過字尾表達式來進行運算出結果
        * */
        //①中綴表達式
        String expression ="1+((2+3)*4)-5";
        //②将中綴表達式轉化為清單
        List<String> infixExpressionList=toInfixExpression(expression);
        System.out.print("中綴表達式為:");
        System.out.println(infixExpressionList);
        //③将中綴表達式轉化為字尾表達式
        List<String> suffxiExpression=parsesuffiExpresionList(infixExpressionList);
        System.out.println("字尾表達式為:"+suffxiExpression);
        //④求解字尾表達式
        int rbs=calculate(suffxiExpression);
        //⑤輸出結果
        System.out.println("計算的結果是= "+rbs);

    }
    /**方法:将中綴表達式轉化為對應的list*/
        public static List<String> toInfixExpression(String s){
            //①定義一個List,存放中綴表達式對應的内容
            List<String> ls=new ArrayList<String>();
            int i=0;
            //指針,用來周遊中綴字元串中的字元
            String str;
            //完成對多位數的拼接
            char c;
            //将周遊到的每個字元放到  c 中
            do{
                //② c是一個字元,需要加入ls
                if((c=s.charAt(i))<48||(c=s.charAt(i))>57){
                    ls.add(""+c);
                    i++;
                }else{
                    /*③ 如果是一個數,還要考慮多位數*/
                    str="";
                    while(i<s.length()&&(c=s.charAt(i))>=48&&(c=s.charAt(i))<=57){
                        str+=c;
                        //拼接
                        i++;
                    }
                    ls.add(str);
                }

            }while(i<s.length());
            return ls;
        }
        /** 方法:将中綴表達式得到的list轉化為 字尾表達式對應的List */
       public static List<String> parsesuffiExpresionList(List<String> ls){
           /*①定義棧
           *   Stack<String> s2=new Stack<String>();
           * 說明:s2在裝轉化的過程中沒有pop()操作,而且到最後還要逆序輸出
           * 改進:可以使用清單來代替 棧 s2
           * */
           Stack<String> s1=new Stack<String>();
           List<String>  s2=new ArrayList<String>() ;
           //周遊ls
           for(String item:ls){
               if(item.matches("\\d+")){
                   s2.add(item);
               }else if(item.equals("(")){
                   s1.push(item);
               }else if(item.equals(")")){
                   /*處理辦法:依次彈出棧s1中的元素,并壓入s2中,直到遇到左括号為止*/
                   while(!(s1.peek().equals("("))){
                       s2.add(s1.pop());
                       //s1中彈出并壓入棧s2
                   }
                   s1.pop();
                   //将“( ”彈出
               }else{
                   /*
                   *符号優先級的比較:
                   * 規則如下:當item的優先級小于棧s1棧頂運算符的優先級時,将s1中的棧頂運算符彈出加入到s2
                   * */
                   while(s1.size()!=0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
                       s2.add(s1.pop());
                   }
                   //将item壓入棧中
                   s1.push(item);
               }
           }
           //将s1中剩餘内容依次加入到s2中
           while(s1.size()!=0){
               s2.add(s1.pop());
           }
           return s2;
       }

    /** 方法一:将一個逆波蘭表達式依次将資料和運算符放入到ArrayList中   */
    public static List<String> getListString(String suffixException) {
        //将 suffixException 分割(依據“ ”)
        String[] split=suffixException.split(" ");
        List<String>list=new ArrayList<String>();
        for(String ele:split){
            list.add(ele);
        }
        return list;
    }
    /**  方法二:完成對逆波蘭表達式的運算
     * 步驟如下:
     *      ①從左至右掃描,将3 和 4 壓入堆棧
     *      ②遇到 + 運算符,彈出 4 和 3 ,計算 3+4,結果為 7 存入棧中
     *      ③将5入棧
     *      ④下一個是 * 運算符,彈出 5 和 7,計算 5*7,将結果 35 存入棧中
     *      ⑤将6入棧
     *      ⑥最後一個是 - 運算符,彈出 6 和 35 ,計算 35-6,将結果29 存入棧中
     * */
    public static int calculate(List<String> ls) {
        //(1)建立一個棧
        Stack<String> stack = new Stack<String>();
        //(2)周遊 ls
        for (String item : ls) {
            //(3)使用正規表達式來取出多位
            if (item.matches("\\d+")) {
                stack.push(item);
            } else {
                //(4)為運算符,pop出兩個數,并進行運算,然後壓入棧
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")) {
                    res = num1 + num2;
                } else if (item.equals("-")) {
                    res = num1 - num2;
                } else if (item.equals("*")) {
                    res = num1 * num2;
                } else if (item.equals("/")) {
                    res = num1 / num2;
                } else {
                    throw new RuntimeException("運算符有誤");
                }
                //将最終的運算結果壓入棧中,然後将棧頂元素彈出并轉化整型
                stack.push("" + res);
            }
        }
        return Integer.parseInt(stack.pop());
    }
}
/**編寫一個類Operation 可以傳回一個運算符對應的優先級*/
class Operation{
    private static int ADD=1;
    private static int SUB=1;
    private static int MUL=2;
    private static int DIV=2;
    /**寫一個方法,傳回對應的優先級數字*/
    public static int getValue(String operation){
        int result=0;
        switch(operation){
            case"+":
                result=ADD;
                break;
            case"-":
                result=SUB;
                break;
            case"*":
                result=MUL;
                break;
            case"/":
                result=DIV;
                break;
            default:
               result=0;
                break;
        }
        return result;
    }
}