算法与数据结构 : 栈的实现及运用

本文是在阅读《算法》第四版时的笔记。

栈数据结构遵循 LIFO( last in first out ) 的原则。
它主要有压栈和弹栈这两种操作以及 isEmpty()size() 函数。

LIFO

关于 LIFO 原则,有一个很好的例子:假如你有一堆邮件在桌子上(这堆邮件就是一个栈结构),有新邮件的时候,你会把新邮件放在这一堆邮件的最上面,这个操作就是压栈,新邮件总是处于栈顶,而你要处理这些邮件的时候,会从最上面的开始读,也就是从栈顶开始一封一封的读,读过的邮件,就从这堆邮件里面拿出来,这就是弹栈,弹栈总是对栈顶的数据进行操作。

Java实现一个简单的栈结构

书上目前没看见关于 Stack 具体实现的代码,所以我也是只基于数组实现了 Stack 的基本功能,有不对的地方,请指正,谢谢!!!
这里主要是实现四个方法:

  • Stack() 定义一个栈
  • push(T t) 压栈
  • pop() 弹栈
  • isEmpty() 判断栈是否为空
  • size() 返回栈内数据个数
public class Stack<T> implements Iterable<T> {
    @Override
    public Iterator<T> iterator() {
        return null;
    }
    private ArrayList<T> array = new ArrayList<T>();
    public Stack(){
    }
    public void push(T t){
        this.array.add(t);
    }
    public T pop(){
        return this.array.remove(array.size()-1);
    }
    public boolean isEmpty(){
        return this.array==null;
    }
    public int size(){
        return this.array.size();
    }
}

栈的运用

引用《算法》第四版里的例子。

先看一个数学表达式:

( 1 + ( 2 + 3 ) * ( 4 * 5 ) ) )

这个数学表达式,学过小学数学的都知道计算结果是 101 。
我们把上面的表达式输入到计算机里面,输入的是字符串,要怎样处理,它才能得出 101 这个结果呢?

上世纪 70 年代,E.W.Dijkstra 运用两个栈结构解决了这个问题,一个栈 ( ops ) 用来保存操作符(+,-,*,/,sqrt ),一个栈 ( vals ) 用来保存操作数(数字),同时还得遵循下面这几种原则:

  • 输入表达式的时候,操作数和操作符要用一个空格隔开。
  • 忽略左括号
  • 遇见右括号,栈 ops 弹出一个操作符,栈 vals 弹出必要的操作数,然后将计算结果再压栈操作压入栈 vals 。

看着有点玄幻,本人还买的英文版,对英语没过四级的我来说就更玄幻了,不急,看代码。

public class Evalution {
    public static void main(String[] args) {
        Stack<String> ops = new Stack<String>();
        Stack<Double> vals = new Stack<Double>();
        while (!StdIn.isEmpty()) {
            String s = StdIn.readString();
            if (s.equals("("));
            else if (s.equals("+")) ops.push(s);
            else if (s.equals("-")) ops.push(s);
            else if (s.equals("*")) ops.push(s);
            else if (s.equals("/")) ops.push(s);
            else if (s.equals("sqrt")) ops.push(s);
            else if (s.equals(")")) {
                String op = ops.pop();
                double v = vals.pop();
                if (op.equals("+")) v = vals.pop() + v;
                else if (op.equals("-")) v = vals.pop() - v;
                else if (op.equals("*")) v = vals.pop() * v;
                else if (op.equals("/")) v = vals.pop() / v;
                else if (op.equals("sqrt")) v = Math.sqrt(v);
                vals.push(v);
            }
            else vals.push(Double.parseDouble(s));
        }
        StdOut.println(vals.pop());
    }
}

首先,你会发现在 while 循环的条件里面乱入了一个 StdIn ,别慌,这是《算法》作者自己封装的一个工具类,这本书里面还用到其他的一些作者自己封装的工具类,在这里 (https://introcs.cs.princeton.edu/java/stdlib/) 可以下载到。

接下来,emmm...,while 循环的判断语句这里有点问题,因为这段代码运行下来,输入上面的数学表达式之后,会死循环,很惊喜,很意外,反正网上说是停止符 EOF 这些的问题,可是我是来学算法的,这些问题不管,有网友说输入上面的数学表达式之后回车,然后 Ctrl + D 就可以了,试了一下,行得通。。。

代码逻辑分析

好了终于到了最重要的环节,来分析一下这段代码的逻辑吧,搞懂了其实挺简单的,然而我花了一些时间才搞懂。。。。

嗯,每一次循环,都会读取一个操作数或者操作符,这里可能会问 sqrt 怎么处理的(其实是我自己问的),在上面就说过,操作数和操作符,用空格隔开,这样才不会出错,其实也是因为 Scanner 类的逻辑,这里不讨论。

读取到一个操作符,就是一层层的 if...else if..else... 判断了,如果是左括号就跳过本次循环,执行下一次循环,如果是其他的操作符(比如 + ,- ),就将操作符压入栈 ops ,如果不是操作符,而是操作数,就压入栈 vals 。

上的各种压栈理清楚了,是不是发现当遇见右括号的时候,画风就变了,是的,这是最重要的一步(个人认为),因为,这里就是进行计算的逻辑,嗯,到这里建议再看看前面实现 Stack 的弹栈和压栈的代码逻辑。

这一步可能图解更好,但是书上的图拍下来不清晰,有时间画了再补上来。开始分析之前,再把数学表达式搬过来:

( 1 + ( 2 + 3 ) * ( 4 * 5 ) ) )

就拿遇见的第一个右括号举例,此时,栈 ops 里面有两个元素( +,+ ),栈 vals 里面有三个元素( 1,2,3 ),遇见右括号,ops 弹栈,拿到栈顶元素操作符 + ,然后 vals 弹栈,拿到栈顶操作数 3 ,此时两个栈都经过了一次弹栈, ops 和 vals 的栈顶元素分别是 +2 ,再看代码,又是一串 if 判断语句,并且是利用 ops 的弹栈弹出的元素做判断条件,而 操作数栈,又得弹栈一次,此时 vals 经历了两次弹栈,弹出的数据分别是 32,然后这两个数据进行相应的算数操作,再将两个数算数操作后得出的结果压入栈 vals ,这个时候就相当于是 2 + 3 这一段现在用 5 代替。

随着 while 的再次循环,再遇见右括号,还是进行相同的弹栈压栈操作,最后栈 ops 为空,栈 vals 里面只有一个数,这个数就是最后的计算结果,这个数学表达式,就成功的得出了结果。

思考

经过上面的不专业的瞎BB,我发现这段代码好像只适合小括号里面只有两个操作数的情况,要是把 ( 2 + 3 ) 改成 ( 2 + 3 +6 ),得到的结果是 182.0 ,而不是 221 ,那么,要怎么改善一下,才能避免这种情况呢,我也是写到这里才想起来这个问题,没有代码实现,说说我现在的想法,我觉得不忽略左括号,将左括号压入 ops 栈,然后遇见右括号的时候,ops 就的弹栈直到遇见右括号,这只是个大概的思路。。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容