数据结构基础:栈(Stack)

什么是栈?

    栈是限制插入和删除只能在同一个位置上进行的表,这个位置就是栈的顶端,对于栈的操作主要有三种形式:入栈(将元素插入到表中),出栈(将表最后的元素删除,也就是栈顶的元素),返回栈顶元素。栈有时又叫LIFO(后进先出)表。

栈的实现

栈可以有两种实现方式:1)数组实现 2)链表实现

栈的双向链表实现

    public class StackForLinked<T>{private int num = 0;public int count { get{return num;} }private Node<T> root = null;private class Node<T>{public T data;  public Node<T> prev;public Node<T> next;public Node(T value){this.data = value;this.prev=null;this.next=null;} public Node(T value,Node<T> prev,Node<T> next):this(value){this.prev = prev;this.next=next;}}public void Push(T value){Node<T> node = new Node<T>(value);if(root==null){root = node;}else{var last= GetLastNode();last.next = node;node.prev= last;}num++;}public T Pop(){var node=GetLastNode();if(node==null)throw new Exception();node.prev.next=null;num--;return node.data;}public T GetTop(){var node =GetLastNode();if(node==null)throw new Exception();return node.data;}private Node<T> GetLastNode(){Node<T> p = root;if(root==null){return null;}while(true){if(p.next!=null){p=p.next;}else break;}return p;}}

栈的数组实现

    public class StackForArray<T>{private static int length = 100;private T[] objList = new T[length]; private int currentIndex = 0;public int count { get { return this.currentIndex;} }private void ensureCapcity(int capcity){if(capcity < currentIndex){return;}T[] old = objList;objList = new T[capcity];for(int i=0;i<old.Length;i++){objList[i] = old[i];}}public void Push(T value){if(currentIndex == length){ensureCapcity(currentIndex * 2 +1);}objList[currentIndex] = value;currentIndex++;}public T Pop(){if(currentIndex == 0)throw new Exception();currentIndex--;return objList[currentIndex];}public T GetTop(){if(currentIndex == 0){throw new Exception();}return objList[currentIndex -1];}}

PS:对于链表在添加和删除方面是O(1), 但是在查找方面则是O(N);数组在查找方面是O(1)操作,但是在插入或者是删除方面涉及到表的中元素的位置上的移动,若数据中的元素较多会带来性能上的损失。所以在考虑使用链表还是使用数组进行存储数据的时候,应该考虑上述所说(数据量大小,插入和删除多还是查找的多)。当前这与栈的关系不太大,因为栈都是在一端进行操作,不会涉及元素移动的问题。

栈的应用

栈的应用有很多,这里只举一些简单的例子:
1.平衡符号:例如判断括号是否成对出现,通过将括号入栈然后碰见成对的括号就出栈,最后判断栈中的元素是否为空,为空则是成对出现,反之则不成对
2.中缀表达式转化为后缀表达式:
    中缀表达式和后缀表达式: 例如表达式 a + b c 这就是我们平时所见的正常的数学计算表达式,在数学上我们也知道怎么计算,但是你把这一段表达式告诉计算机,然后计算出结果,那么这个结果是怎么计算出来的呢?这里就涉及一个概念叫做后缀表达式,首先 a + b c 会先通过栈转化为 a b c * + 然后再通过栈进行计算从而得出结果,前者通常叫做中缀表达式。随后会有一段代码的例子,可以让你真正的了解数学表达式是如何通过栈进行计算的
3.方法调用:类似于平衡符号的检测,所有的方法调用和返回都是成对的,在开始调用的时候需要在栈内存储一些信息(位置,对应变量的名字和返回地址等),方法完成后需要从栈中出栈拿到当初存储的信息然后做一些操作,这也正好印证了,当递归调用的时候,不当的递归会出现 Stackoverflow 异常,也就是说在栈(在window下,默认大小是1M )中已经存储不下每一次递归的信息了,就会报这个错误。

PS: 我这里说的都是同步方法,存在一个方法的调用需要等待另一个方法的完成,异步方法还不太了解,期待后续学习会有更多的收获

