c# 数据结构与算法分析 --栈与队列

c# 数据结构与算法分析 --栈与队列

栈stack
栈是一种后进后出机制,它只允许访问访问一个数据项,即 栈顶(最后插入的数据项)。它有主要的三种操作:push,向栈内压入值;pop,弹出栈顶的值,即返回栈顶的值,并把它从栈内删除;peek,只返回但不删除栈顶。

概念很容易理解,无非就像给弹匣压子弹等等这种类比,但是像我这样的新手在刚接触到栈的时候总是很迷茫,认为它很难,其实这只是错觉,主要是因为没有搞清楚栈主要用在那些场景。

栈普遍应用于编译器、文本检测、科学计算等等,在编译器中,它用来检测一个函数体等是否为封闭的(括号是否成对等等),在文本检测中,想想你的vs,如果最后没有大括号它就会检测出来,和编译器中一样,在科学计算中,就像商店能买到的那些高级计算器,输入一个算式可以直接计算出来结果,而不像普通计算器中,只能输入数字,然后在按键一步一步计算,以上这些应用场景,归根结底都是对文本的检测,现在知道栈的用途了吧,后面将结合队列一起练练手。

队列queue
顾名思义,就是一堆东西成队排列了,它是先进先出机制(FIFO)与栈相反,栈是后进后出(LIFO)。它的主要操作有:Enqueue,向队尾添加值;Dequeue,返回并移除对头的值;peek,返回但不移除对头的值。

队列很容易理解,它主要应用在网络通信、系统等。windows的所有事件都是放在队列里面的。

最典型的,就是系统的任务分配了,每个进程或线程都分配在某些队列里,方便cpu时间片的分配调度,任务的运行不可能是你最后打开的程序先运行吧,当然它有优先级,这个排除在外。

栈的应用
现在搞清楚了栈和队列的应用场景,那就趁热打铁,练练手。

现在,按部就班的实现一个科学表达式的计算。

首先了解一下计算机是怎么计算类似于 1*(2+3)+4/2 这样的计算。对于大脑来说,这个算式简单到不能再简单了,但是电脑却不是如此,得让它明白那个先算,那个后算,以及哪些是操作符(运算符)哪些不是(例如括号)。

像我们经常使用的这种算式称为 中缀式 ,就是运算符都在两个操作符中间了,这种中缀式对于我们人脑来说,并不是顺序执行的,它可以从中间先开始,比如1+2*3 ,这正是电脑很难理解中缀式的原因,因为他总是顺序执行的。那么,我们就得把中缀式换成 后缀式(postfix)  或称作 逆波兰记法(reverse Polish notation)。

中缀式就是把运算符放到两个操作数后边,让算式保持顺序计算(对于我们人脑也是如此)。具体怎么放,大家看看下面的例子就明白了。

示例 1:

中缀式 后缀式
A+B+C-D A B+C+D-
AB/C+D A BC/D+
A*(B+CD)+E A B C D * + * E +
(A+B)C/(D+E-F) A B + C * D E + F - /
A+B
C+(D
E+F)*G A B C * + D E * F + G * +

中缀到后缀的转换

当读到一个操作数时,立即把它放到输出中,即显示出来。操作符不立即输出,从而必须先存在某个地方,例如栈或变量。当遇到一个左括号时也把它放入栈中。计算是从一个初始化为空的栈开始。
如果栈为空,则将符号入栈。
如果遇见一个右括号,那么就将栈元素弹出并输出,直到遇到一个(相对应的)左括号,但是这个左括号只弹出,并不输出。
如果遇见优先级与栈首元素相同或比较低的符号,则将栈的所有元素弹出并输出,直到遇见一个左括号为止,这个左括号只弹出,并不输出,最后,将遇到的那个符号入栈。
如果遇见优先级与栈首元素高的符号(右括号除外),则把它入栈。
如果遇见右括号,则弹出并输出所有元素,直到遇到一个与之相对应的左括号,这个左括号只弹出不输出。
如果读到末尾了,则将栈中元素弹出并输出,直到栈为空,左括号只弹出不显示
代码实现

