【数据结构】栈--实现判断括号的匹配 / 后缀and前缀表达式的计算(c++代码)

目录

判断括号的匹配

思路

代码如下

后缀and前缀表达式的计算

定义

实现计算的思路

!!注意

代码如下


P19-P21笔记

视频教程地址👇

http://www.bilibili.com/video/BV1Fv4y1f7T1?p=21&vd_source=02dfd57080e8f31bc9c4a323c13dd49c

这两部分的东西都属于栈的一些应用。

判断括号的匹配

思路

看到这个问题,在实际生活中我们是怎么判断括号是不是正确匹配呢?我们通常会从前到后一个一个看,如下图,(哈哈感觉我习惯通过例子来说明一件事情,数学线代什么的好像都是做几道例题就明白了hhhh)

从左到右直观的就看出来是不是匹配了,在我们的大脑中默认进行了很多操作。我们要思考的就是把他们用文字先梳理出来,接着用代码实现。

我们判断的关键有两个:一是括号的数量是否匹配,二是括号的顺序是否正确。

从左到右,看到一个左括号,我们都会默默记下来,然后继续往后看,出现一个右括号,我们会判断他是否与距离它最近的左括号匹配,如果匹配就是正确的,并且这一对已经匹配的括号就不用看了,接下来再遇到右括号就是判断上一个左括号了.....

这种思想有种先进后出的意思,因为我们扫描到的括号是与其距离最近的括号是否匹配,而不是最先存下的那个括号,当判断完匹配之后,又将与其距离最近的括号抹去不看。而与其距离最近的括号又是后出现的。由此我们可以想到用栈来实现。(哎感觉写的不好,好啰嗦,我不知道怎么引入了[哭])。

思路:创建一个栈,用来存储读取到的左括号,遇到右括号的就将其与栈顶的那个左括号进行是否匹配的判断。当读取完整个字符串之后,如果栈为空,那么说明括号正确匹配了。

代码实现上:当扫描到左括号,就直接入栈;当扫描到右括号,就有两种情况了:如果其与栈顶元素匹配,那么就将栈顶元素出栈,如果不匹配或者栈为空的时候就直接返回,因为已经说明该字符串括号不匹配了。

在视频中,老师并没有详细说明如何判断是否匹配,这里我采用的方式是直白的用 | | 全部罗列来判断[笑]

代码如下

#include<iostream>
#include<stack>
#include<cstring>
using namespace std;

bool isMatch(char A[],int n)
{
    char x1='}',y1=')',z1=']',x2='{',y2='(',z2='[';
    stack<char> S;
    for(int i=0;i<n;i++)
    {
        if(A[i]=='{' || A[i]=='(' || A[i]=='[')S.push(A[i]);
        else{//如果栈中为空或者不匹配 就return 否则就出栈
            if(S.empty() || (A[i]==x1 && S.top()!=x2) || (A[i]==y1 && S.top()!=y2) || (A[i]==z1 && S.top()!=z2)) return 0;
            else S.pop();
        }
    }
    //整个数组中的元素遍历完之后,就要判断是否完全匹配了。而判断条件就是 栈是否为空
    return S.empty();
}
int main()
{
    char A[51];
    printf("Enter a string: ");
    gets(A);
    bool ans=isMatch(A,strlen(A));
    cout<<ans;
    return 0;
}

这里栈这个类相关的就不在多说了。

我多使用了#include<cstring> 的头文件,是因为 计算字符串长度的 srelen 函数包含在其中。

老师的代码如下👇

http://gist.github.com/mycodeschool/7207410

主要不同就是在判断是否匹配那里,但我觉得大同小异改不改都行。

后缀and前缀表达式的计算

定义

我们平时写的式子都是中缀表达式,即操作符 '+' '-' '*' '/' 都是写在两个操作数之间。这样符合我们人的习惯,对于我们来说可读性强,容易理解,我们根据 操作符的优先级 和 结合性(括号什么的) 来运算。但是 对其进行无歧义的分析和求值并不是很容易。于是出现了前缀表达式和后缀表达式,这样可以实现 没有括号也可以无歧义的分析,也不需要关心优先级和结合性。

如下图 

前缀表达式:操作符都写在两个操作数前面。且从右向左读取

后缀表达式:操作符都写在两个操作数后面。从左向右读取

注意,我们将中缀表达式转化成这两种的时候,是根据中缀表达式中我们计算的优先级和结合性得出先后顺序的

这样会使可读性降低,但是对于计算机来说不用加括号就节省了内存,更适合机器体质的表达式写法hhh

实现计算的思路

先看后缀表达式。

如图这是一个完整的转换过程。这个应该不用过多解释,可以写几个自己练练转换即可。我主要就阐述一下计算机是如何根据后缀表达式实现计算得出结果的。