栈实现基本操作的运算

    用栈来实现简单的基本数学运算:+ ,- ,* , / 和( )在数学中我们都知道这几种符号的优先级,那么计算机时怎么做到的呢? 下面我们以一个表达式为例子来实现一下计算机的计算过程
    表达式:a + b c + ( d e + f ) * g ,除栈外,我们还需要一个输出字符串用来存储生成的后缀表达式,默认情况下为空,出于方便我会使用 [] 代表一个栈,[ 代表栈底,] 代表栈顶,用 | 分割每个元素。

原理

1.对常数输出,对操作符号入栈操作,根据优先级别判断是出栈还是入栈;
2.优先级低的不允许出现在优先级高的后面(栈中存在一个优先级高的符号,此时来了一个低优先级符号,需要进行先把高优先级的符号出栈,然后将低优先级的符号入栈);
3.同优先级的符号先出栈再入栈;
4.对于存在括号的情况,当前栈顶的元素是 ( 那么接下来的一个符号不比较优先级直接入栈,接下来的操作遵循前3条规则;
5.当遇见 )的时候,执行出栈操作直到遇见出栈的第一个( 为止;
6.得到后缀表达式之后再次使用栈进行计算,将后缀表达式中的值入栈,碰见操作符,出栈两个元素执行运算符计算,将计算的结果插入到栈中,直到后缀表达式的尽头,最终结果位于栈顶,出栈即可得到

步骤:

1.将中缀表达式转化为后缀表达式

Step 1 : 我们第一次读取的是常数 a ,直接输出,此时输出的字符串中的值为 a
Step 2 : 继续读取,接下来读取的是操作符号 + ,入栈操作,此时栈中的元素为 [ + ]
Step 3 : 继续读取,接下来读取的是常数 b , 直接输出,此时输出的字符串中的值为 a b
Step 4 : 继续读取,接下来读取的是操作符号 , 入栈操作,此时栈中的元素为 [ + | ]
Step 5 : 继续读取,接下来读取的是常数 c , 直接输出,此时输出的字符串中的值为 a b c
Step 6 : 继续读取,接下来读取的是操作符号 + , 栈顶元素 的优先级高于 + ,将 出栈,之后发现栈顶元素为 +,优先级相同,继续出栈,然后将 + 进栈,此时栈中的元素为[ + ],输出字符串中的值为 a b c * +
Step 7 : 继续读取,接下来读取的是操作符号 ( , 入栈操作,此时栈中的元素为 [ + | ( ]
Step 8 : 继续读取,接下来读取的是常数 d , 直接输出,此时输出的字符串中的值为 a b c * + d
Step 9 : 继续读取,接下来读取的是操作符号 , 入栈操作,此时栈中的元素为[ + | ( | ]
Step 10 : 继续读取,接下来读取的是常数 e , 直接输出,此时输出的字符串中的值为 a b c * + d e
Step 11 : 继续读取,接下来读取的是操作符号 + , 栈顶元素 的优先级高于 + ,先执行出栈操作,在执行 + 入栈操作,此时栈中的元素为[ + | ( | +] , 输出字符串中的值为 a b c + d e *
Step 12 : 继续读取,接下来读取的是常数 f , 直接输出,此时输出的字符串中的值为 a b c + d e f
Step 13 : 继续读取,接下来读取的是操作符号 ) , 开始执行出栈操作,直到遇见第一个出栈的元素为 ( 停止,此时栈中的元素为[ + ] , 输出字符串中的值为 a b c + d e +
Step 14 : 继续读取,接下来读取的是操作符号 , 入栈操作,此时栈中的元素为[ + | ]
Step 15 : 继续读取,接下来读取的是常数 g , 直接输出,此时输出的字符串中的值为 a b c + d e f + g
Step 16: 继续读取,到达了末端,开始执行中出栈操作,直到栈为空,此时输出的字符串中的值为 a b c + d e f + g + ,从而得出后缀表达式为:a b c + d e f + g +

2.计算后缀表达式 (Step中的括号的使用是起到辅助清晰的作用,因为并没给字符实际的值)

Step 1 : 读取后缀表达式,第一个遇到的是 a 执行入栈操作,此时栈中的元素为 [ a ]
Step 2 : 继续读取,解下来遇到的是 b ,执行入栈操作,此时栈中的元素为 [ a | b ]
Step 3 : 继续读取,解下来遇到的是 c ,执行入栈操作,此时栈中的元素为 [ a | b | c ]
Step 4 : 继续读取,解下来遇到的是 ,出栈两个元素,计算 b c ,然后在执行入栈, 此时栈中的元素为 [ a | b * c ]
Step 5 : 继续读取,解下来遇到的是 + ,出栈两个元素,计算 a + b c,然后执行入栈操作,此时栈中的元素为 [ a + b c ]
Step 6 : 继续读取,解下来遇到的是 d ,执行入栈操作,此时栈中的元素为 [ a + b * c | d ]
Step 7 : 继续读取,解下来遇到的是 e ,执行入栈操作,此时栈中的元素为 [ a + b * c | d | e ]
Step 8 : 继续读取,解下来遇到的是 ,出栈两个元素,计算 d e ,然后执行入栈操作,此时栈中的元素为 [ a + b c | d e ]
Step 9 : 继续读取,解下来遇到的是 f ,执行入栈操作,此时栈中的元素为 [ a + b c | d e | f ]
Step 10 : 继续读取,解下来遇到的是 + ,出栈两个元素,计算 d e + f ,然后执行入栈操作,此时栈中的元素为 [ a + b c | d * e + f ]
Step 11 : 继续读取,解下来遇到的是 g ,执行入栈操作,此时栈中的元素为 [ a + b c | d e + f | g ]
Step 12 : 继续读取,解下来遇到的是 ,出栈两个元素,计算 ( d e + f ) g ,然后执行入栈操作,此时栈中的元素为 [ a + b c | ( d e + f ) g ]
Step 13 : 继续读取,解下来遇到的是 + ,出栈两个元素,计算 a + b c + ( d e + f ) g ,然后执行入栈操作,此时栈中的元素为 [ a + b c + ( d e + f ) g ]
Step 14 : 继续读取,到达了末端,执行出栈操作,出栈的结果就是计算的结果 a + b c + ( d e + f ) * g

实现代码

class Program
{static List<Priority> priority = new List<Priority>(){new Priority() { level = 0 , symbal ="+" },new Priority() { level = 0 , symbal ="-" },new Priority() { level = 1 , symbal ="*" },new Priority() { level = 1 , symbal ="/" },new Priority() { level = int.MaxValue , symbal ="(" },new Priority() { level = int.MaxValue , symbal =")" }};static void Main(string[] args){//string expression = "a + b * c + ( d * e + f ) * g";//string expression ="10 + 1 * 10 + ( 2 * 3 + 4 ) * 10"; // 120 //string expression ="1 + 2 + 3 ";string expression ="3 * ( ( 2 - 4 ) / ( 2 - 1 ) ) ";GenerateExpression(expression);Console.WriteLine(OutputStr);Console.WriteLine(CalculateValue());}///生成后缀表达式的栈static StackForArray<Priority> symbalStack = new StackForArray<Priority>();/// 输出字符串static string OutputStr=string.Empty;/// 生成后缀表达式static void GenerateExpression(string expression){string[] symbals = expression.Split(' ');foreach(string symbal in symbals){RecursionExpression(symbal);}while(true){try{var popElement = symbalStack.Pop();OutputStr += popElement.symbal +" ";}catch(Exception ex){break;}}}// 计算表达式的值static int CalculateValue(){StackForArray<int> valueStack = new StackForArray<int>();string[] symbals = OutputStr.Split(new char[]{' '},StringSplitOptions.RemoveEmptyEntries);foreach(var symbal in symbals){var temp = priority.Where(x=>x.symbal ==symbal).FirstOrDefault();if(temp!=null){var t = 0;var second = valueStack.Pop();var first = valueStack.Pop();switch (symbal){case "*":t = first * second;break;case "/":t = first / second;break;case "+":t = first + second;break;case "-":t = first - second;break;default:throw new Exception();}valueStack.Push(t);}else{valueStack.Push(int.Parse(symbal));}}return valueStack.Pop();}/// 递归表达式static void RecursionExpression(string symbal){var temp = priority.Where(x=>x.symbal == symbal).FirstOrDefault();if(temp==null) {OutputStr+= symbal + " ";return;}var stackCount = symbalStack.count;if(stackCount==0 || temp.symbal =="("){symbalStack.Push(temp);return;}if(temp.symbal ==")"){while(true){var popElement = symbalStack.Pop();OutputStr += popElement.symbal +" ";if(popElement.symbal=="("){OutputStr=OutputStr.Substring(0,OutputStr.Length -2);break;}}return;}var topElement = symbalStack.GetTop();if(temp.level > topElement.level || topElement.symbal=="("){symbalStack.Push(temp);return;}if(temp.level <= topElement.level && topElement.symbal != "("){var popElement= symbalStack.Pop();OutputStr += popElement.symbal +" ";RecursionExpression(temp.symbal);return;}} }public class Priority{public string symbal { get; set; }public int level { get; set; }}
}

PS:测试的代码比较简单,主要看思路吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/288499.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Apache错误日志提示AH02004: SSL Proxy: Peer certificate is expired

1 、问题 apache错误日志提示如下 AH02004: SSL Proxy: Peer certificate is expired 接下来日志会打印ssl握手失败 然后抓包分析的时候错误提示如下 Level: Fatal, Description: Certificate Unkonw 2 、open ssl命令探测服务器证书日期 我们用open ssl命令探测服务器证书…

【ArcGIS遇上Python】窗体版Python批量处理地理数据--栅格裁剪

一、说明 之前写过用Python代码块处理地理数据的例子,如裁剪、投影、格式转换等,曾想过用C#制作个窗体,嵌入Python代码,今天无意发现Python也能用tKinter构建简单的窗体,眼前一亮,先写个批量裁剪的例子吧。这样就不用对数据路径改来改去了,只需确定元数据路径,掩膜数据…

缺少linux内核,Linux内核缺页

整个缺页异常的处理过程非常复杂&#xff0c;我们这里只简单介绍一下缺页涉及到的内核函数。当CPU产生一个异常时&#xff0c;将会跳转到异常处理的整个处理流程中。对于缺页异常&#xff0c;CPU将跳转到page_fault异常处理程序中&#xff0c;该异常处理程序会调用do_page_faul…

简单好用的Adapter---ArrayAdapter

2019独角兽企业重金招聘Python工程师标准>>> ListView中比较简单但又非常方便的ArrayAdapter。 ArrayAdapter是BaseAdapter的派生类&#xff0c;在BaseAdapter的基础上&#xff0c;添加了一项重大的功能:可以直接使用泛型构造。 先来看一个简单的例子: Overrideprot…

表单数据自动录入_Excel总表录入、分表自动更新,只要数据透视表和一个快捷键就行...

合并多个分表到一个总表中&#xff0c;这样的操作很常见。但&#xff0c;有时候我们也需要反过来&#xff0c;即将一个总表拆分成多个子表&#xff0c;并且在总表中录入/修改数据后&#xff0c;每个分表都可以自动更新数据。如以下资金流动表。除了资金流水&#xff0c;我们还特…

.net Core中如何限制接口请求次数

.net core中如何限制接口请求次数像AspNetCoreRateLimit这种轮子我前面有给大家介绍过&#xff0c;今天就不说了&#xff0c;我们来聊聊背后的原理&#xff0c;欢迎各位大佬指正&#xff01;像我们经常看的一些APi请求接口网站&#xff1a;拿请求国外主要城市的七日接口举例&am…

linux之setsid命令

1 setsid命令 setsid主要是重新创建一个session,子进程从父进程继承了SessionID、进程组ID和打开的终端,子进程如果要脱离父进程&#xff0c;不受父进程控制&#xff0c;我们可以用这个setsid命令 2 测试 比如我们ping baidu.com setsid ping baidu.com 这个时候我们再ctrl…

【深入JAVA】java注解

在阅读的过程中有不论什么问题&#xff0c;欢迎一起交流 邮箱&#xff1a;1494713801qq.com QQ&#xff1a;1494713801 1、什么是java注解 注解&#xff0c;顾名思义&#xff0c;注解,就是对某一事物进行加入凝视说明&#xff0c;会存放一些信息。这些信息可能对以后某…

vlan跨交换机 udp广播_【详解】VLAN和VXLAN有何区别?VXLAN运用场景有哪些?

随着网络技术的发展&#xff0c;云计算凭借其系统利用率高、人力/管理成本低以及灵活性/扩展性方面展现的优势&#xff0c;已经成为目前各大行业IT建设的新趋势。而服务器的虚拟化作为云计算的核心技术之一&#xff0c;也得到了越来越多的应用&#xff0c;从而极大的增加了数据…

Android开源项目SlidingMenu本学习笔记(两)

我们已经出台SlidingMenu使用&#xff1a;Android开源项目SlidingMenu本学习笔记&#xff08;一个&#xff09;&#xff0c;接下来再深入学习下。依据滑出项的Menu切换到相应的页面 文件夹结构&#xff1a; 点击Bluetooth能够切换到对应的界面 关键代码 MainActivity.java pack…

扒开系统调用的三层皮(下)

5234 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 1.给MenuOS增加time和time-asm命令 步骤 rm menu -rf //强制删除git clonehttp://github.com/menging/menu.git // 克隆相关信息到menucd menumake rootfs //自动编…

Hello Playwright:(1)从开发到部署

前言虽然前面写过几篇 C# 使用 Playwright 的文章&#xff0c;但是讲解的不太全面。看到有朋友有学习这方面的需要&#xff1a;因此&#xff0c;就有了《Hello Playwright》这个系列的产生。Playwright 介绍Playwright 是微软开源的一个基于 Node.js 的工具库&#xff0c;可使用…

没有主清单属性_原神:晴知的主C诺艾尔大型进阶攻略初版

作者&#xff1a;NGA-最可爱的晴知前言大家好我是晴知&#xff0c;一个诺艾尔主C玩家(目前是0命座)&#xff0c;截至10月8日&#xff0c;我的账号等级为41级&#xff0c;深境螺旋11层打通&#xff0c;但星数不够12层&#xff0c;一者因为圣遗物太烂而我想45级再刷圣遗物&#x…

【ArcGIS风暴】ArcGIS栅格数据(分区)统计方法总结

【问题描述】如图所示,如何根据中国植被区划分区,统计各个区域内NDVI的平均值? 目录 1、Layer Properties 2、Zonal Statistics as Table 3、Band Collection Statistics 分区统计及根据统计区域或赋值栅格为每个区域计算统计数据。分区统计包括众数、最大值、均值、中位…

快速合并同一个excel表中的多个sheet

很多朋友会遇到这样的问题,就是很有很多页的数据,少的有几十页,多的可能有几百页,然后需要合并到一个页面做数据分析,如果一页页的复制粘贴的话,就比较麻烦。下面我就介绍一种利用excel的宏计算来解决这个问题。 一、数据准备 二、合并效果

释放内存软件_原来苹果手机这样清理内存,可以释放大量空间,真是太好用了...

相信大家都喜欢用苹果手机&#xff0c;给人感觉就是流畅&#xff0c;但是苹果手机使用久了&#xff0c;内存不足也会导致卡顿的&#xff0c;今天就来教大家清理苹果手机内存的方法&#xff0c;可以释放大量空间。一、正确卸载软件很多人在使用手机时&#xff0c;发现有些软件不…

gitlab永久设置密码

在 .gitconfig 文件中加入&#xff1a; [credential] helper store .git-credentials close address转载于:https://www.cnblogs.com/rhxuza1993/p/7355952.html

程序对拍

在一些特殊的比赛中&#xff0c;我们需要用一个暴力程序来验证一个采取了高效算法的程序是否正确&#xff0c;所以就有了对拍。 对拍程序&#xff1a; echo off :loop rand.exe>data.in std.exe<data.in>std.outmy.exe<data.in>my.out fc my.out std.out …

Xamarin效果第二十三篇之离线语音识别

在前面文章中简单玩了玩GIS的基本操作、Mark相关、AR、测距、加载三维白模、可扩展浮动操作和录音效果;今天抽空再来分享一下最近摸索的基于讯飞的离线语音识别效果,其实就简单用了一下离线命令词识别,看效果:1、先去控制台创建应用:https://console.xfyun.cn/app/myapp2、去组…

linux脚本漏洞,Silver Peak VX跨站脚本漏洞(CVE-2014-2975)

发布日期&#xff1a;2014-07-28更新日期&#xff1a;2014-07-30受影响系统&#xff1a;silver peak Silver Peak VX描述&#xff1a;--------------------------------------------------------------------------------BUGTRAQ ID: 68923CVE(CAN) ID: CVE-2014-2975Silver P…