数据结构之栈

一:概述

    栈的概念其实很好理解,栈是一种用于存储数据的简单数据结构,有点类似链表或者顺序表(统称线性表),栈与线性表的最大区别是数据的存取的操作,我们可以这样认为栈(Stack)是一种特殊的线性表,其插入和删除操作只允许在线性表的一端进行,一般而言,把允许操作的一端称为栈顶(Top),不可操作的一端称为栈底(Bottom),同时把插入元素的操作称为入栈(Push),删除元素的操作称为出栈(Pop)。若栈中没有任何元素,则称为空栈,栈的结构可以通过下图形象的表示:
栈.004.jpeg

    由图我们可看成栈只能从栈顶存取元素,同时先进入的元素反而是后出,而栈顶永远指向栈内最顶部的元素。到此可以给出栈的正式定义:栈(Stack)是一种有序特殊的线性表,只能在表的一端(称为栈顶,top,总是指向栈顶元素)执行插入和删除操作,最后插入的元素将第一个被删除,因此栈也称为后进先出(Last In First Out,LIFO)或先进后出(First In Last Out FILO)的线性表。栈的基本操作创建栈,判空,入栈,出栈,获取栈顶元素等,注意栈不支持对指定位置进行删除,插入,其接口Stack声明如下:

public interface Stack<T> {
    // 栈是否为空
    boolean isEmpty();

    // 入栈
    void push(T data);

    // 取出栈顶元素,不出栈
    T peek();

    // 出栈
    T pop();
}

二:顺序栈的设计与实现
    顺序栈,顾名思义就是采用顺序表实现的的栈,顺序栈的内部以顺序表为基础,实现对元素的存取操作,当然我们还可以采用内部数组实现顺序栈,在这里我们使用内部数据组来实现栈:

public class SeqStack<T> implements Stack<T>{
    //栈顶索引,-1代表空栈
    private int top = -1;

    //默认容量大小
    private int capacity = 10;

    //存放数据的数组
    private T[] arr;

    //栈的实际使用量
    private int size;

    public SeqStack() {
        arr = (T[]) new Object[capacity];
    }

    //是否是空栈的判断依据是top是否为-1;如果大于等于0,说明栈中有元素
    @Override
    public boolean isEmpty() {
        return top == -1;
    }

    //获取栈顶元素,本例基于数组来实现栈,所以栈顶元素,就是数组的最后一个元素
    @Override
    public T peek() {
        //如果是空栈就抛出异常
        if(isEmpty()) {
            new EmptyStackException();
        }
        //返回数组的最后一个元素
        return arr[top];
    }

    //获取栈顶元素并出栈
    @Override
    public T pop() {
        //国际惯例,空栈抛出异常
        if(isEmpty()) {
            new EmptyStackException();
        }
        size --;
        //需要注意的是,这里不会将数组的最后一个元素置空,因为top的值已经修改
        //下次再push的时候只是把原来的数组元素的值进行了修改,因为一旦pop后,
        //top的值减小了,原来的数组的元素永远访问不到,完全没有必要置空.
        return arr[top--];
    }

    //入栈
    @Override
    public void push(T data) {
        //如果栈已满,那么扩容
        if(size == arr.length) {
            ensureCapacity(size*2+1);
        }
        //先将栈顶指针+1,然后将元素放入top + 1那个位置
        arr[++top] = data;
        size++;
    }

    //扩容的思路很简单,创建一个新的数组,然后将原来数组的数据复制到新数组
    private void ensureCapacity(int capacity) {
        //以防万一
        if(capacity > size) {
            return;
        }
        
        T[] old = arr;
        arr = (T[]) new Object[capacity];
        for(int i = 0 ; i < old.length ; i++) {
            arr[i] = old[i];
        }
    }
}

    测试代码:

