//出队 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(); } }
整个计算过程就是:
Match(),匹配计算的元素和检查合法性,例如元素:(、数字、+等;合法性:(后面不能是+号吧?
中缀表达式转化为后缀表达式。
利用后缀表达式计算出结构。
每一个方法的运行,我门认为它是入栈,当一个方法调用完,我们认为它出栈。
递归就是自己方法里面调用自己方法,方法里面提供一个退出点。
//斐波那契数列 递归实现 栈的应用 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; }
栈是一种受限的线性表,所以栈的链式存储结构和普通线性表的链式结构差不多。
不同的就是,栈的入栈和出栈(添加和删除)只能在头结点处进行,没有尾结点的概念。