读一读

//出队
public bool DeQueue(ref T element) {
    if (this.isEmpty()) {
        Console.WriteLine("队列为空!");
        return false;
    }

    element = this._data[this._front];
    //指标向前移动
    this._front = (this._front + 1) % (this._maxLength + 1);
    return true;
}


队头指标向后移,同样需要注意队头指标超过数组下标,需要回到数组开头的情况


//入队
public bool EnQueue(T element) {
    if (this.isFull()) {
        Console.WriteLine("队列已满!");
        return false;
    }

    this._data[this._rear] = element;
    //指标向下移动
    this._rear = (this._rear + 1) % (this._maxLength + 1);
    return true;
}


队尾指标再到达数组最后面的时候,需要从头来过


public bool isEmpty()
{
    if (this._rear == this._front)
    {
        return true;
    }

    return false;
}

public bool isFull()
{
    int next = (this._rear + 1) % (this._maxLength + 1);
    if (next == this._front)
    {
        return true;
    }

    return false;
}

队空的时候很简单,那就是队头和队尾指标等于在一起了

但是队满的情况,队头队尾相等?队尾在队头的下一位?不行

所以,这里留空了一位来判断队满,就是队尾可以在那个留空位上,下一位就一定是队头

因为是循环队列,所以队尾的下一位的表示方式不能简单的+1,要考虑的情况:

循环队列与队列的链式结构循环队列与队列的链式结构

所以用+1再求余的方式来判断是否队满。


队列同样也是一种受限的线性表,是一种先进先出的线性结构。

循环队列是属于顺序存储结构里面很好的实现方式。使用两个指标:队头和头尾指标来构成在数组上的循环使用。这里的队尾指标实际指向的是队尾的下一个元素,作用就是用来判断队列的空满,同时也不影响添加元素。

循环队列与队列的链式结构

//队列的顺序存储结构-循环队列
class QueueCycleList<T>
{
    private T[] _data;
    private int _front;
    private int _rear;
    private int _maxLength = 24;

    public QueueCycleList()
    {
        //留一个空位,用于判断队满的情况
        this._data = new T[this._maxLength + 1];
        _front = _rear = 0;
    }

    public QueueCycleList(int length)
    {
        if (length > 0)
        {
            this._maxLength = length;
        }

        this._data = new T[this._maxLength + 1];
        this._front = this._rear = 0;
    }
}

规则:从左遍历后缀表达式

①如果是数字就入栈

②遇到运算符号,就出栈两个数

③进行运算,先出栈的数在右

public string GoResult() {
    int Length = this._laterString.GetListLength();
    if (Length <= 0) {
        Console.WriteLine("后缀表达式为空!");
        return "";
    }
            
    string e = "";
    //遍历整个后缀表达式
    for (int i = 1; i <= Length; i++) {
        this._laterString.GetElement(i, ref e);

        //如果为符号,出栈两个进行计算
        if (e == "+") {
            string termElement = "";
            this._midStack.Pop(ref termElement);
            double e1 = Double.Parse(termElement);

            this._midStack.Pop(ref termElement);
            double e2 = Double.Parse(termElement);

            double result = e2 + e1;
            this._midStack.Push(result.ToString());
            continue;
        }

        if (e == "-") {
            string termElement = "";
            this._midStack.Pop(ref termElement);
            double e1 = Double.Parse(termElement);

            this._midStack.Pop(ref termElement);
            double e2 = Double.Parse(termElement);

            double result = e2 - e1;
            this._midStack.Push(result.ToString());
            continue;
        }

        if (e == "*")
        {
            string termElement = "";
            this._midStack.Pop(ref termElement);
            double e1 = Double.Parse(termElement);

            this._midStack.Pop(ref termElement);
            double e2 = Double.Parse(termElement);

            double result = e2 * e1;
            this._midStack.Push(result.ToString());
            continue;
        }

        if (e == "/")
        {
            string termElement = "";
            this._midStack.Pop(ref termElement);
            double e1 = Double.Parse(termElement);

            this._midStack.Pop(ref termElement);
            double e2 = Double.Parse(termElement);

            if (e1 == 0) {
                Console.WriteLine("除数不能为0!");
                return "";
            }
            double result = e2 / e1;
            this._midStack.Push(result.ToString());
            continue;
        }

        //剩下为数字,入栈
        this._midStack.Push(e);
    }

    //栈中会留有一个元素,那就是结果了
    string r = "";
    this._midStack.Pop(ref r);
    return r;
}