public static void main(String[] args) {
    SeqStack<String> stack = new SeqStack<>();
    stack.push("A");
    stack.push("B");
    stack.push("C");
    stack.push("D");

    int l = stack.size;
    System.out.println("size : " + l + " , top index : " + stack.top + 
        " , top : " + stack.peek());
        
    for(int i = 0 ; i < l; i ++) {
        stack.pop();
    }
    //此处抛出异常
    System.out.println("size : " + l + " , top : " + stack.peek());
}

    测试结果:

push A , size : 1
push B , size : 2
push C , size : 3
push D , size : 4
size : 4 , top index : 3 , top : D
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
    at teststack.SeqStack.peek(SeqStack.java:58)
    at teststack.SeqStack.main(SeqStack.java:42)

    从上面可以看出,顺序栈的逻辑很简单,就是操作底层的数组而已,所有的入栈和出栈操作,都是往数组添加和删除元素,然后改变top指针的位置。
    以上就是顺序栈的实现,简单。

三:链式栈的设计与实现

    了解完顺序栈,我们接着来看看链式栈,所谓的链式栈(Linked Stack),就是采用链式存储结构的栈,由于我们操作的是栈顶一端,因此这里采用单链表作为基础,直接实现栈的添加,获取,删除等主要操作即可。其操作过程如下图:
5.005.jpeg
    从上图可以看出,链式栈的设计就是头节点就是栈顶元素,所有的入栈和出栈,都是操作头结点,下面用代码来实现:
public class LinkStack<T> implements Stack<T>{
    //栈顶元素
    private Node<T> top;

    private int size;

    public LinkStack() {
        top = new Node<T>();
    }

    //如果栈顶元素为空,或者栈顶元素的数据为空,一律视为空栈
    @Override
    public boolean isEmpty() {
        return top == null || top.data == null;
    }

