首页 > 其他分享 >数据结构——栈

数据结构——栈

时间:2023-02-01 18:03:25浏览次数:44  
标签:index return 栈顶 数据结构 data public size

简介

限定仅在表尾进行插入和删除操作的线性表。允许插入和删除的一端成为栈顶,另一端成为栈低,不含任何元素的栈成为空栈,栈又称为先进先出的线性表,简称LIFO结构。

 

栈的插入操作,叫做进栈,也称压栈,入栈。

栈的删除操作,也叫出战,也有的叫做弹栈。

栈的附加功能

  • Peep 窥视:返回堆栈的栈顶元素(不删除)
  • isEmpty:检查堆栈是否为空。
  • isFull:检查堆栈是否已经满了。 

栈的存储表示方法:

  • 顺序栈:利用顺序存储结构实现的栈(①利用一组地址连续的存储单元一次存放从栈底到栈顶的数据元素;②附设指针top指向栈顶元素的位置,base指针指向栈底元素位置;③采用动态分配原则)  

    • 初始化:为顺序栈分配一个数组空间(stacksize,栈的最大容量),base和top同时指向栈底,表示空栈。
    • 入栈:判断栈是否已满,满则报错,否则将元素压入栈顶,top加一。
    • 出栈:判断栈是否为空,空则报错,否则将top减一,栈元素出栈。  
  • 链栈:采用链式存储结构实现的栈,通常用单链表表示(无头节点)。 

    • 初始化:构造一个空栈,无头结点,将栈顶指针置为空。
    • 入栈:为新的栈顶元素分配结点空间,将新结点插入到栈顶,修改栈顶指针。 
    • 出栈:判断栈是否为空,空则报错,否则取栈顶元素,修改栈顶指针,释放原栈顶元素空间。

顺序栈与链栈的优缺点:

  • 顺序栈受到最大空间容量的限制,在满员时扩容工作量较大,在无法估计栈可能达到的最大容量时应使用链栈。   

栈的应用:

  • 表达式评估。
  • 递归编程中实现函数调用。    

入栈

入栈操作

出栈

出栈操作

实现一个栈

栈主要包含两个操作,入栈和出栈,也就是在栈顶插入一个数据和从栈顶删除一个数据。栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。 根据上一节我们实现的数组代码我们来实现一个自己的栈。

栈的抽象接口

public interface Stack<E> {

    /**
     * 栈的容量
     * @return
     */
    int getSize();

    /**
     * 栈是否为空
     * @return
     */
    boolean isEmpty();

    /**
     * 向栈中添加元素
     * @param e
     */
    void push(E e);

    /**
     * 向栈中取出元素
     * @return
     */
    E pop();

    /**
     * 查看栈顶的元素
     * @return
     */
    E peek();
}

数组栈

public class ArrayStack<E> implements Stack<E> {

    private E[] data;

    private int size;

    public ArrayStack(){
        this(10);
    }

    public ArrayStack(int capacity){
        this.data = (E[]) new Object[capacity];
        this.size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 获取栈的容量
     * @return
     */
    public int getCapacity(){
        return data.length;
    }

    @Override
    public void push(E e) {
        addLast(e);
    }

    @Override
    public E pop() {
        return removeLast();
    }

    @Override
    public E peek() {
        return get(size - 1);
    }

    /**
     * 获取指定索引位置的值
     * @param index
     * @return
     */
    private E get(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Get failed. index is illegal.");
        }
        return data[index];
    }

    /**
     * 数组尾部新增元素
     * @param e
     */
    private void addLast(E e){
        add(size, e);
    }

    /**
     * 在指定位置插入元素
     * @param index
     * @param e
     */
    private void add(int index, E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("AddLast failed. require index >=0 and index <= size");
        }
        if(size == data.length){
            //扩容
            resize(2 * data.length);
        }

        for(int i = size - 1; i >= index; i --){
            data[i + 1] = data[i];
        }
        data[index] = e;
        size ++;
    }

    /**
     * 数组扩容
     * @param newCapacity
     */
    private void resize(int newCapacity){
        E[] newData = (E[])new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    /**
     * 删除栈中最后一个元素
     * @return
     */
    private E removeLast(){
        return remove(size - 1);
    }

    /**
     * 删除栈数组中index位置的元素, 并返回删除的元素
     * @param index
     * @return
     */
    private E remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Remove failed. index is illegal.");
        }
        E ret = data[index];
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size --;
        data[size] = null;
        if(size == data.length / 4 && data.length / 2 != 0){
            //当数组长度缩小为原数组的4分之一的时候才进行数组的缩容,
            //缩小为原数组的2分之一,预留空间,防止有数据添加导致扩容浪费性能
            resize(data.length / 2);
        }
        return ret;
    }

    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("Stack: ");
        sb.append("[");
        for (int i = 0; i < size; i++) {
            sb.append(data[i]);
            if(i != size - 1){
                sb.append(", ");
            }
        }
        sb.append("] top");
        return sb.toString();
    }
}

链表栈

public class LinkedListStack<E> implements Stack<E> {

    private class Node<E>{
        public E e;

        public Node<E> next;

        public Node(E e){
            this.e = e;
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }

    private Node<E> dummyHead;

    private Integer size;

