数据结构中,针对线性表包含两种结构,一种是顺序线性表,一种是链表。顺序线性表适用于查询,时间复杂度为O(1),增删的时间复杂度为O(n).链表适用于增删,时间复杂度为O(1),查询的时间复杂度为O(n).

栈可以说是特殊的线性表,因为栈拥有线性表的基础特征基础上,有一些特殊的要求,比如后进先出,即每次插入的元素只能放在栈顶,每次弹出值也只能弹出栈顶。同样的,栈分成顺序栈和链栈。本篇内容为顺序栈的实现以及简单应用。

顺序栈可以应用到很多的地方,比如递归运算,语法检查(比如括号匹配问题),数值转换(十进制转换成其他进制),四则运算等等。

栈在java中有现有的封装的类,但是在apex中貌似没有已经封装的类,我们可以针对其功能进行自行的封装。顺序栈是顺序线性表的特殊情况,所以说实现上可以使用数组来实现。

一.顺序栈的实现

针对栈的类应该有以下的构造函数及方法:

1.构造函数:设计成了三种,无参设置默认长度,传入默认长度,以及传默认长度并且指定此栈为固定长度还是动态扩展;

2.empty:判断此栈是否为空栈;

3.peek:返回栈顶元素,栈顶元素指针不减一;

4.push:入栈,栈顶元素指针加一;

5.pop:出栈,栈顶元素减一;

6.search:搜索obj在栈的位置,大于0说明存在;

7.toString:重写stack默认返回的内容。

顺序栈类应该还有其他的方法,比如destroy等,有兴趣的可以自行填充。

Stack类的代码设计如下:

 public without sharing class Stack {

     //数据集
private Object[] datas{get;set;}
//栈最大容量
private Integer maxSize{get;set;}
//栈顶指针
private Integer topIndex{get;set;}
//是否允许动态扩展栈的容量
private Boolean allowExtension{get;set;}
//默认扩展容量大小
private final Integer DEFAULT_EXTENSION_SIZE = 5; public Stack() {
this(5);
} public Stack(Integer stackSize) {
this(stackSize,false);
} public Stack(Integer stackSize,Boolean allowStackExtension) {
if(stackSize > 0) {
datas = new Object[stackSize];
maxSize = stackSize;
topIndex = -1;
allowExtension = allowStackExtension;
} else {
//TODO throw exception
//栈容量必须大于0
throw new StackException('栈容量必须大于0');
}
} public Boolean empty() {
return topIndex == -1 ? true : false;
} public Object peek() {
if(topIndex == -1) {
//TODO throw exception
//空栈无法获取栈顶值
throw new StackException('空栈无法获取栈顶值');
}
return datas[topIndex];
} public Object push(Object obj) {
if(topIndex == maxSize - 1) {
if(allowExtension) {
datas = copyOf(maxSize + DEFAULT_EXTENSION_SIZE);
} else {
//TODO 栈已满,无法入栈
throw new StackException('栈已满,无法入栈');
}
}
datas[++topIndex] = obj;
return obj;
} public Object pop() {
if(topIndex == -1) {
//TODO 空栈,无法出栈
throw new StackException('空栈无法获取栈顶值');
} Object popObj = datas[topIndex];
datas[topIndex] = null;
topIndex -=1;
return popObj;
} public Integer search(Object obj) {
Integer i=topIndex;
while(i != -1){
if(datas[i] != obj) {
i--;
} else {
break;
}
}
return i + 1;
} private Object[] copyOf(Integer newStackSize) {
Object[] tempObjs = new Object[newStackSize];
for(Integer i = 0;i < datas.size();i++) {
tempObjs[i] = datas[i];
}
return tempObjs;
} override public String toString() {
List<Object> objs = new List<Object>();
for(Object obj : datas) {
if(obj != null) {
objs.add(obj);
}
}
return String.valueOf(objs);
} public class StackException extends Exception{ }
}

二.顺序栈的简单应用

顺序栈可以应用到很多场景,demo来一个简单的四则运算。此四则运算考虑的东西比较少,没有对细节进行完善,目前仅支持 + - * /  以及整数的操作,返回的结果为double类型的值。

来一个简单的四则运算的例子:1 + 2 + 3 * 4 - 8 / 5 * 2 + 3 - 1

此表达式为中缀表示法--运算符均在数字中间。我们需要以一定的规则转换成后缀表达式,这便用到了栈的知识。

1).中缀表达式转换成后缀表达式

中缀表达式转换成后缀表达式规则为将运算符放在空栈里面:

1.当栈为空情况下,第一个运算符入栈;

2.当前的运算符优先级如果比栈顶元素高,则入栈;

