如何生成尾碼表達式

来源:http://www.cnblogs.com/luoxn28/archive/2017/02/12/6391050.html
-Advertisement-
Play Games

如果計算一個表達式,比如 4+5+6*2,隨著計算器的不同,簡單的四功能計算器是30,許多科學計算器知道乘法的優先順序高於加法,所以科學答案是21。典型計算順序可以是計算4+5,存為臨時變數a,再計算6*2,存為b,最後計算a+b可得出最後結果。這種操作順序如下:45+62*+ 這種記法就是尾碼表達式 ...


  如果計算一個表達式,比如 4+5+6*2,隨著計算器的不同,簡單的四功能計算器是30,許多科學計算器知道乘法的優先順序高於加法,所以科學答案是21。典型計算順序可以是計算4+5,存為臨時變數a,再計算6*2,存為b,最後計算a+b可得出最後結果。這種操作順序如下:45+62*+

  這種記法就是尾碼表達式,其求值的過程就是上面描述的整個過程。那如何生成尾碼表達式呢,也就是從中綴表達式轉換為尾碼表達式,可以藉助於棧來實現,整個步驟如下:

  1. 依次讀取輸入的表達式,如果是操作數,則把它放入到輸出中。
  2. 如果是操作符,棧為空的話直接將該操作符入棧;如果棧非空,則比較棧頂操作符和該操作符優先順序,如果棧頂操作符優先順序小於該操作符,則該操作符入棧;否則彈出棧頂操作符並將其放入到輸出中,直到棧為空或者發現優先順序更低的操作符為止。
  3. 如果是括弧,比如'('和')',則特殊處理。如果是'('的話,直接入棧;如果是')',那麼就將棧頂操作符彈出寫入到輸出中,直到遇到一個對應的'(',但是這個'('只彈出不寫入到輸出中。註意:"("可以理解為優先順序最高。
  4. 當表達式讀取完畢後,如果棧中還有操作符,則依次彈出操作符並寫入到輸出中。

 

比如(1+2)*(3+2)-4/2表達式,其尾碼表達式和計算結果為:

Java示例代碼如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * MyComputer
 */
public class MyComputer {

    public static int computer(String input) {
        List<String> cutList = cutInput(input);
        List<String> afterList = getAfterList(cutList);

        return getResultFromAfterList(afterList);
    }

    /**
     * 根據尾碼表達式計算結果
     */
    private static int getResultFromAfterList(List<String> afterList) {
        Stack<Integer> stack = new Stack<>();

        for (String ele : afterList) {
            if (isFlag(ele.charAt(0))) {
                int b = stack.pop();
                int a = stack.pop();

                stack.push(cal(a, b, ele.charAt(0)));
            } else {
                stack.push(Integer.valueOf(ele));
            }
        }

        if (stack.size() != 1) {
            throw new StackOverflowError();
        }
        return stack.pop();
    }

    /**
     * 獲取兩個數的計算結果
     */
    private static int cal(int a, int b, char flag) {
        int result = 0;

        switch (flag) {
            case '+': {
                result = a + b;
                break;
            }
            case '-': {
                result = a - b;
                break;
            }
            case '*': {
                result = a * b;
                break;
            }
            case '/': {
                result = a / b;
                break;
            }
            default: {
                break;
            }
        }

        return result;
    }

    /**
     * 生成尾碼表達式
     */
    private static List<String> getAfterList(List<String> cutList) {
        List<String> output = new ArrayList<>();
        Stack<Character> stack = new Stack<>();

        for (String ele : cutList) {
            char flag = ele.charAt(0);
            if (isFlag(ele.charAt(0)) || (flag == '(') || (flag == ')')) {
                // 計算符入棧
                if (stack.isEmpty()) {
                    stack.push(flag);
                } else {
                    // 如果待入棧計算符大於棧頂計算符,則直接入棧;否則出棧直到棧為空或者待入棧計算符小於棧頂計算符
                    if (flag == '(') {
                        stack.push(flag);
                    } else if (flag == ')') {
                        while (stack.peek() != '(') {
                            output.add(String.valueOf(stack.pop()));
                        }
                        stack.pop();
                    }
                    else if (isFlagSmaller(stack.peek(), flag)) {
                        stack.push(flag);
                    } else if (stack.peek() == '(') {
                        stack.push(flag);
                    } else{
                        do {
                            if (stack.peek() == '(') {
                                break;
                            }
                            output.add(String.valueOf(stack.pop()));
                        } while (!stack.isEmpty() && !isFlagSmaller(stack.peek(), flag));
                        stack.push(flag);
                    }
                }
            } else {
                // 數字直接添加到輸出中
                output.add(ele);
            }
        }

        while (!stack.isEmpty()) {
            if ((stack.peek() != '(') || (stack.peek() != ')')) {
                output.add(String.valueOf(stack.pop()));
            }
        }

        return output;
    }

