《數(shù)據(jù)結(jié)構(gòu)》第十五篇、棧(中篇)---「中綴表達(dá)式」轉(zhuǎn)「后綴表達(dá)式」

怒火攻心.jpg

引言

上篇文章中,我們把棧的概念,棧的兩種實(shí)現(xiàn)方式:順序棧、鏈棧做了一個(gè)介紹,相信大家對(duì)棧這個(gè)概念有了初步的認(rèn)識(shí)了吧,今天給大家?guī)淼氖恰笚5膽?yīng)用」這篇內(nèi)容,我們做android必然非常熟悉activity棧,不過這個(gè)棧其實(shí)還算簡(jiǎn)單,今天不做介紹,如果有需要可以給大家單獨(dú)介紹。

棧是一種常用的數(shù)據(jù)結(jié)構(gòu),基本上稍微復(fù)雜一點(diǎn)的程序都會(huì)用到:
遍歷一個(gè)圖時(shí),會(huì)用到棧;
搜索一個(gè)解時(shí),也會(huì)用到棧。
即使程序代碼中沒有明確用棧,在程序執(zhí)行過程中也會(huì)用到棧,因?yàn)槌绦蚍祷睾秃瘮?shù)調(diào)用以及其他遵循?!赶冗M(jìn)先出」原則的地方,系統(tǒng)都會(huì)用棧來存儲(chǔ)數(shù)據(jù)。

而這篇文章則主要提一下其中一個(gè)比較典型和常用的應(yīng)用:「用棧實(shí)現(xiàn)四則運(yùn)算」

用棧實(shí)現(xiàn)四則運(yùn)算

我們都知道,計(jì)算機(jī)的基本功能大多基于對(duì)數(shù)據(jù)的操作,給出一個(gè)運(yùn)算式,計(jì)算機(jī)能夠迅速的計(jì)算出其結(jié)果,若運(yùn)算式有錯(cuò)誤,如"1+3*(2+5" 這個(gè)運(yùn)算式右邊少寫了一個(gè)")",編譯器會(huì)立即檢查出錯(cuò)誤并報(bào)告,那么計(jì)算機(jī)是如何做到的呢?

其實(shí)計(jì)算機(jī)在進(jìn)行運(yùn)算時(shí),將運(yùn)算表達(dá)式轉(zhuǎn)換成了「逆波蘭表達(dá)式」,這是一種不需要括號(hào)的后綴表達(dá)方式,例如"1+2"經(jīng)轉(zhuǎn)變變?yōu)椤? 2 +”,然后再進(jìn)行運(yùn)算,而在轉(zhuǎn)化的過程中,這些數(shù)據(jù)就保存在棧中。 需要計(jì)算的時(shí)候,利用棧"后進(jìn)先出"的原則,來進(jìn)行字符的匹配檢查,直到完成轉(zhuǎn)換后,再對(duì)后綴表達(dá)式(「逆波蘭表達(dá)式」)計(jì)算結(jié)果。

計(jì)算機(jī)在這個(gè)過程中,執(zhí)行了兩步操作:
(1)將中綴表達(dá)式轉(zhuǎn)換成為了后綴表達(dá)式
(2)對(duì)后綴表達(dá)式進(jìn)行計(jì)算
而這兩步操作都用到了棧,下面先來學(xué)習(xí)執(zhí)行這兩步操作的基本原理。

中綴轉(zhuǎn)后綴

將中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式的過程中,數(shù)據(jù)是用棧來存儲(chǔ)的,在遍歷中綴表達(dá)式時(shí),遵循以下規(guī)則:

    * 對(duì)于數(shù)字:直接輸出
    * 對(duì)于符號(hào):
            左括號(hào):進(jìn)棧,不管棧中是否有元素
            運(yùn)算符:
                   -若此時(shí)棧為空,直接進(jìn)棧
                   -若此時(shí)棧中有元素,則與棧頂符號(hào)進(jìn)行優(yōu)先級(jí)比較:
                   若新符號(hào)優(yōu)先級(jí)較高,則新符號(hào)進(jìn)棧
                   -(默認(rèn)左括號(hào)優(yōu)先級(jí)最低,直接入棧);
                   -若新符號(hào)優(yōu)先級(jí)低,則將棧頂符號(hào)彈出并且輸出,之后讓新符號(hào)進(jìn)棧。
            右括號(hào):不斷將棧頂符號(hào)彈出并輸出,直到匹配到左括號(hào),再接著讀取下一個(gè)符號(hào)。
                  -(注意:左右括號(hào)匹配完成即可,不需要將其輸出)
    * 遍歷結(jié)束時(shí),將棧中所有符號(hào)彈出并輸出。 