3.当前的运算符如果比栈顶元素低,则将栈中从栈顶开始所有连续的高于当前运算符的元素出栈,然后将当前运算符入栈;

4.当表达式结束后,将栈中所有的元素弹出。

原始表达式:1 + 2 + 3 * 4 - 8 / 5 * 2 + 3 - 1

第一轮:1是数字,所以不进入栈,直接弹出;  内容1

第二轮:+是运算符,因为栈为空,所以直接入栈  内容1      栈:+

第三轮:2是数字,所以不进入栈,直接弹出;  内容1 2      栈: +

第四轮:+是运算符,优先级不如栈顶元素,将栈顶元素+弹出,并将当前的+入栈  内容1 2 +    栈:+

第五轮:3是数字,不进入栈,直接弹出  内容1 2 + 3     栈:+

第六轮:*是运算符,因为优先级比栈顶元素+高,所以入栈  内容1 2 + 3     栈:+ *

第七轮:4是数字,不进入栈,直接弹出  内容1 2 + 3 4   栈:+ *

第八轮:-是运算符,因为优先级比栈顶元素* 以及相邻元素+优先级低,所以* + 出栈,-入栈  内容1 2 + 3 4 * +    栈:-

第九轮:8是数字,不进入栈,直接弹出  内容1 2 + 3 4 * + 8   栈:-

第十轮:/是运算符,因为优先级比栈顶元素-高,所以入栈  内容1 2 + 3 4 * + 8   栈:- /

第十一轮:5是数字,不进入栈,直接弹出  内容1 2 + 3 4 * + 8 5   栈:- /

第十二轮:*是运算符,优先级比栈顶元素低,但是比-高,所以/出栈,*入栈  内容1 2 + 3 4 * + 8 5 /   栈:- *

第十三轮:2是数字,不进入栈,直接弹出  内容1 2 + 3 4 * + 8 5 / 2  栈:- *

第十四轮:+是运算符,优先级比* - 低,所以 * -出栈,+入栈   内容1 2 + 3 4 * + 8 5 / 2 * -  栈: +

第十五轮:3是数字,不进入栈,直接弹出  内容1 2 + 3 4 * + 8 5 / 2 * - 3 栈: +

第十六轮:-是运算符,优先级比+低,所以+出栈,-入栈  内容1 2 + 3 4 * + 8 5 / 2 * - 3 + 栈: -

第十七轮:1是数字,不进入栈,直接弹出  内容1 2 + 3 4 * + 8 5 / 2 * - 3 + 1 栈: -

第十八轮,表达式已结束,将栈所有元素弹出  内容1 2 + 3 4 * + 8 5 / 2 * - 3 + 1 -

所以此表达式转换成后缀表达式的结果为: 1 2 + 3 4 * + 8 5 / 2 * - 3 + 1 -

 2)后栈表达式求结果

后栈表达式为运算符在数字的后面,规则为将数字放到栈里,遇到运算符则把栈顶的前两个元素拿出来进行运算,并把结果值放入栈顶,重复操作,直到表达式运算到最后,栈里只有一个值,即最终的结果。

原始表达式:1 2 + 3 4 * + 8 5 / 2 * - 3 + 1 -

第一轮:1是数字,当前栈为空栈,入栈   栈 : 1

第二轮:2是数字,入栈  栈:1 2

第三轮:+是运算符,弹出位于栈顶前两个内容进行相加,结果为3入栈  栈:3

第四轮:3是数字,入栈  栈:3 3

第四轮:4是数字,入栈  栈:3 3 4

第五轮:*是运算符,弹出位于栈顶前两个内容进行相乘,结果为12入栈  栈:3 12

第六轮:+是运算符,弹出位于栈顶前两个内容进行相加,结果为15入栈  栈:15

第七轮:8是数字,入栈  栈:15 8

第八轮:5是数字,入栈  栈: 15 8 5

第九轮:/是运算符,弹出位于栈顶前两个内容进行相除,结果为1.6入栈  栈:15 1.6

第十轮:2是数字,入栈  栈: 15 1.6 2

第十一轮:*是运算符,弹出位于栈顶前两个内容进行相乘,结果为3.2入栈  栈:15 3.2

第十二轮:-是运算符,弹出位于栈顶前两个内容进行相减,结果为11.8入栈  栈:11.8

第十三轮:3是数字,入栈  栈:11.8 3

第十四轮:+是运算符,弹出位于栈顶前两个内容进行相加,结果为14.8入栈  栈:14.8

第十五轮:1是数字,入栈  栈:14.8 1

第十六轮:-是运算符,弹出位于栈顶前两个内容进行相减,结果为15.8入栈  栈:13.8