    @Override
    public void push(T data) {
        if (data == null) {
            try {
                throw new Exception("data can\'t be null");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //如果栈顶节点是空的话,那么创建一个新的节点当做栈顶节点
        if (top == null) {
            top = new Node<>(data);
        //如果栈顶节点的数据为空的话,那么将数据赋值给该节点
        } else if (top.data == null) {
            top.data = data;
        //如果栈顶节点和数据都不为空,说明不是空栈,这时候需要创建一个
        //新的节点,将它赋值给栈顶节点,同时将它的next指向当前的栈顶节点
        } else {
            Node<T> p = new Node<T>(data, this.top);
            top = p;
        }
        size++;
    }

    // 获取栈顶元素,我们一直持有栈顶节点的引用,直接返回好了
    @Override
    public T peek() {
        if (isEmpty()) {
            new EmptyStackException();
        }
        return top.data;
    }

    //出栈,就是将栈顶节点的下一个节点当做新的栈顶节点,
    //实质上就是移除链表中的头结点,很简单
    @Override
    public T pop() {
        if (isEmpty()) {
            new EmptyStackException();
        }

        T data = top.data;
        top = top.next;
        size--;
        return data;
    }

    //节点
    class Node<T> {
        public T data;
        public Node<T> next;

        public Node() {
        }

        public Node(T data) {
            this.data = data;
        }

        public Node(T data, Node<T> next) {
            this.data = data;
            this.next = next;
        }
}

    测试代码:

public static void main(String[] args) {
    LinkStack<String> sl = new LinkStack<>();
    sl.push("A");
    sl.push("B");
    sl.push("C");
    int length = sl.size;
    for (int i = 0; i < length; i++) {
        System.out.println("sl.pop->" + sl.pop() + " , size : " + sl.size);
    }
    sl.push("T");
    System.out.println("sl.pop->" + sl.peek() + " , size : " + sl.size);
}

    测试结果:

sl.pop->C , size : 2
sl.pop->B , size : 1
sl.pop->A , size : 0
sl.pop->T , size : 1

    综上可知,栈的实现还是很简单的,下面讲下栈的应用场景。
四:栈的应用
    栈是一种很重要的数据结构,在计算机中有着很广泛的应用,如下一些操作都应用到了栈:
    符号匹配
    中缀表达式转换为后缀表达式
    计算后缀表达式
    实现函数的嵌套调用
    HTML和XML文件中的标签匹配
    网页浏览器中已访问页面的历史记录

    符号匹配:
    在编写程序的过程中,我们经常会遇到诸如圆括号“()”与花括号“{}”,这些符号都必须是左右匹配的,这就是我们所说的符合匹配类型,当然符合不仅需要个数相等,而且需要先左后右的依次出现,否则就不符合匹配规则,如“)(”,明显是错误的匹配,而“()”才是正确的匹配。有时候符合如括号还会嵌套出现,如“9-(5+(5+1))”,而嵌套的匹配原则是一个右括号与其前面最近的一个括号匹配,事实上编译器帮我检查语法错误是也是执行一样的匹配原理,而这一系列操作都需要借助栈来完成,接下来我们使用栈来实现括号”()”是否匹配的检测。
    判断原则如下(str=”((5-3)*8-2)”):
    a.设置str是一个表达式字符串,从左到右依次对字符串str中的每个字符char进行语法检测,如果char是,左括号则入栈,如果char是右括号则出栈(有一对匹配就可以去匹配一个左括号,因此可以出栈),若此时出栈的字符char为左括号,则说明这一对括号匹配正常,如果此时栈为空或者出栈字符不为左括号,则表示缺少与char匹配的左括号,即目前不完整。
    b.重复执行a操作,直到str检测结束,如果此时栈为空,则全部括号匹配,如果栈中还有左括号,是说明缺少右括号。
    接着我们用栈作为存储容器通过代码来实现这个过程

public class CheckExpression {
 
    public static String isValid(String expstr){
        //创建栈
        LinkedStack<String> stack = new LinkedStack<>();
        int i = 0 ;
        //遍历字符串,挨个取出每个字符
        while(i < expstr.lenght()){
            char c = expstr.charAt(i);
            i++;
            switch (ch){
                //如果该字符是左括号,那么放入栈中
                case '(' :
                    stack.push(ch + "");
                    break;
                //如果是右括号,那么将左括号出栈
                case ")" :
                     if(stack.isEmpty() || !stack.pop().equals("(")) {
                         return "FAILURE";
                     }
             }    
        }
        //经过这么一轮循环,如果栈为空,说明每个左括号都有
        //右括号和他匹配,这时候校验通过,否则不通过
        if(stack.isEmpty()) {
            return "PASS";
        }else {
            return "FAILURE";
        }
    }
}

    测试代码:

String expstr="((5-3)*8-2)";
System.out.println(expstr + " check  " + check(expstr));        
String expstr1="((5-3)*8-2";
System.out.println(expstr1 + " check  " + check(expstr1));

    测试结果:

((5-3)*8-2) check  PASS
((5-3)*8-2 check  FAILURE

摘自:https://blog.csdn.net/javazejian/article/details/53362993

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

推荐阅读更多精彩内容

  • 栈的规则 先进后出。如:依次入栈顺序为:A,B,C,D;怎出栈顺序为:D,C,B,A . 二叉树和表达式 表达式的...
    zhangivon阅读 829评论 0 0
  • 作者原文:http://hawkzz.com/blog/blog/1515054561771 定义 栈是一种特殊的...
    hawkzz阅读 232评论 0 1
  • 什么是栈结构 栈结构是从数据的运算来分类的,也就是说栈结构具有特殊的运算规则.而从数据的逻辑结构来看,栈结构是一种...
    雨飞飞雨阅读 476评论 0 1
  • 大学一个宿舍的一般都会兄弟相称,也是因为关系到了称兄道弟的程度。大学在一个窝里(我称宿舍为窝应该有不少爪爪举起表示...
    水中星火阅读 409评论 3 3
  • 巍巍群山巅, 云雾绕似仙。 笑看风云变, 醉卧天地间。
    未之不生阅读 231评论 0 0