不使用+、-、*、/四则运算符号求两个整数之和

该博客介绍了如何在不允许使用+、-、*、/四则运算的情况下,通过位运算符求两个整数之和。详细解析了二进制加法的步骤,并给出了C++实现的代码示例,同时讲解了位逻辑运算符和移位运算符的相关知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

剑指OFFER题2------按牛客网通过率排序

时间:2018.10.14.2110
作者:Waitt

题目

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

时间限制:1秒 空间限制:32768K 热度指数:81665

解答

通过按位运算进行计算:
思路:
首先看十进制是如何做的: 5+7=12,三步走
第一步:相加各位的值,不算进位,得到2。

第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。

第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。

同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111
第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。

第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。

第三步重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。
继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。

代码

class Solution 
<think>我们使用两个栈:一个操作数栈(存储数字),一个操作符栈(存储运算符和括号)。同时,我们也可以使用队列来辅助,但根据问题描述,用户希望用队列实现。然而,引用[4]中提到使用队列存储整个表达式的各个元素,然后利用栈来进行计算。所以我们按照以下步骤:1.将表达式字符串分割成一个个的token(数字、运算符、括号)并存入队列。2.然后,使用两个栈:操作数栈和操作符栈,按照以下规则处理队列中的每个元素:-如果遇到数字,直接压入操作数栈。-如果遇到左括号,压入操作符栈。-如果遇到右括号,则断弹出操作符栈顶的运算符,并从操作数栈弹出两个操作数进行运算,将结果压回操作数栈,直到遇到左括号(左括号弹出但处理)。-如果遇到运算符(+、-、*、/):a.如果操作符栈为空或者栈顶是左括号,则直接压入操作符栈。b.否则,比较当前运算符与操作符栈栈顶运算符的优先级:-如果当前运算符优先级高于栈顶,则压入操作符栈。-否则,弹出操作符栈顶的运算符,从操作数栈弹出两个操作数进行运算,将结果压回操作数栈。然后再次比较当前运算符与新的栈顶运算符的优先级,直到当前运算符优先级高于栈顶或栈为空或栈顶为左括号,最后将当前运算符压入操作符栈。3.当队列处理完毕后,将操作符栈中剩余的运算符依次弹出,每弹出一个运算符,就从操作数栈弹出两个操作数进行运算,将结果压回操作数栈。4.操作数栈最后剩下的一个数就是表达式的结果。注意:数字可能是多位数,也可能有小数(但题目没有要小数,所以我们先实现整数)。另外,负数的情况,我们可以在表达式前面添加0来处理,比如“-1+1”变成“0-1+1”。但是,题目要使用队列,因此我们首先将表达式拆分成token并存入队列。拆分表达式时,需要识别连续的多个数字组成的多位数。我们按照引用[4]的思路,用队列存储表达式元素(token),然后按顺序出队进行处理。实现步骤:1.从表达式中提取token(数字、运算符、括号)并存入队列。2.使用两个栈:数字栈和符号栈,然后按照上述规则处理队列中的每个元素。代码设计:1.使用一个队列(tokenQueue)来保存表达式中的每个token(数字、运算符、括号)。2.使用两个栈:numStack(操作数栈)和opStack(操作符栈)。3.处理每个token的规则如上所述。然而,用户指定使用C语言,所以我们用C语言实现。注意:C语言中没有现成的队列和栈,我们需要自己实现。实现队列和栈的基本操作:-队列:入队、出队、判空-栈:入栈、出栈、获取栈顶元素、判空另外,我们需要一个函数来分割表达式为token。考虑到负数:在表达式开头遇到负号,我们可以在表达式前加一个0;或者在左括号后面遇到负号,同样加一个0。例如:"(-1+2)"变成"(0-1+2)"。我们可以在分割token时处理这种情况:当遇到负号且前面没有数字且是右括号时,我们就认为它是一个负号而是减号,然后将其与后面的数字组合成负数。但是,为了简单起见,我们先考虑负数,然后再扩展。步骤:1.实现队列和栈。2.编写一个函数,将表达式字符串分割成token并存入队列。同时处理多位数字。3.处理每个token:按照上述规则使用两个栈进行计算。对于负数,我们可以在token分割阶段这样处理:-如果第一个字符是负号,则我们插入一个0,然后把这个负号当作运算符,后面跟的数字取负(但这样需要修改计算过程?)。-或者在token分割时,如果遇到一个负号,并且前面的token是空或者是左括号或者是运算符(除右括号外),那么我们就认为它是一元负号。然后我们将它后面的数字作为负数。我们可以这样:当我们在表达式中遇到一个负号('-')时,我们判断:-如果它是第一个字符,则后面紧跟的数字为负数。-如果它前面的字符是'('或运算符(除')'),则后面紧跟的数字为负数。因此,在分割token时,我们可能需要向前或向后查看字符。我们可以用一个指针遍历字符串,并且用一个标志位来指示当前是否可能是一元负号(即作为负号而是减号)。但为了简化,我们可以在分割token时处理一元负号:将表达式中的一元负号视为"0-(数字)",但这样会改变表达式结构。或者,我们在分割token时将一元负号与数字合并成一个带负号的数字。这里我们采用:在分割token时,将可能的一元负号与后面的数字合并成一个负数。具体处理如下:在遍历表达式字符串时:-遇到数字,则一直读直到非数字,将这串数字字符串转换为整数,然后入队。-遇到运算符时:-如果当前字符是'-',并且是表达式第一个字符,或者前一个字符是'('或者'+'、'-'、'*'、'/'(即运算符)则说明它是一元负号。-那么我们就需要读后面的数字,并将其作为负数入队(或者用一个特殊标记?)然而,更简单的方法:在分割token时,如果遇到了一个减号,并且满足以下条件之一:a.它是表达式的第一个字符b.前一个token是左括号c.前一个token是运算符(即前一个token是'+'、'-'、'*'、'/')那么我们就将下一个数字(无论是一个数字还是多位数字)取负,作为一个数字(负数)入队。但是,如果减号后面是一个括号,该怎么办?比如:-(1+2),我们需要计算括号表达式的相反数。这时,我们可以在数字栈中先压一个0,然后处理0-(括号表达式的结果)。考虑到时间,我们先实现带负数的情况,再扩展。计划:1.先实现一个支持整数和基本四则运算及括号的表达式计算器,用队列存储token,两个栈计算。2.然后再考虑负数的扩展。下面我们先考虑负数的情况实现代码。代码步骤:1.定义队列和栈的数据结构及操作函数。2.实现表达式拆分token的函数,注意多位数,存入队列。3.实现计算函数:处理队列中的token,用两个栈计算。4.主函数:输入表达式,调用拆分函数得到队列,然后计算,输出结果。我们先实现队列:队列节点:每个节点存储一个token(用字符串或者联合体?)为了简单,我们用一个联合体,因为token可以是整数(我们暂时只处理整数)也可以是字符(运算符、括号)。但是,也可以全部用字符串,然后在计算时再区分。但为了简单,我们用一个结构体来存储token类型和值。定义token类型:typedefenum{TOKEN_NUM,TOKEN_OP,TOKEN_LPAREN,TOKEN_RPAREN}TokenType;定义token结构体:typedefstructToken{TokenTypetype;intvalue;//如果类型是TOKEN_NUM,则存储数字值;否则,对于运算符,我们可以用字符的ASCII,但我们存储到value,而是另外定义?charop;//如果类型是运算符或括号,则用op存储字符}Token;这样,当我们遇到数字,就设置type为TOKEN_NUM,并设置value;当遇到运算符或括号,设置type为相应的类型,并设置op为对应字符(+、-、*、/、(、))。队列节点:typedefstructNode{Tokentoken;structNode*next;}Node;队列:typedefstructQueue{Node*front;Node*rear;}Queue;栈:我们也可以使用Node,但为了代码清晰,我们分开定义栈。操作数栈:只存整数(用于计算),所以操作数栈可以用整数栈。操作符栈:存储运算符和括号,我们定义成字符栈,因为括号和运算符都是一个字符。但是,我们的token队列已经区分了类型,而在计算过程中,我们只需要使用数字栈(int)和操作符栈(char),因此我们可以将队列中的token按顺序出队,然后根据类型分别处理:-数字:直接压入数字栈?但是,在计算过程中,操作符栈弹出的操作符需要从数字栈弹出两个操作数进行计算。所以,我们在处理每个token时,如果是数字,就入数字栈;如果是操作符或括号,则按照规则处理。然而,规则中要处理运算符优先级,我们需要比较优先级。优先级函数:intgetPriority(charop){if(op=='+'||op=='-')return1;if(op=='*'||op=='/')return2;return0;//其他情况,比如括号,优先级返回0}注意:括号在栈中处理时,左括号只是作为入栈,右括号会触发弹栈直到左括号。在比较优先级时,我们会比较括号,因此括号优先级返回0。算法流程(在已经得到队列的情况下):while(队列为空){出队一个tokent如果t是数字:压入操作数栈如果t是左括号:压入操作符栈如果t是右括号:while(操作符栈顶是左括号){弹出操作符栈顶op弹出操作数栈顶两个数(先弹出的是第二个操作数,再弹出的是第一个操作数)进行运算:firstopsecond,将结果压入操作数栈}弹出操作符栈顶(左括号)如果t是运算符:记作op_currentwhile(操作符栈为空且操作符栈顶为左括号且操作符栈顶的优先级>=op_current的优先级){弹出操作符栈顶op弹出操作数栈顶两个数,计算并压栈}将op_current压入操作符栈}然后,将操作符栈中剩余的操作符依次弹出计算:while(操作符栈为空){弹出操作符栈顶op弹出操作数栈顶两个数,计算并压栈}最后,操作数栈顶元素就是结果。注意:在计算时,注意除数为0的情况。下面我们实现:由于C语言需要手动管理内存,我们在每一步都需要注意内存释放。为了简化,我们先考虑内存释放(在程序结束时释放,或在一个计算函数内部分配和释放)。现在,我们开始写代码:1.定义Token和队列、栈结构。2.队列操作:初始化、入队、出队、判断空。3.栈操作:初始化(用于操作数栈和操作符栈)、入栈、出栈、判断空、获取栈顶元素(出栈)。4.表达式分割:将字符串表达式分割成token并存入队列。遍历字符串,跳过空格,遇到数字则读完整数字字符串(可能有多位),然后转成整数,构成一个数字token入队。遇到其他字符:如果是运算符(+、-、*、/)或括号(左括号、右括号),则作为一个token入队(类型分别为TOKEN_OP、TOKEN_LPAREN、TOKEN_RPAREN,同时记录字符)。5.计算函数:接受队列,返回整数结果(或浮点数?先整数)。代码较长,我们逐步实现。由于题目要整数,所以我们暂时处理小数。另外,我们考虑处理负数的扩展:在表达式分割时,检测可能的一元负号。如何检测?在分割时,如果当前字符是'-',并且以下条件之一成立:a.是表达式的第一个字符b.前一个token是左括号(我们队列中前一个token是刚入队的)或者前一个token是运算符(+、-、*、/、左括号)那么,我们就需要将负号与后面的数字合并成一个负数(或者,如果后面是括号,则我们无法合并,需要特殊处理:在队列中插入0,然后插入'-',然后括号表达式作为一个整体?)处理一元负号的方法:方法1:将其转化为0-(后面的表达式)。因此,如果检测到一元负号,我们在队列中插入0(数字token),然后插入一个'-'运算符token,然后再正常处理后面的部分(括号或数字)。方法2:在token分割时,如果一元负号后面是数字,我们就合并,如果是括号,我们就使用0-的方法。这里我们使用方法1:统一使用0-的方法,即在队列中插入0和减号。因此,在分割token时,当我们检测到一元负号(即作为负号而是减号),我们就:先入队一个数字token(0)然后入队运算符token('-')然后下一个token正常处理(可能是数字,也可能是括号?)但是,如果下一个token是括号,那么我们就需要处理整个括号表达式,然后0减去括号表达式的结果。这样处理可以统一为二元减号,因此计算规则变。所以,在分割token时,如果遇到一元负号:我们入队:数字0、运算符'-'然后,继续处理后面的字符(注意要重复处理)但是,这样处理后,表达式实际上被改变了。比如:原表达式:-1->变成:0-1原表达式:-(1+2)->变成:0-(1+2)因此,我们在分割token时,当遇到一个可能的一元负号,我们就插入两个token:0和'-'。然后继续处理后面的字符(要跳过,因为当前字符'-'已经被处理了,但是我们还要继续处理后面的)。但是,这里有一个问题:在插入0和'-'后,后面如果是数字,我们就会正常分割数字,这样会有问题。步骤修正:在表达式分割时,我们遍历字符串指针,每次处理一个token,然后将指针后移。当遇到一个'-'字符,并且是表达式开头(即当前指针位于开头,i=0)或者前一个字符(我们需要记录上一个token是什么)是左括号或运算符(注意,此时上一个token已经入队),那么我们就认为是一元负号。注意,我们能只通过前一个字符判断,而需要根据上一个token的类型判断。所以,我们在分割时,需要记录上一个token的类型。具体:定义一个变量记录上一个token的类型(也可以是字符,或者一个标记)。因为我们的token是顺序入队,我们可以记录上一个入队的token类型。初始时,上一个token类型为TOKEN_NONE(我们设置一个初始类型)。当我们遇到一个'-'字符:如果上一个token类型是TOKEN_NONE(即第一个字符)或者上一个token类型是TOKEN_LPAREN或者上一个token类型是TOKEN_OP(注意运算符),则当前负号是一元负号。那么,我们就:入队一个数字0(token类型TOKEN_NUM,value=0)入队一个运算符'-'(token类型TOKEN_OP,op='-')然后,将上一个token类型更新为TOKEN_OP(因为刚入队了一个运算符)注意:当前指针(指向'-')已经处理,所以指针后移(i++),然后继续循环(下一次循环,指针指向下一个字符)。这样,下一个字符会被正常处理,如果是数字,就会分割数字;如果是括号,就会分割括号。注意:这样处理会使得队列中有多个token:0和减号。并且,原来的负号并没有跳过任何字符(除了它自己),所以下一个字符会被正常处理。但是,这样处理后的表达式就变成了:0-(原来的后续表达式)。然后后续的处理就和普通的二元减号一样了。那么,我们还需要记录上一个token的类型。在循环中,我们每次处理一个token(数字、运算符、括号)后,就更新上一个token的类型。特别地,如果我们遇到一个数字,那么上一个token类型就是TOKEN_NUM;遇到运算符,则上一个token类型是TOKEN_OP;遇到左括号,上一个token类型是TOKEN_LPAREN;右括号,上一个token类型是TOKEN_RPAREN?注意,遇到右括号,后面可能会跟着运算符,所以右括号之后出现减号应该是二元运算符吗?是,右括号之后应该跟运算符或结束,所以减号在这里应该是二元运算符(减号)。因为右括号后面可以跟运算符,所以如果减号在右括号后面,它应该是二元运算符。因此,对于一元负号的判断条件只包括:1.在开头2.在左括号后3.在运算符后而右括号后出现的减号,我们是视为二元运算符(即减号)而是一元负号。所以,在分割时,我们记录上一个token的类型(prevType),初始化为一个特殊值(比如-1,表示没有上一个token)。然后:for(遍历字符串){如果当前字符是数字:读取整个数字,生成数字token,入队,并将prevType设置为TOKEN_NUM。如果当前字符是'+'、'*'、'/':生成运算符token,入队,prevType设置为TOKEN_OP。如果当前字符是'-':如果prevType等于TOKEN_NONE(即开头)或者prevType等于TOKEN_LPAREN或者prevType等于TOKEN_OP:这是一元负号:入队0和减号,prevType设置为TOKEN_OP(因为刚入队了减号,它作为运算符,所以下一次的prevType是TOKEN_OP)。否则:生成运算符token(减号),入队,prevType设置为TOKEN_OP。如果当前字符是'(':入队左括号token,prevType设置为TOKEN_LPAREN。如果当前字符是')':入队右括号token,prevType设置为TOKEN_RPAREN。}注意:我们每次处理当前字符后,指针都会后移。在读取数字时,指针会移动到数字的最后一个字符,然后循环会继续,指针再后移一位。然而,我们处理一元负号时,由于我们连续入队了两个token,所以我们需要将指针向后移动一位(即跳过当前负号),然后进入下一次循环,处理下一个字符(因为负号已经处理完了)。但是,注意:我们还没有跳过这个负号!因为我们在处理负号时,还没有移动指针(我们读取数字时移动指针是因为用了while循环读取多个数字)。因此,我们需要在负号的处理中手动移动指针(但是,当前字符是负号,所以我们在循环最后会i++)。所以,我们需要额外移动指针,但是这样就会跳过负号,然后继续处理下一个字符。所以,在负号处理中,我们需要额外移动指针,因为循环最后会有i++。但注意,处理数字和负号都需要将指针移动到连续的数字的末尾,然后循环的i++会到下一个字符。但这里,处理一元负号时,我们只是将两个token入队,并没有移动指针(除了循环本身的i++)。所以,当处理一元负号时,我们将指针i所在的位置视为处理完毕(即负号处理完毕),然后再i++到下一个字符。所以,这样没有问题。但是,注意:如果我们这样处理,那么一元负号后面的数字(如果有)就会在下一轮循环中被当作数字处理,并且会错过。因此,我们按照这个思路实现。由于时间关系,我们先考虑负数情况,先实现无负数的情况,然后在此基础上增加一元负号的处理。下面给出完整代码步骤:1.定义Token类型、队列、栈。2.实现队列的初始化、入队、出队、判断空。3.实现操作数栈(int)和操作符栈(char)的初始化、入栈、出栈、判断空、取栈顶(出栈)操作。4.表达式分割函数:Queue*tokenize(char*expr),将字符串表达式分割成token队列,包括一元负号的处理。5.计算函数:intevaluate(Queue*tokenQueue),使用两个栈计算表达式的值。6.主函数:输入表达式字符串,调用tokenize得到队列,调用evaluate计算,输出结果。注意:在计算过程中,要注意操作数栈弹出两个数的顺序:先弹出的是第二个操作数,后弹出的是第一个操作数(即栈顶是第二操作数,次顶是第二操作数),所以计算减法除法时要注意顺序:value=v1(op)v2应该是先弹出的v2作为第二个操作数。例如:操作数栈:[3,5](栈顶5,下面是3),然后遇到减号,我们弹出5,再弹出3,然后计算3-5。因此,计算函数中:intb=pop_numstack();//第二个操作数inta=pop_numstack();//第一个操作数switch(op){case'+':push_numstack(a+b);break;case'-':push_numstack(a-b);break;case'*':push_numstack(a*b);break;case'/':if(b==0){printf("divisionbyzero\n");exit(1);}push_numstack(a/b);break;}由于代码较长,我们分模块实现。为了简洁,以下代码做了一些简化(例如,进行动态分配检查,内存释放也简化处理)。我们先实现无负数的情况,然后再扩展负数。以下是完整代码框架:头文件:#include<stdio.h>#include<stdlib.h>#include<ctype.h>#include<string.h>定义Token类型和结构体,以及队列、栈://token类型枚举typedefenum{TOKEN_NUM,//数字TOKEN_OP,//运算符:+-*/TOKEN_LPAREN,//左括号TOKEN_RPAREN//右括号}TokenType;//token结构typedefstructToken{TokenTypetype;intvalue;//数字值charop;//运算符或括号(对于非数字,如果是运算符或括号,这里存储字符)}Token;//队列节点typedefstructNode{Tokentoken;structNode*next;}Node;//队列typedefstructQueue{Node*front;Node*rear;}Queue;//操作符栈(char栈)和操作数栈(int栈)分别实现//操作符栈typedefstructOpStack{char*data;inttop;intcapacity;}OpStack;//操作数栈typedefstructNumStack{int*data;inttop;intcapacity;}NumStack;//初始化队列voidinitQueue(Queue*q){q->front=q->rear=NULL;}//入队voidenqueue(Queue*q,Tokentoken){Node*newNode=(Node*)malloc(sizeof(Node));newNode->token=token;newNode->next=NULL;if(q->rear==NULL){q->front=q->rear=newNode;}else{q->rear->next=newNode;q->rear=newNode;}}//出队Tokendequeue(Queue*q){if(q->front==NULL){Tokenempty;empty.type=TOKEN_NUM;empty.value=0;returnempty;}Node*temp=q->front;Tokentoken=temp->token;q->front=q->front->next;if(q->front==NULL){q->rear=NULL;}free(temp);returntoken;}//判断队列空intisQueueEmpty(Queue*q){returnq->front==NULL;}//初始化操作符栈voidinitOpStack(OpStack*s,intcapacity){s->data=(char*)malloc(capacity*sizeof(char));s->top=-1;s->capacity=capacity;}//入栈voidopPush(OpStack*s,charop){if(s->top< s->capacity-1){s->data[++s->top]=op;}}//出栈charopPop(OpStack*s){if(s->top==-1)return'\0';returns->data[s->top--];}//取栈顶charopTop(OpStack*s){if(s->top==-1)return'\0';returns->data[s->top];}//判空intisOpStackEmpty(OpStack*s){returns->top==-1;}//初始化操作数栈voidinitNumStack(NumStack*s,intcapacity){s->data=(int*)malloc(capacity*sizeof(int));s->top=-1;s->capacity=capacity;}//入栈voidnumPush(NumStack*s,intnum){if(s->top< s->capacity-1){s->data[++s->top]=num;}}//出栈intnumPop(NumStack*s){if(s->top==-1)return0;returns->data[s->top--];}//取栈顶intnumTop(NumStack*s){if(s->top==-1)return0;returns->data[s->top];}//判空intisNumStackEmpty(NumStack*s){returns->top==-1;}//优先级函数intpriority(charop){if(op=='+'||op=='-')return1;if(op=='*'||op=='/')return2;return0;//其他}//表达式分割函数Queue*tokenize(char*expr){Queue*tokenQueue=(Queue*)malloc(sizeof(Queue));initQueue(tokenQueue);inti=0;intprevType=-1;//0:num,1:op,2:lparen,3:rparen,-1:nonewhile(expr[i]!='\0'){if(isspace(expr[i])){i++;continue;}if(isdigit(expr[i])){//读取整个数字intval=0;while(isdigit(expr[i])){val=val*10+(expr[i]-'0');i++;}TokennumToken;numToken.type=TOKEN_NUM;numToken.value=val;enqueue(tokenQueue,numToken);prevType=TOKEN_NUM;}elseif(expr[i]=='+'||expr[i]=='*'||expr[i]=='/'){TokenopToken;opToken.type=TOKEN_OP;opToken.op=expr[i];enqueue(tokenQueue,opToken);prevType=TOKEN_OP;i++;}elseif(expr[i]=='('){TokenlparenToken;lparenToken.type=TOKEN_LPAREN;lparenToken.op='(';enqueue(tokenQueue,lparenToken);prevType=TOKEN_LPAREN;i++;}elseif(expr[i]==')'){TokenrparenToken;rparenToken.type=TOKEN_RPAREN;rparenToken.op=')';enqueue(tokenQueue,rparenToken);prevType=TOKEN_RPAREN;i++;}elseif(expr[i]=='-'){//判断是一元负号还是二元减号if(prevType==-1||prevType==TOKEN_LPAREN||prevType==TOKEN_OP){//一元负号:插入0和减号Tokenzero;zero.type=TOKEN_NUM;zero.value=0;enqueue(tokenQueue,zero);Tokenminus;minus.type=TOKEN_OP;minus.op='-';enqueue(tokenQueue,minus);prevType=TOKEN_OP;i++;//跳过负号}else{//二元减号TokenopToken;opToken.type=TOKEN_OP;opToken.op='-';enqueue(tokenQueue,opToken);prevType=TOKEN_OP;i++;}}else{printf("Invalidcharacter:%c\n",expr[i]);exit(1);}}returntokenQueue;}//计算函数intevaluate(Queue*tokenQueue){//初始化栈intcapacity=100;NumStacknumStack;OpStackopStack;initNumStack(&numStack,capacity);initOpStack(&opStack,capacity);while(!isQueueEmpty(tokenQueue)){Tokentoken=dequeue(tokenQueue);if(token.type==TOKEN_NUM){numPush(&numStack,token.value);}elseif(token.type==TOKEN_LPAREN){opPush(&opStack,token.op);}elseif(token.type==TOKEN_RPAREN){//弹栈直到遇到左括号while(opTop(&opStack)!='('&&!isOpStackEmpty(&opStack)){charop=opPop(&opStack);intb=numPop(&numStack);inta=numPop(&numStack);if(op=='+')numPush(&numStack,a+b);elseif(op=='-')numPush(&numStack,a-b);elseif(op=='*')numPush(&numStack,a*b);elseif(op=='/'){if(b==0){printf("Error:divisionbyzero\n");exit(1);}numPush(&numStack,a/b);}}//弹出左括号if(!isOpStackEmpty(&opStack)&&opTop(&opStack)=='('){opPop(&opStack);}else{printf("Mismatchedparentheses\n");exit(1);}}elseif(token.type==TOKEN_OP){while(!isOpStackEmpty(&opStack)&&opTop(&opStack)!='('&&priority(opTop(&opStack))>=priority(token.op)){charop=opPop(&opStack);intb=numPop(&numStack);inta=numPop(&numStack);if(op=='+')numPush(&numStack,a+b);elseif(op=='-')numPush(&numStack,a-b);elseif(op=='*')numPush(&numStack,a*b);elseif(op=='/'){if(b==0){printf("Error:divisionbyzero\n");exit(1);}numPush(&numStack,a/b);}}opPush(&opStack,token.op);}}//处理栈中剩余操作符while(!isOpStackEmpty(&opStack)){charop=opPop(&opStack);intb=numPop(&numStack);inta=numPop(&numStack);if(op=='+')numPush(&numStack,a+b);elseif(op=='-')numPush(&numStack,a-b);elseif(op=='*')numPush(&numStack,a*b);elseif(op=='/'){if(b==0){printf("Error:divisionbyzero\n");exit(1);}numPush(&numStack,a/b);}}//最终结果intresult=numPop(&numStack);if(!isNumStackEmpty(&numStack)){printf("Error:toomanynumbers\n");exit(1);}//释放栈内存free(numStack.data);free(opStack.data);returnresult;}//主函数intmain(){charexpr[100];printf("Inputanexpression:");fgets(expr,100,stdin);expr[strlen(expr)-1]='\0';//去除换行符Queue*tokenQueue=tokenize(expr);intresult=evaluate(tokenQueue);printf("Result:%d\n",result);//释放队列内存while(!isQueueEmpty(tokenQueue)){dequeue(tokenQueue);}free(tokenQueue);return0;}注意:上述代码中,负号的处理(包括一元负号)已经加入。如果表达式中有空格,会被忽略。这个代码可以处理整数、加减乘除、括号和负数(一元负号)。然而,这个代码没有释放队列中的所有节点(在tokenize中分配的节点)。我们在主函数中最后释放队列节点:但是,evaluate中已经将队列中的token出队并释放了节点(dequeue中有free),所以队列在evaluate后为空。但在主函数中,我们还需要将队列结构释放。注意:dequeue中会释放节点,所以当evaluate完成后,队列中的所有节点已经被释放,队列为空。因此,我们只需要free(tokenQueue)。但由于我们在tokenize中malloc了一个Queue结构,所以我们应当在主函数中释放它。但需要确保队列中节点已经被释放:evaluate函数会调用dequeue,将队列中的节点释放。但是,如果表达式为空,队列中也可能有节点没有被释放,因此我们用一个while循环释放队列中剩余的节点:while(!isQueueEmpty(tokenQueue)){dequeue(tokenQueue);}然后再free(tokenQueue);这个代码是基础的实现,可能没有处理所有异常情况(比如括号匹配),但基本功能已经实现。由于时间,我们只能给出这个版本。可以进一步测试。最后,生成相关问题:1.如何处理浮点数四则运算?2.如何扩展该计算器以支持函数(如sin、cos)?3.表达式语法错误检测的方法有哪些?4.如何将中缀表达式转换为其他形式(如前缀表达式)?5.栈和队列在数据结构中还有哪些应用场景?</think> 下面我将使用队列和C语言实现包含加减乘除及括号的四则运算表达式计算器。算法主要分为两个阶段:表达式分割为token队列和基于双栈的计算过程。 ### 算法设计与实现 #### 1. 核心数据结构 ```c #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <stdbool.h> // Token类型 typedef enum { NUMBER, OPERATOR, LPAREN, RPAREN } TokenType; // Token结构体 typedef struct Token { TokenType type; double value; // 数值 char op; // 操作符 } Token; // 队列节点 typedef struct QueueNode { Token token; struct QueueNode* next; } QueueNode; // 队列结构 typedef struct { QueueNode* front; QueueNode* rear; } TokenQueue; // 数值栈 typedef struct { double* data; int top; int size; } NumStack; // 操作符栈 typedef struct { char* data; int top; int size; } OpStack; ``` #### 2. 队列操作函数 ```c TokenQueue* createQueue() { TokenQueue* q = (TokenQueue*)malloc(sizeof(TokenQueue)); q->front = q->rear = NULL; return q; } void enqueue(TokenQueue* q, Token token) { QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode)); newNode->token = token; newNode->next = NULL; if (q->rear == NULL) { q->front = q->rear = newNode; } else { q->rear->next = newNode; q->rear = newNode; } } Token dequeue(TokenQueue* q) { if (q->front == NULL) return (Token){NUMBER, 0, ' '}; QueueNode* temp = q->front; Token token = temp->token; q->front = q->front->next; if (q->front == NULL) q->rear = NULL; free(temp); return token; } bool isQueueEmpty(TokenQueue* q) { return q->front == NULL; } ``` #### 3. 栈操作函数 ```c NumStack* createNumStack(int size) { NumStack* s = (NumStack*)malloc(sizeof(NumStack)); s->data = (double*)malloc(size * sizeof(double)); s->top = -1; s->size = size; return s; } void pushNum(NumStack* s, double val) { s->data[++s->top] = val; } double popNum(NumStack* s) { return s->data[s->top--]; } OpStack* createOpStack(int size) { OpStack* s = (OpStack*)malloc(sizeof(OpStack)); s->data = (char*)malloc(size * sizeof(char)); s->top = -1; s->size = size; return s; } void pushOp(OpStack* s, char op) { s->data[++s->top] = op; } char popOp(OpStack* s) { return s->data[s->top--]; } char peekOp(OpStack* s) { return s->top >= 0 ? s->data[s->top] : '\0'; } ``` #### 4. 表达式分割(中缀转队列) ```c TokenQueue* parseExpression(const char* expr) { TokenQueue* queue = createQueue(); int i = 0; bool expectOperator = false; // 标记下一个期望的token类型 while (expr[i] != '\0') { if (isspace(expr[i])) { i++; continue; } // 处理数字(整数和小数) if (isdigit(expr[i]) || expr[i] == '.') { double value = 0; int decimal = 0; bool isDecimal = false; double factor = 1.0; while (isdigit(expr[i]) || expr[i] == '.') { if (expr[i] == '.') { isDecimal = true; i++; continue; } if (!isDecimal) { value = value * 10 + (expr[i] - '0'); } else { factor *= 0.1; value += factor * (expr[i] - '0'); decimal++; } i++; } enqueue(queue, (Token){NUMBER, value, ' '}); expectOperator = true; // 数字后应接操作符 i--; // 补偿while循环的i++ } // 处理左括号 else if (expr[i] == '(') { enqueue(queue, (Token){LPAREN, 0, '('}); expectOperator = false; // 左括号后可接数字或一元负号 } // 处理右括号 else if (expr[i] == ')') { enqueue(queue, (Token){RPAREN, 0, ')'}); expectOperator = true; // 右括号后应接操作符 } // 处理负号(一元/二元区分) else if (expr[i] == '-' && !expectOperator) { // 一元负号处理:添加0和负号 [^1][^2] enqueue(queue, (Token){NUMBER, 0, ' '}); enqueue(queue, (Token){OPERATOR, 0, '-'}); } // 处理运算符 else if (strchr("+-*/", expr[i]) && expectOperator) { enqueue(queue, (Token){OPERATOR, 0, expr[i]}); expectOperator = false; // 操作符后应接数字/左括号 } i++; } return queue; } ``` #### 5. 计算引擎(基于双栈) ```c double calculate(TokenQueue* queue) { NumStack* numStack = createNumStack(100); OpStack* opStack = createOpStack(100); double result = 0; while (!isQueueEmpty(queue)) { Token token = dequeue(queue); if (token.type == NUMBER) { pushNum(numStack, token.value); } else if (token.type == LPAREN) { pushOp(opStack, '('); } else if (token.type == RPAREN) { while (peekOp(opStack) != '(') { executeOperation(numStack, opStack); } popOp(opStack); // 弹出'(' } else if (token.type == OPERATOR) { while (!isOpStackEmpty(opStack) && getPriority(peekOp(opStack)) >= getPriority(token.op) && peekOp(opStack) != '(') { executeOperation(numStack, opStack); } pushOp(opStack, token.op); } } while (!isOpStackEmpty(opStack)) { executeOperation(numStack, opStack); } result = popNum(numStack); free(numStack->data); free(opStack->data); free(numStack); free(opStack); return result; } // 执行操作 void executeOperation(NumStack* numStack, OpStack* opStack) { char op = popOp(opStack); double b = popNum(numStack); double a = popNum(numStack); switch (op) { case '+': pushNum(numStack, a + b); break; case '-': pushNum(numStack, a - b); break; case '*': pushNum(numStack, a * b); break; case '/': if (b == 0) { fprintf(stderr, "Error: Division by zero\n"); exit(EXIT_FAILURE); } pushNum(numStack, a / b); break; } } // 操作符优先级 int getPriority(char op) { if (op == '+' || op == '-') return 1; if (op == '*' || op == '/') return 2; return 0; } ``` #### 6. 主函数 ```c int main() { char expression[100]; printf("Enter expression: "); fgets(expression, sizeof(expression), stdin); TokenQueue* queue = parseExpression(expression); double result = calculate(queue); // 释放队列内存 while (!isQueueEmpty(queue)) dequeue(queue); free(queue); printf("Result: %f\n", result); return 0; } ``` ### 算法特点 1. **双阶段处理**:先分割表达式为token队列,再用双栈计算 2. **队列应用**:存储表达式元素实现顺序处理 [^4] 3. **栈应用**: - 数值栈存储操作数 - 操作符栈处理优先级和括号 4. **边界处理**: - 一元负号转化为`(0 - x)`格式 [^1][^2] - 检测除零错误 - 自动跳过空白字符 5. **支持特性**: - 整数/小数 - 括号嵌套 - 负数表达式 ### 示例计算 ``` 表达式: 3.5 + (2 * (10 - 4)) / 3 步骤: 1. 队列: [3.5, '+', '(', 2, '*', '(', 10, '-', 4, ')', ')', '/', 3] 2. 计算: 10 - 4 = 6 2 * 6 = 12 12 / 3 = 4 3.5 + 4 = 7.5 输出: 7.5 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值