运算结束,结果为13.8

代码实现:

 public with sharing class MathUtil {

     private static Set<String> symbolSet = new Set<String>{'+','-','*','/'};

     private static Integer compareTo(String stackTopValue,String compareValue) {
Integer result;
if(stackTopValue == '+' || stackTopValue == '-') {
if(compareValue == '+' || compareValue == '-') {
result = -1;
} else if(compareValue == '*' || compareValue == '/') {
result = 1;
}
} else if(stackTopValue == '*' || stackTopValue == '/') {
return -1;
}
return result;
} //将表达式从中缀表示法转换成后缀表示法
//eg : 1 + 2 + 3 * 4 - 10 / 5 * 2 + 3 - 1 ==> 1 2 + 3 4 * + 10 5 / 2 * - 3+ 1-
private static String transferToPostFixNotation(String inFixNotationContent) {
String result = '';
Stack symbolStack = new Stack(10,true);
Boolean previousCharacterIsNumric = true;
Integer[] chars = inFixNotationContent.getChars();
for(Integer charInteger : chars) {
String tempChar = String.fromCharArray(new List<Integer>{charInteger}); if(symbolSet.contains(tempChar)) {
if(symbolStack.empty()) {
symbolStack.push(tempChar);
} else {
String stackTopValue = (String)symbolStack.peek();
if(compareTo(stackTopValue,tempChar) > 0) {
symbolStack.push(tempChar);
} else {
Boolean enablePop = true;
//将所有栈中优先级比当前的符号高的出栈
while(enablePop) {
if(!symbolStack.empty()) {
String symbolStackPop = (String)symbolStack.peek();
if(compareTo(symbolStackPop,tempChar) < 0) {
symbolStackPop = (String)symbolStack.pop();
result += symbolStackPop;
} else {
enablePop = false;
}
} else {
enablePop = false;
}
}
symbolStack.push(tempChar);
}
}
} else if(tempChar.isWhitespace()) {
continue;
} else {
if(previousCharacterIsNumric) {
result += tempChar;
} else {
result += ' ' + tempChar;
}
}
if(tempChar.isNumeric() || tempChar == '.') {
previousCharacterIsNumric = true;
} else {
previousCharacterIsNumric = false;
}
}
while(!symbolStack.empty()) {
result += (String)symbolStack.pop();
}
return result;
} public static Double calculate(String inFixNotationContent) {
String postFixNotationContent = transferToPostFixNotation(inFixNotationContent);
Stack numricStack = new Stack(10,true);
Integer[] chars = postFixNotationContent.getChars();
Boolean previousCharacterIsNumric = true;
for(Integer charInteger : chars) {
String character = String.fromCharArray(new List<Integer>{charInteger});
if(character.isNumeric()) {
if(!numricStack.empty()) {
if(previousCharacterIsNumric) {
character = (String)numricStack.pop() + character;
}
}
numricStack.push(character);
} else if(character == ' ') {
previousCharacterIsNumric = false;
continue;
} else if(symbolSet.contains(character)){
Double number1 = Double.valueOf(numricStack.pop());
Double number2 = Double.valueOf(numricStack.pop());
Double result;
if(character.equals('+')) {
result = number2 + number1;
} else if(character.equals('-')) {
result = number2 - number1;
} else if(character.equals('*')) {
result = number2 * number1;
} else if(character.equals('/')) {
result = number2 / number1;
}
numricStack.push(String.valueOf(result));
}
if(character.isNumeric()) {
previousCharacterIsNumric = true;
} else {
previousCharacterIsNumric = false;
}
}
String result = numricStack.toString().remove('(').remove(')');
return Double.valueOf(result);
} }

执行结果:

String test = '1 + 2 + 3 * 4 - 8 / 5 * 2 + 3 - 1';
System.debug('result :' + MathUtil.calculate(test));

总结:此篇只是简单的进行了顺序栈的实现,有好多方法没有封装,有用到顺序栈的小伙伴可以自行优化。四则运算没有考虑表达式校验,小数情况以及具有括号情况,有兴趣的自行优化。篇中有错误的地方欢迎指出,有问题欢迎留言。