规则:从左到右遍历中缀表达式,

①如果是数字就直接输出成为后缀表达式的一部分;

②如果是运算符号,则判断它与栈顶的优先级,如果栈为空或栈顶符号优先级低于等于它或是左括号,就将它入栈,否则输出所有优先级大于等于它的运算符号直到栈空或遇到左括号。

③如果是左括号,直接入栈。

④如果是右括号,匹配到前面的左括号,输出之间的运算符号

//计算出后缀表达式
private bool TranformMidToLater() {

    int eleLength = this._elements.GetListLength();

    if (eleLength == 0) {
        return false;
    }

    for (int i = 1; i <= eleLength; i++)
    {
        string element = "";
        this._elements.GetElement(i, ref element);

        if (element == "(")
        {
            this._midStack.Push(element);
            continue;
        }

        if (element == ")")
        {
            //匹配前面的左括号
            string e = "";
            this._midStack.Pop(ref e);
            while (e != "(")
            {
                this._laterString.Add(e);
                this._midStack.Pop(ref e);
            }
            continue;
        }

        if (element == "+" || element == "-")
        {

            if (this._midStack.GetLength() == 0)
            {
                //栈中没有元素
                this._midStack.Push(element);
                continue;
            }

            //获取栈顶元素
            string e = "";
            this._midStack.GetTop(ref e);
            if (e == "*" || e == "/")
            {
                //栈顶符号优先级较高,输出
                while (e != "(" && this._midStack.GetLength() != 0)
                {
                    this._midStack.Pop(ref e);
                    this._laterString.Add(e);
                    this._midStack.GetTop(ref e);
                }
            }

            this._midStack.Push(element);
            continue;
        }

        if (element == "*" || element == "/")
        {
            this._midStack.Push(element);
            continue;
        }

        //剩下就是数字了
        //直接输出
        this._laterString.Add(element);

        //判断是否为最后一个数字
        if (i == eleLength) {
            //将中专栈中的符号全部输出
            while (this._midStack.GetLength() > 0) {
                string e = "";
                this._midStack.Pop(ref e);
                this._laterString.Add(e);
            }
        }

    }

    return true;
}