根据后缀表达式的思想,我们从左到右读取,遇到操作符,就是其前面紧挨着的两个操作数的运算规则,将其计算得出这一部分的结果之后,就将这个结果代替这一个运算式子进行接下来的操作。循环往复,直到字符串结束。

思路就是:首先创建一个栈,用来存放读取到的操作数,接着遍历整个字符串,当扫描到的是操作数,就直接入栈,如果是操作符,就出栈执行运算,并将运算结果再次压栈。

!!注意

1.读取数字之间需要隔开,以作区分,我这里采用空格区分,所以在扫描到空格的时候,就跳过,继续执行下面的即可。

2.我对多位数的运算也包含其中,所以我定义的栈的类型是string,并且定义了一个字符数组,存放多位数字符,在扫描到是操作数的时候,进行了while循环,当该字符接下来的字符仍是数字并且在字符串范围之内的时候,就不断存入数组,最后将整个字符数组作为整体压入栈,只是这里需要将‘123’这样的字符转化为123int类型的数据,我偷个懒直接搜索了相关的函数使用hh,stoi()函数,其中参数为需要转化的字符串。最后将整个计算结果压栈的时候,又需要将其转化为字符串to_string()

在这一步,老师的代码为了简便只处理了单个字符的操作数。

3.从左到右两个操作数的顺序,应该是先出栈的在右边,后出栈的在左边。这是因为减法和除法的左右两个操作数的位置是不能变的。当然这针对后缀表达式。前缀表达式由于是从右到左扫描,所以先出栈的本身就在右边,与我们本来的想法一致,所以不需要交换

4.扫描到操作符,执行运算这一步单独分装一个函数对四种不同操作符的运算进行罗列。在除法运算时,注意分母不为零

5.这次需要函数返回啊,栈的数据类型啊什么的比较多,写的时候要注意返回类型和转化类型。细心!!

代码如下

后缀表达式计算

#include <iostream>
#include <cstring>
#include <stack>
using namespace std;

int calculate(int x, int y, char z)
{
    if (z == '+')
        return x + y;
    else if (z == '-')
        return x - y;
    else if (z == '*')
        return x * y;
    else
    {
        if (y == 0)
        {
            cout << "error";
            return 0;
        }
        return x / y;
    }
}

int Suffix(char A[], int n)
{
    int op1, op2, res;

    stack<string> S;

    for (int i = 0; i < n; i++)
    {
        char ch[] = {0};
        int j = 0;
        // 判断是否需要入栈
        if (A[i] == ' ')
            continue;//continue的用法回顾
        else if (A[i] >= 48 && A[i] <= 57)//ASCII码
        {
            // while(A[++i]!=' ')//之前没想到写错啦
            while (i < n && A[i] != ' ')//注意循环出口
            {
                ch[j++] = A[i++];
            }
            S.push(ch);
        }
        else
        {
            op1 = stoi(S.top());
            S.pop();
            if (S.empty())
            {
                cout << "error";
                return 0;
            }
            op2 = stoi(S.top()); // 转化为int型数据
            S.pop();
            res = calculate(op2, op1, A[i]);//注意传参的顺序
            S.push(to_string(res));
        }
    }
    return stoi(S.top());//遍历完之后,剩下的唯一一个元素就是计算结果
}
int main()
{
    char A[101];
    printf("Enter a string: ");
    gets(A);
    int ans = Suffix(A, strlen(A));
    cout << ans;
    return 0;
}

前缀表达式在其基础上修改了一点,大部分是一样的,我这里只附上不同的代码。修改的部分都标有注释

int Prefix(char A[],int n)
{
    int op1, op2, res;
    stack<string> S;
    for (int i = n-1; i >= 0; i--)//从右向左扫描
    {
        char ch[] = {0};
        int j = 0;

        if (A[i] == ' ')
            continue;
        else if (A[i] >= 48 && A[i] <= 57)
        {
            // while(A[++i]!=' ')
            while (i >= 0 && A[i] != ' ')//条件修改
            {
                ch[j++] = A[i--];
            }
            S.push(ch);
        }
        else
        {
            op1 = stoi(S.top());
            S.pop();
            if (S.empty())
            {
                cout << "error";
                return 0;
            }
            op2 = stoi(S.top()); 
            S.pop();
            res = calculate(op1, op2, A[i]);//不需要更改顺序
            S.push(to_string(res));
        }
    }
    return stoi(S.top());
}

这里和老师的代码也是有不同的。

老师的代码地址在这👇 

http://gist.github.com/mycodeschool/7702441


ok啦,至此这两种表达式的计算都已经写完啦。这次就先到这把,去干饭了hh

如果有问题欢迎指出,非常感谢!!

也欢迎交流建议哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值