salesforce零基础学习(七十六)顺序栈的实现以及应用的更多相关文章

  1. salesforce 零基础学习(十六)Validation Rules & Date/time

    上一篇介绍的内容为Formula,其中的Date/time部分未指出,此篇主要介绍Date/time部分以及Validation rules. 本篇参考PDF: Date/time:https://r ...

  2. salesforce零基础学习(九十六)Platform Event浅谈

    本篇参考:https://developer.salesforce.com/blogs/2018/07/which-streaming-event-do-i-use.html https://trai ...

  3. salesforce零基础学习(九十六)项目中的零碎知识点小总结(四)

    本篇参考: https://developer.salesforce.com/docs/atlas.en-us.216.0.apexcode.meta/apexcode/apex_classes_ke ...

  4. salesforce 零基础学习(十九)Permission sets 讲解及设置

    Permission sets以及Profile是常见的设置访问权限的方式. Profile规则为'who see what'.通过Profile可以将一类的用户设置相同的访问权限.对于有着相同Pro ...

  5. salesforce 零基础学习(十八)WorkFlow介绍及用法

    说起workflow大家肯定都不陌生,这里简单介绍一下salesforce中什么情况下使用workflow. 当你分配许多任务,定期发送电子邮件,记录修改时,可以通过自动配置workflow来完成以上 ...

  6. salesforce零基础学习(一百零五)Change Data Capture

    本篇参考: https://developer.salesforce.com/docs/atlas.en-us.232.0.api_streaming.meta/api_streaming/using ...

  7. salesforce 零基础学习(六十八)http callout test class写法

    此篇可以参考: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_restfu ...

  8. salesforce零基础学习(八十)使用autoComplete 输入内容自动联想结果以及去重实现

    项目中,我们有时候会需要实现自动联想功能,比如我们想输入用户或者联系人名称,去联想出系统中有的相关的用户和联系人,当点击以后获取相关的邮箱或者其他信息等等.这种情况下可以使用jquery ui中的au ...

  9. salesforce零基础学习(八十二)审批邮件获取最终审批人和审批意见

    项目中,审批操作无处不在.配置审批流时,我们有时候会用到queue,related user设置当前步骤的审批人,审批人可以一个或者多个.当审批人有多个时,邮件中获取当前记录的审批人和审批意见就不能随 ...

  10. salesforce零基础学习(八十九)使用 input type=file 以及RemoteAction方式上传附件

    在classic环境中,salesforce提供了<apex:inputFile>标签用来实现附件的上传以及内容获取.salesforce 零基础学习(二十四)解析csv格式内容中有类似的 ...

随机推荐

  1. 如何提取Redis中的大KEY

    工作中,经常有些Redis实例使用不恰当,或者对业务预估不准确,或者key没有及时进行处理等等原因,导致某些KEY相当大. 那么大Key会带来哪些问题呢? 如果是集群模式下,无法做到负载均衡,导致请求 ...

  2. Linux命令 文件备份归档恢复

    cp [功能说明] 文件的备份 英文xxxx  #cp命令将源文件复制到另外安全的地方,复制的文件和源文件是两个相互独立的文件,对认识一个文件的操作不影响另一个文件,但与符号链接文件中的硬链接是有区别 ...

  3. PHP设计模式:抽象工厂

    示例代码详见https://github.com/52fhy/design_patterns 抽象工厂 抽象工厂(Abstract Factory)是应对产品族概念的.比如说,每个汽车公司可能要同时生 ...

  4. scanner--inputstreamreader--console对比

    1 JDK 1.4 及以下版本读取的方法 JDK 1.4 及以下的版本中要想从控制台中输入数据只有一种办法,即使用System.in获得系统的输入流,再桥接至字符流从字符流中读入数据.示例代码如下: ...

  5. 【Android Developers Training】 2. 运行你的应用

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  6. JavaScript中的排序

    <script> //1. 冒泡排序 function bubbleSort(arr) { var len = arr.length; for (var i = 0; i < len ...

  7. Javacript 学习笔记

    一.初探 javacript 学习无法是围绕着对象和属性两个方面来兜圈子,万变不离其宗. 在js中,能点出来的,或者中括号里面的必然是属性(方法).数组除外. 对象调用属性! 对象调用属性! 对象调用 ...

  8. 浅析ConcurrentHashMap

    一.导论 这些天一直在看关于多线程和高并发的书籍,也对jdk中的并发措施了解了些许,看到concurrentHashMap的时候感觉知识点很乱,有必要写篇博客整理记录一下. 当资源在多线程下共享时会产 ...

  9. mybatis学习笔记(二)-- 使用mybatisUtil工具类体验基于xml和注解实现

    项目结构  基础入门可参考:mybatis学习笔记(一)-- 简单入门(附测试Demo详细过程) 开始体验 1.新建项目,新建类MybatisUtil.java,路径:src/util/Mybatis ...

  10. 打包可执行的jar

    #配置项目路径 *除程序文件以外,其他相关素材也可以打包进jar,但在内部访问时需以包名作为跟路径,如hello/xxx/yyy.zzz mkdir hello vi hello/HelloWorld ...