    public LinkedListStack(){
        dummyHead = new Node<>(null);
        size = 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void push(E e) {
        Node<E> node = new Node<>(e);
        node.next = dummyHead.next;
        dummyHead.next = node;
        size++;
    }

    @Override
    public E pop() {
        if(isEmpty()){
            throw new IllegalArgumentException("Cannot pop from an empty stack.");
        }
        Node<E> prev = dummyHead;
        Node<E> retNode = prev.next;
        prev.next = retNode.next;
        retNode.next = null;
        size -- ;
        return retNode.e;
    }

    @Override
    public E peek() {
        if(isEmpty()){
            throw new IllegalArgumentException("Cannot pop from an empty stack.");
        }
        return dummyHead.next.e;
    }

    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("Stack: top ");
        Node<E> cur = dummyHead.next;
        while (cur != null){
            sb.append(cur + "->");
            cur = cur.next;
        }
        sb.append("NULL");
        return sb.toString();
    }
}

栈应用

函数应用

操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。

栈的应用函数调用

表达式求值

我将算术表达式简化为只包含加减乘除四则运算,比如:34+13*9+44-12/3。对于这个四则运算编译器就是通过两个栈来实现的。其中一个保存操作数的栈,另一个是保存运算符的栈。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。

 

如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算 完的结果压入操作数栈,继续比较。

栈的表达式

括号匹配

我们假设表达式中只包含三种括号,圆括号 ()、方括号 [] 和花括号{},并且它们可以任意嵌套。比如,{[{}]}或 [{()}([])] 等都为合法格式,而{[}()] 或 [({)] 为不合法的格式。在给你一个包含三种括号的表达式字符串。我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比 如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不 能配对的右括号,或者栈中没有数据,则说明为非法格式。当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。

/**
 * @Author: huangyibo
 * @Date: 2021/12/26 18:58
 * @Description:
 * 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
 *
 * 有效字符串需满足:
 *
 * 左括号必须用相同类型的右括号闭合。
 * 左括号必须以正确的顺序闭合。
 * 注意空字符串可被认为是有效字符串。
 */
public class LeetCodeQuestion {

    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<Character>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if(c == '(' || c == '{' || c == '{'){
                stack.push(c);
            }else {
                if(stack.isEmpty()){
                    return false;
                }
                Character pop = stack.pop();
                if(c == ')' && pop != '('){
                    return false;
                }
                if(c == '}' && pop != '{'){
                    return false;
                }
                if(c == ']' && pop != '['){
                    return false;
                }
            }
        }
        //如果栈里面还有字符的话,那么也不行,因为意味着还有字符没办法匹配
        return stack.isEmpty();
    }
}

参考: https://blog.csdn.net/weixin_39084521/article/details/89820132

https://www.cnblogs.com/smallzhen/p/14152557.html

标签:index,return,栈顶,数据结构,data,public,size
From: https://blog.51cto.com/u_14014612/6031729

相关文章

  • 数据结构——链表
    链表数组要一块连续的内存空间来存储,对内存的要求比较高。如果我们申请一个100MB大小的数组,当内存中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于100MB,......
  • [数据结构] 根据前中后序遍历中的两种构造二叉树
    前中后序遍历前中后序遍历的特点前序遍历前序遍历顺序:根节点->左子树->右子树前序遍历结果:[根节点,[左子树前序遍历结果],[右子树前序遍历结果]]假如把前序遍历结果......
  • Python-数据结构
    数据结构:数据是一个抽象的概念,将其进行分类后得到程序设计语言中的基本类型。如int/float/char等。数据元素之间不是独立的,存在特点的关系,这些关系便是结构。数据结构指数......
  • 网络协议中的数据结构
    我对Context和Handler是最迷惑的,什么玩意儿,用他就实现通信啦??没有直观理解,不知道到底扮演一个什么角色。1、Context上下文可以从里面获取session等对象,是个容器。准确......
  • 掌握hashtable,深度理解python字典的数据结构
    文章目录​​1.hash函数​​​​2.hashtable​​​​2.1链地址法实现hashtable​​​​2.2解决冲突​​​​2.3开放寻址法实现hashtable​​​​2.4逻辑删除key​​​......
  • python实用小技之数据结构
     本文大多数例子搬自pythoncookbook这里是对学习的一个总结和提炼ps:python版本为python3 1.解压序列赋值给多个变量#有一个包含N个元素的元组或者是序列,怎样将......
  • 【数据结构和算法】Trie树简介及应用详解
    作者:京东物流马瑞1什么是Trie树1.1Trie树的概念Trie树,即字典树,又称单词查找树或键树,是一种树形结构,典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以......
  • [数据结构]二叉树的前中后序遍历(递归+迭代实现)
    二叉树的遍历主要的三种遍历方式二叉树主要的遍历方式有前序遍历、中序遍历和后序遍历。(1)前序遍历:根节点-->左子树-->右子树(2)中序遍历:左子树-->根节点-->右子树(3)后序......
  • C/C++晋中理工学院数据结构[2023-01-30]
    C/C++晋中理工学院数据结构[2023-01-30]晋中理工学院数据结构实验周任务书2022-2023学年第1学期学院: 信创与大数据学院专业: 学生姓名: 学号......
  • 怎么高效学习数据结构和算法?
    5个步骤:基础语法学习—>语法配套练习—>数据结构—>算法入门—>算法进阶一、数据结构官方解释:​​数据结构​​是一门研究非数值计算的程序设计问题中的额操作对象,以及他们......