    /**
     * 將字元串以操作符為分隔符切片
     */
    private static List<String> cutInput(String input) {
        List<String> cutList = new ArrayList<>();
        boolean running = true;

        while ((input.length() > 0) && running) {
            char c = input.charAt(0);
            if (isFlag(c) || (c == '(') || (c == ')')) {
                cutList.add(String.valueOf(c));
                input = input.substring(1);
            } else {
                for (int i = 0; i < input.length(); i++) {
                    char tmpC = input.charAt(i);
                    if (isFlag(tmpC) || (tmpC == '(') || (tmpC == ')')) {
                        cutList.add(input.substring(0, i));
                        cutList.add(String.valueOf(tmpC));

                        input = input.substring(i + 1);
                        break;
                    }

                    if (i == input.length() - 1) {
                        cutList.add(input);
                        running = false;
                    }
                }
            }
        }

        return cutList;
    }

    /**
     * 判斷一個字元是否是操作符
     */
    private static boolean isFlag(char c) {
        return (c == '+' || c == '-' || c == '*' || c == '/');
    }

    /**
     * 第一個操作符優先順序是否小於第二個
     */
    private static boolean isFlagSmaller(char a, char b) {
        boolean flag = true;

        switch (a) {
            case '+':
            case '-': {
                if ((b == '+') || (b == '-')) {
                    flag = false;
                }
                break;
            }

            case '*':
            case '/': {
                flag = false;
            }
            case '(': {
                flag = false;
            }
            default: {
                break;
            }
        }

        return flag;
    }
}

 

尾碼表達式和表達式樹

  表達式樹是由尾碼表達式構造而來的,那麼如何構造呢,也是藉助於棧來實現。上述代碼已經實現了從中綴表達式轉換為為尾碼表達式,整個步驟大致如下:

  1. 依次遍歷尾碼表達式,如果是如果是操作數,則以其為數據創建一個單節點書,然後將該樹入棧。
  2. 如果是操作符,則從棧中彈出2棵樹T1和T2(T1先彈出)並形成一個新的書,該樹的根就是該操作符,左右孩子分別是T2和T1,然後將這棵新樹入棧。
  3. 最後棧中會剩下一棵樹,則這棵樹就是表達式樹。具體代碼可以參考表達式樹

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 20170210問題解析請點擊今日問題下方的“【Java每日一題】20170213”查看(問題解析在公眾號首發,公眾號ID:weknow619) 今日問題: 請問主程式輸出結果是什麼?(點擊以下“【Java每日一題】20170213”查看20170210問題解析) 題目原發佈於公眾號、簡書:【Jav ...
  • Java中的內部類 小記:內部類可以作為其他類的成員,而且可訪問它所在類的成員。 ...
  • 最近開始看Spring Boot,發現其開發起來真是方便。今天就來實現一個簡單的Spring MVC 請求,純Java代碼的哦。 1、Maven必不可少,先看看都載入了那些依賴: 2、Controller 3、頁面,放在src/main/resources/templates/目錄下,方便解析 4、 ...
  • 浮點數會有精度損失這個在上大學的時候就已經被告知,但是至今完全沒有想明白其中的原由,老師講的時候也是一筆帶過的,自己也沒有好好琢磨。終於在工作的時候碰到了,於是google了一番。 問題: 對兩個double類型的值進行運算,有時會出現結果值異常的問題。比如: 輸出: 39.989999999999 ...
  • 最近一直在搞郵件這塊,本來我們郵件發送是用的騰訊免費的企業郵箱,郵件功能沒有問題,但是由於郵件的限制,如下: 這些限制導致我們的部分客戶是收不到郵件的,哪怕付費,這樣的固定頻率限制也是無法解決的,可以說我們國內的郵件廠商都是這樣,而國外的卻要收費。 那麼問題來了,如何突破發送郵件的頻率限制? 1. ...
  • 當JSTL標簽庫已經無法滿足我們的需求時候,就需要自己開發自定義標簽,來滿足我們的需求,自定義標簽實際上是一個普通的java類,繼承SimpleTagSupport類。 做類。派生自SimpleTagSupport,重寫doTag()方法。getJspBody(),getJspContext(),i ...
  • 理解volatile 平時工作中對於多線程的應用並不太多,但是不能說工作中不應用就可以對此不去瞭解,至少要做的知道有這麼個東西,主要是作什麼的,這樣有助於看其它人寫的代碼。提到這個volatile,一般都會想到併發,同步,鎖之類,但要想搞清楚需要看看下麵一些知識。 處理器,高速緩存,主記憶體之間的關係 ...
  • Java 生成驗證碼的流程是: 收到請求 生成驗證碼所用的隨機數 使用隨機數寫出圖片 將隨機數記錄到Session中 輸出驗證碼 Java 驗證驗證碼的流程是: 收到請求 獲取用戶傳過來的驗證碼數字 驗證是否正確 輸出驗證結果 下麵通過一個例子來展示驗證碼的生成流程,該例子使用基本Java Spri ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...