private bool Match() {
    int index = 0;
    int exprLength = this._expression.Length;
    int lKuo = 0;
    int rKuo = 0;

    while (index != exprLength) {
        char who = this._expression[index];
        char next = '\0';
        if (index != exprLength-1)
        {
            next = this._expression[index + 1];
        }
                
                
        switch (who)
        {
            case '(':
                if (index == exprLength - 1 || isRightKuo(next) || isSymbol(next)) {
                    return false;
                }
                lKuo++;

                //保证前面的左括号多于右括号
                if (rKuo >= lKuo) {
                    return false;
                }

                this._elements.Add("(");
                index++;
                break;

            case ')':
                rKuo++;
                if (index == exprLength - 1) {
                    this._elements.Add(")");
                    index++;
                    break;
                }

                if (index == 0 || isLeftKuo(next) || isNumber(next)) {
                    return false;
                }
                        
                if (rKuo > lKuo) {
                    return false;
                }

                this._elements.Add(")");
                index++;
                break;

            default:
                //如果是符号类型
                if (isSymbol(who)){
                    if (index == 0 || index == exprLength - 1) {
                        return false;
                    }

                    if (isRightKuo(next) || isSymbol(next)) {
                        return false;
                    }

                    this._elements.Add(who.ToString());
                    index++;
                    break;
                }

                        
                if (!isNumber(who)) {
                    return false;
                }

                //剩下为数字,匹配数字的范围
                int numberLenth = 0;
                bool isXiaoShu = false;

                while (isNumber(this._expression[index + numberLenth]) || this._expression[index + numberLenth] == '.') {
                    if (this._expression[index + numberLenth] == '.') {
                        if (isXiaoShu)
                        {
                            //不能有两个小数点以上
                            return false;
                        }
                        else
                        {
                            isXiaoShu = true;
                        }
                    }

                    int myIndex = index + numberLenth;
                    numberLenth++;
                    if (myIndex == exprLength - 1) {
                        //最后一个元素
                        break;
                    }
                            
                }
                string num = this._expression.Substring(index, numberLenth);
                this._elements.Add(num);
                index += numberLenth;
                
                //检查数字的下一个元素
                if(index != exprLength)
                {
                   next = this._expression[index];
                }
                                                        
                if (isLeftKuo(next)) {
                   return false;
                }

                break;
        }
    }

    if (lKuo != rKuo) {
        return false;
    }

    return true;
            
}

向后检查法,只检查当前元素和后面元素的合法性,遍历表达式的每一个元素。

元素类型分为:左括号、右括号,运算符号、数字

左括号,不能是最后一个元素吧。后面不能是右括号和运算符号吧!

右括号,不能是第一个元素吧。后面一定是运算符号吧,其他情况不合格

运算符号,不能是第一和最后一个元素吧。后面不能是运算符号和右括号吧!

数字,后面不能是左括号吧!

匹配元素,主要的就是要把数字匹配出来,只要遇到数字,就一直下一个,直到不是数字为止,注意小数点的出现,只能出现一次,且不是数字的第一位。


普通的我们写出来的2+3运算符号在中间的叫做中缀表达式,通过应用栈转化为后缀表达式,最后通过后缀表达式和应用栈就可以计算出结果了。(博客文章-栈的应用)

class Calculator
{
    private string _expression;
    public MyList<string> _elements;
    private StackList<string> _midStack;
    public MyList<string> _laterString;

    public Calculator() {
        _elements = new MyList<string>(100);
        _midStack = new StackList<string>(100);
        _laterString = new MyList<string>(100);
    }

    //计算表达式
    public string Cal(string expression) {

        this.Clear();//清空那3个线性表
        _expression = expression;
        if (!this.Match()) {
            Console.WriteLine("表达式非法!");
            return "";
        }

        this.TranformMidToLater();
        return this.GoResult();
    }
}


整个计算过程就是:

  1. Match(),匹配计算的元素和检查合法性,例如元素:(、数字、+等;合法性:(后面不能是+号吧?

  2. 中缀表达式转化为后缀表达式。

  3. 利用后缀表达式计算出结构。


每一个方法的运行,我门认为它是入栈,当一个方法调用完,我们认为它出栈。

递归就是自己方法里面调用自己方法,方法里面提供一个退出点。

//斐波那契数列 递归实现  栈的应用
static int Fbi(int i) {

    Console.WriteLine("Fbi(" + i + ") 被调用(入栈)");
    if (i < 2)
    {
        num++;
        Console.WriteLine("Fbi(" + i + ") 调用完毕(出栈)");
        return i == 0 ? 0 : 1;
    }

    int a = Fbi(i - 1);
    int b = Fbi(i - 2);

    Console.WriteLine("Fbi(" + i + ") 调用完毕(出栈)");
    return a + b;
}

栈的应用-递归和计算器


栈是一种受限的线性表,所以栈的链式存储结构和普通线性表的链式结构差不多。

不同的就是,栈的入栈和出栈(添加和删除)只能在头结点处进行,没有尾结点的概念。