private void stack()
{
    
    
    Dictionary<char, int> opreater = new Dictionary<char, int>();  //这个用来存放操作符和他们的优先级

    //3最高
    opreater.Add('+', 1);
    opreater.Add('-', 1);
    opreater.Add('*', 2);
    opreater.Add('/', 2);
    opreater.Add('(', 3);
    opreater.Add(')', 3);


    string m = Console.ReadLine(); //读取一个算式

    Stack<char> opreaters = new Stack<char>(); //这个栈用来存放操作符
    Stack<int> numStack = new Stack<int>(); //这个栈又来存放算数
    Queue<char> mq = new Queue<char>(); //这个队列存放算式
    //将算式字符串的字符一个个插入队列
    for (int i = 0; i < m.Length; i++)
    {
    
    
        mq.Enqueue(m[i]);
    }


    //开始将中缀转换为后缀算式
    Console.WriteLine("开始转换");
    string nums = ""; //存放数字的字符串

    while (mq.Count > 0) //当算式队列没用完则执行
    {
    
                   
        

        if (opreater.ContainsKey(mq.Peek()) == true) //如果下一个字符是操作符的话执行
        {
    
    
            if (nums != "")
            {
    
    
                int num = int.Parse(nums); //将数字字符串转为整形
                nums = "";
                numStack.Push(num); //将这个数字入栈
            }
            if (mq.Peek() == ')') //如果为右括号,则弹出所有元素,直到遇到一个与之相对应的左括号
            {
    
    
                mq.Dequeue(); //让右括号弹出,它已经没什么用了

                while (opreaters.Count > 0)
                {
    
    
                    if (opreaters.Peek() == '(') //如果遇到了左括号,则不打印,直接弹出
                    {
    
                                    
                        opreaters.Pop();
                        break;
                    }
                    else
                        suan(opreaters.Pop(), ref numStack);
                }
            }
            else if (opreaters.Count == 0 || opreater[mq.Peek()] > opreater[opreaters.Peek()] || mq.Peek() == '(') //如果栈为空或者操作符优先级高,则入栈
            {
    
    
                opreaters.Push(mq.Dequeue());
                continue;
            }
            else //如果操作符优先级相等或者比较低,则输出所有操作符直到遇到左括号
            {
    
    
                if (opreaters.Peek() != '(') //这个程序写的逻辑性不太好,写完了到很难在回头分析了,大家不要学我,最忌讳这样了
                    suan(opreaters.Pop(), ref numStack);

                while (opreaters.Count != 0 && opreater[mq.Peek()] >= opreater[opreaters.Peek()]) //如果遇见其他操作符时则弹出打印,直到遇见更低的操作符
                {
    
    
                    if (opreaters.Peek() == '(') //如果为左括号则直接弹出不打印
                    {
    
    
                        opreaters.Pop();
                        break;
                    }
                    else //打印直到遇到左括号
                    {
    
    
                        suan(opreaters.Pop(), ref numStack);

                        if (opreaters.Count != 0 && opreater[mq.Peek()] < opreater[opreaters.Peek()])
                        {
    
    
                            suan(opreaters.Pop(), ref numStack);

                        }
                    }
                }


                opreaters.Push(mq.Dequeue()); //最后,将当前操作符入栈
            }
        }
        else                    
            nums += mq.Dequeue(); //提取字符
    }


    while (opreaters.Count != 0)
    {
    
    
        if (nums != "") //判断算数字符串是否为空
        {
    
    
            int num = int.Parse(nums); //将数字字符串转为整形
            nums = "";
            numStack.Push(num);
        }
        suan (opreaters.Pop(),ref numStack);                

    }
    //读完数据,则将栈内元素全部弹出
    Console.Write(numStack.Pop());
}

/// <summary>
/// 按照运算符运算两个操作数
/// </summary>
/// <param name="opt">运算符</param>
/// <param name="numStack">算数栈</param>
private void suan(char opt,ref Stack<int> numStack)
{
    
    
    int i = 0;
    int j = 0;
    switch (opt)
    {
    
    
        case '+':
            i = numStack.Pop();
            j = numStack.Pop(); 
            numStack.Push(j + i); //将运算结果入栈
            break;
        case '-':
            i = numStack.Pop();
            j = numStack.Pop();
            numStack.Push(j - i);
            break;
        case '*':
            i = numStack.Pop();
            j = numStack.Pop();
            numStack.Push(j * i);
            break;
        case '/':
            i = numStack.Pop();
            j = numStack.Pop();
            numStack.Push(j / i);
            break;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_51803498/article/details/121329948