額,不知道大家有沒有懵逼,不過懵逼是正常的。。。

下面就用「實(shí)際案例+圖形展示」讓大家認(rèn)識(shí)下「中綴表達(dá)式」 轉(zhuǎn) 「后綴表達(dá)式」 的步驟。
在大家看例子的過程中,不妨根據(jù)上邊的規(guī)則對(duì)照一下,加強(qiáng)記憶

舉個(gè)栗子

下面,以"1 + 2 * ( 6 + 8 )"這個(gè)普通的四則運(yùn)算表達(dá)式(當(dāng)然這個(gè)就是中綴表達(dá)式)轉(zhuǎn)換成后綴表達(dá)式為例,大家一起圍觀一下

中綴轉(zhuǎn)后綴01-遍歷字符串.png

中綴轉(zhuǎn)后綴02-數(shù)字1直接輸出.png
中綴轉(zhuǎn)后綴03-遍歷“+”符號(hào).png
中綴轉(zhuǎn)后綴04-此時(shí)棧為空,無需比較,“+”直接入棧.png
中綴轉(zhuǎn)后綴05-讀取下一個(gè)字符2.png
中綴轉(zhuǎn)后綴06-數(shù)字2直接輸出.png
中綴轉(zhuǎn)后綴07-讀取下一個(gè)字符*.png
中綴轉(zhuǎn)后綴08-字符“*”優(yōu)先級(jí)大于此時(shí)棧內(nèi)棧頂元素優(yōu)先級(jí),直接入棧.png
中綴轉(zhuǎn)后綴09-讀取下一個(gè)字符“(”,注意左括號(hào)直接入棧,且左括號(hào)優(yōu)先級(jí)最低.png
中綴轉(zhuǎn)后綴10-“(”直接入棧.png
中綴轉(zhuǎn)后綴11-讀取下一個(gè)字符.png
中綴轉(zhuǎn)后綴12-數(shù)字6直接輸出.png
中綴轉(zhuǎn)后綴13-讀取下一個(gè)字符“+”.png
中綴轉(zhuǎn)后綴14-“+”優(yōu)先級(jí)大于此時(shí)棧頂元素“(”的優(yōu)先級(jí),顧入棧.png
中綴轉(zhuǎn)后綴15-讀取下一個(gè)字符8.png
中綴轉(zhuǎn)后綴16-數(shù)字8直接輸出.png
中綴轉(zhuǎn)后綴17-讀取下一個(gè)字符“)”.png
中綴轉(zhuǎn)后綴20-遍歷到")",去匹配"(".png
中綴轉(zhuǎn)后綴21-不斷彈出棧頂符號(hào),直到匹配完成.png
中綴轉(zhuǎn)后綴22-匹配完成(注意"("和")"匹配完成即可,不用輸出).png
中綴轉(zhuǎn)后綴23-接著彈出元素.png
中綴轉(zhuǎn)后綴24-彈出棧中所有元素,轉(zhuǎn)換完成.png

OK,通過上面的24個(gè)步驟(寫的啰嗦了,其實(shí)如果我們自己去算的話,不需要這么多步),我們就得到了傳說中的「后綴表達(dá)式」了。

「中綴表達(dá)式」1+2*(6+8)
轉(zhuǎn)成
「后綴表達(dá)式」1 2 6 8 + * +

就轉(zhuǎn)換成功了!

在這個(gè)轉(zhuǎn)換匹配的過程中,如果有錯(cuò)誤,比如如果運(yùn)算式少寫一個(gè)左括號(hào),則直到彈出棧中的所有元素也匹配不到左括號(hào),那么編譯器就會(huì)報(bào)錯(cuò),原理其實(shí)就在此。


代碼來了

下面我們通過代碼來演示一遍「中綴表達(dá)式」轉(zhuǎn)「后綴表達(dá)式」的過程
(注意,在遍歷運(yùn)算式時(shí)是以字符串的形式進(jìn)行的)

linkstack.h 頭文件

#ifndef _LINKSTACK_H_
#define _LINKSTACK_H_

typedef struct Node * pNode;
typedef struct Stack * LinkStack;

struct Node{//數(shù)據(jù)結(jié)點(diǎn)
        char data;//數(shù)據(jù)
        pNode next;//指針
};
struct Stack{//此結(jié)構(gòu)記錄棧的大小和棧頂元素指針
      pNode top;//棧頂元素指針
      int size;//棧大小
};
LinkStack Create();//創(chuàng)建棧
int IsEmpty(LinkStack lstack);//判斷棧是否為空
int getSize(LinkStack lstack);//獲取棧的大小
int Push(LinkStack lstack,int val);//元素入棧
pNode getTop(LinkStack lstack);//獲取棧頂元素
pNode Pop(LinkStack lstack);//彈出棧頂元素
void  Destory(LinkStack lstack);//銷毀棧
#endif

linkstack.c 操作函數(shù)實(shí)現(xiàn)文件

#include <stdio.h>
#include <stdlib.h>
#include "linkstack.h"
LinkStack Create(){
    LinkStack lstack=(LinkStack)malloc(sizeof(struct Stack));
    if(lstack!=null){
    lstack->top=NULL;
    lstack->size=0;  
    }
return lstack;
}    

int IsEmpty(LinkStack stack){
 if(stack->top==NULL||stack->size==0){
  return 1;
  }
  return 0;
 }
 int getSize(LinkStack stack){
  if(!IsEmpty(stack)){
    return(stack->size);
  }
  return 0;
}
int Push(LinkStack stack,char val){
  pNode node=(pNode)malloc(sizeof(struct Node));
  if(node!=null){
  node->data=val;
  node->next=getTop(lstack);
  lstack->top=node;
  lstack->size++;
  }
 return 1; 
}
 pNode getTop(LinkStack lstack){
  if(!IsEmpty(lstack)){
    return lstack->top;
    } 
    return NULL;
  }
pNode Pop(LinkStack lstack){
  if(IsEmpty(lstack)){
      return NULL;
    }
  pNode node=lstack->top;
  lstack->top=lstack->top->next;
  lstack->size--;
  return node;
}
 void Destory(LinkStack lstack){
    if(IsEmpty(lstack)){
        free(lstack);
        printf("棧以為空,無需清理\n");
        return;
    }
//如果棧不為空,需要把棧中的結(jié)點(diǎn)都釋放
do{
        pNode pTmp;
        pTmp=Pop(lstack);
        free(pTmp);
  }
while(lstack->size>0);
    printf("棧銷毀成功\n");
}

main.c(測(cè)試文件)

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdlib.h>
    #include <stdio.h>
    #include "linkstack.h"
    
    char buffer[256]={0};//臨時(shí)緩沖區(qū),從棧中彈出的元素可以先存放到這里
    void put(char val){
          static int index=0;
          buffer[index++]=val;//將字符存入,然后索引index后移
    }
    //優(yōu)先級(jí)比較函數(shù)
    int priority(char ch){
          int ret=0;
          switch(ch){
              case '+':
              case '-':
                          ret=1;
               break;
              case '*':
              case '/':
                          ret=2;
              break;
              default:
               break;
          }
          return ret;
    }

     //判斷字符是否是數(shù)字
    int inNumber(char ch){
              return (ch>='0' &&ch <='9');//是數(shù)字返回1,否則返回0
    }
    //判斷字符是否是運(yùn)算符
    int isOperator(char ch){
              return(ch=='+'||ch=='-'||ch=='*'||ch=='/');
    }
    //判斷字符是否是左括號(hào)
    int isLeft(char ch){
            return(ch=='(');
    }
    //判斷字符是否是右括號(hào)
    int isRight(char ch){
              return(ch==')');
    }
    //函數(shù)功能:中綴轉(zhuǎn)后綴表達(dá)式
    //函數(shù)返回值:正確返回0,錯(cuò)誤返回-1
    int Transform(const char * str){
              //在轉(zhuǎn)換字符串時(shí),先常見一個(gè)棧
              LinkStack lstack=Create();//創(chuàng)建棧
              //創(chuàng)建完成之后,就遍歷字符串中的字符,數(shù)字輸出,運(yùn)算符入棧...
              int i=0;
              while(str[i]!='\0'){
                  //判斷是否是數(shù)字
                  if(isNumber(str[i])){//如果是數(shù)字,就直接輸出
                          Put(str[i]);//存入到buffer中
                   }
                    //判斷是否是運(yùn)算符
                  else if(isOperator(str[i])){
                          //如果是運(yùn)算符,則先判斷棧是否為空
                          if(!IsEmpty(lstack)){//如果棧不為空
                             //要比較此符號(hào)和棧頂符號(hào)的優(yōu)先級(jí)
                              while(!IsEmpty(lstack)&&
                              Priority(*(char*)getTop(lstack)))>=Priority(str[i]))
                                {
                                        //如果棧頂符號(hào)優(yōu)先級(jí)高,就將棧頂符號(hào)彈出并輸出
                                        //直到棧頂符號(hào)的優(yōu)先級(jí)小于此符號(hào)
                                        //或者棧已彈空
                                        Put(Pop(lstack)->data);//將彈出的棧頂符號(hào)存入buffer

                                 }
                          }
                          Push(lstack,str[i]);//如果棧為空,符號(hào)直接入棧
                  }
                  //如果是做符號(hào),直接入棧
                  else if(isLeft(str[i])){
                        Push(lstack,str[i]);
                  }
                  else if(isRight(str[i])){
                  //判斷棧頂是不是左括號(hào),如果不是就彈出,直到匹配到左括號(hào)
                  while(!isLeft(*((char*)getTop(lstack)))){
                      //彈出棧頂符號(hào)并存入到buffer中
                        Put(Pop(lstack)->data);
                         if(IsEmpty(lstack)){
                        //彈出元素后,棧已經(jīng)空了,就匹配錯(cuò)誤
                        printf("沒有匹配到左括號(hào)!\n");
                        return -1;//棧以為空,結(jié)束程序
                          }
                  }
                Pop(lstack);//while循環(huán)結(jié)束,匹配到了左括號(hào),將左括號(hào)彈出,不保存
                  }
                  else{
                        printf("有不能識(shí)別的字符!\n");
                        return -1;
                  }
                    i++;
              }
              //遍歷結(jié)束
               if(str[0]==‘\0’){
                  //遍歷結(jié)束后,將棧中所有符號(hào)依次彈出
                  while(!IsEmpty(lstack)){
                      if(getTop(lstack)->data=='('){
                      //如果棧中還有‘(’說明缺少右括號(hào)
                      printf("有沒有匹配的‘(’,缺少右括號(hào)!\n");
                      return -1;
                      }
              put(Pop(lstack)->data);
                  }
                }
            else{
                  printf("遍歷沒有完成!\n");
                  }
              return 1;
    }
      int main(){
              char str[1024]=(0);
              printf("請(qǐng)輸入四則運(yùn)算表達(dá)式:\n");
              scanf("%s",str);
             if(Transform(str)==-1){
                  printf("遍歷中出現(xiàn)錯(cuò)誤,無法完成轉(zhuǎn)換!\n");
              }
             else{
                  printf("轉(zhuǎn)換后的表達(dá)式是:%s\n",buffer);
            }
            system("pause");
            return 0;
      }

上面的實(shí)例代碼只能處理簡(jiǎn)單的一位數(shù)字的運(yùn)算,如果是兩位或者多位數(shù)字,它并不能識(shí)別。

總結(jié)

ok,本篇文章就到這里吧,下篇文章我們?cè)倭摹负缶Y表達(dá)式」運(yùn)算哦,學(xué)完本篇文章,你就會(huì)大概知道計(jì)算機(jī)是如何處理四則運(yùn)算了,是不是很興奮~~~~

不早了,睡吧~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容