1.. 栈的特点:
  • 栈也是一种线性结构;
  • 相比数组,栈所对应的操作是数组的子集;
  • 栈只能从一端添加元素,也只能从这一端取出元素,这一端通常称之为"栈顶";
  • 向栈中添加元素的过程,称之为"入栈",从栈中取出元素的过程称之为"出栈";
  • 栈的形象化描述如下图:
  • "入栈"的顺序若为1-2-3-4,那么出栈的顺序只能为4-3-2-1,即,栈是一种"后进先出"(Last In First Out)的数据结构;
  • 从用户的角度,只能看到栈顶元素,其它元素对用户是不可见的;
  • 在计算机世界里,"栈"有着不可思意的作用;
2.. 简单举例"栈"的应用
  • 应用程序中的"撤销"(Undo)操作的底层原理就是通过"栈"来实现的;
  • 程序调用的系统栈,通过下图简单示例:
3.. 栈的实现
  • 任务目标如下:
  • Stack<E>
    ·void push(E) //入栈
    ·E pop() // 出栈
    ·E peek() // 查看栈顶元素
    ·int getSize() // 查看栈中共有多少元素
    ·boolean isEmpty() // 查看栈是否为空
  • 需要提一下,从用户的角度来看,只要实现上述操作就好,具体底层实现,用户并不关心,实际上,底层确实有多种实现方式。
  • 我们准备在之前实现的动态数组基础上,来实现"栈"这种数据结构。
  • 先定义一个接口Interface,如下:
  • public interface Stack<E> {  // 支持泛型
    int getSize(); boolean isEmpty(); void push(E e); E pop(); E peek();
    }
  • 然后实现一个ArrayStack类,如下:
  • public class ArrayStack<E> implements Stack<E> {
    Array<E> array; //构造函数
    public ArrayStack(int capacity) {
    array = new Array<>(capacity);
    } //无参数构造函数
    public ArrayStack() {
    array = new Array<>();
    } //实现getSize方法
    @Override
    public int getSize() {
    return array.getSize();
    } //实现isEmpty方法
    @Override
    public boolean isEmpty() {
    return array.isEmpty();
    } //实现getCapacity方法
    public int getCapacity() {
    return array.getCapacity();
    } //实现push方法
    @Override
    public void push(E e) {
    array.addLast(e);
    } //实现pop方法
    @Override
    public E pop() {
    return array.removeLast();
    } //实现peek方法
    public E peek() {
    return array.getLast();
    } //方便打印测试
    @Override
    public String toString() {
    StringBuilder res = new StringBuilder();
    res.append("Stack: ");
    res.append('[');
    for (int i = 0; i < array.getSize(); i++) {
    res.append(array.get(i));
    if (i != array.getSize() - 1) {
    res.append(", ");
    }
    }
    res.append("] top");
    return res.toString();
    }
    }
  • Array类的业务逻辑如下:
  • public class Array<E> {
    
        private E[] data;  //设置为private,不希望用户从外部直接获取这些信息,防止用户篡改数据
    private int size; //构造函数,传入数组的容量capacity构造Array
    public Array(int capacity) {
    data = (E[]) new Object[capacity];
    size = 0;
    } //无参数构造函数,默认数组容量capacity=10
    public Array() {
    this(10); //这里的capacity是IDE自动添加的提示信息,实际不存在
    } //获取数组中的元素个数
    public int getSize() {
    return size;
    } //获取数组的容量
    public int getCapacity() {
    return data.length;
    } //判断数组是否为空
    public boolean isEmpty() {
    return size == 0;
    } //向数组末尾添加一个新元素e
    public void addLast(E e) {
    add(size, e);
    } //向数组开头添加一个新元素e
    public void addFirst(E e) {
    add(0, e);
    } //在index位置插入一个新元素e
    public void add(int index, E e) { if (index < 0 || index > size) {
    throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size");
    }
    if (size == data.length) {
    resize(2 * size); //扩大为原容量的2倍
    }
    for (int i = size - 1; i >= index; i--) {
    data[i + 1] = data[i];
    }
    data[index] = e;
    size++;
    } //获取index位置的元素
    public E get(int index) {
    if (index < 0 || index >= size) {
    throw new IllegalArgumentException("Get failed. Index is illegal.");
    }
    return data[index];
    } //获取最后一个元素
    public E getLast() {
    return get(size - 1);
    } //获取开头的元素
    public E getFirst() {
    return get(0);
    } //修改index位置的元素为e
    public void set(int index, E e) {
    if (index < 0 || index >= size) {
    throw new IllegalArgumentException("Set failed. Index is illegal.");
    }
    data[index] = e;
    } //查找数组中是否存在元素e
    public boolean contains(E e) {
    for (int i = 0; i < size; i++) {
    if (data[i].equals(e)) {
    return true;
    }
    }
    return false;
    } //查看数组中元素e的索引,若找不到元素e,返回-1
    public int find(E e) {
    for (int i = 0; i < size; i++) {
    if (data[i].equals(e)) {
    return i;
    }
    }
    return -1;
    } //删除掉index位置的元素,并且返回删除的元素
    public 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 + 1; i < size; i++) {
    data[i - 1] = data[i];
    }
    size--; //data[size]会指向一个类对象,这部分空间不会被释放loitering objects
    data[size] = null; if (size == data.length / 4 && data.length / 2 != 0) {
    resize(data.length / 2); //被利用的空间等于总空间的一半时,将数组容量减少一半
    }
    return ret;
    } //删除掉数组开头的元素,并返回删除的元素
    public E removeFirst() {
    return remove(0);
    } //删除掉数组末尾的元素,并返回删除的元素
    public E removeLast() {
    return remove(size - 1);
    } //如果数组中有元素e,那么将其删除,否则什么也不做
    public void removeElement(E e) {
    int index = find(e);
    if (index != -1) {
    remove(index);
    }
    } @Override
    public String toString() { //覆盖父类的toString方法 StringBuilder res = new StringBuilder();
    res.append(String.format("Array: size=%d, capacity=%d\n", size, data.length));
    res.append('[');
    for (int i = 0; i < size; i++) {
    res.append(data[i]);
    if (i != size - 1) {
    res.append(", ");
    }
    }
    res.append(']');
    return res.toString();
    } private void resize(int newCapacity) {
    E[] newData = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
    newData[i] = data[i];
    }
    data = newData;
    }
    }

4.. 对我们实现的栈进行测试:

  • public class Main {
    
        public static void main(String[] args) {
    
            ArrayStack<Integer> stack = new ArrayStack<>();
    
            //测试入栈push
    for (int i = 0; i < 5; i++) {
    stack.push(i);
    System.out.println(stack);
    } //测试出栈
    stack.pop();
    System.out.println(stack);
    }
    }
  • 输出结果如下:
  • Stack: [0] top
    Stack: [0, 1] top
    Stack: [0, 1, 2] top
    Stack: [0, 1, 2, 3] top
    Stack: [0, 1, 2, 3, 4] top
    Stack: [0, 1, 2, 3] top

5.. 栈的时间复杂度分析

  • Stack<E>
    ·void push(E) O(1) 均摊
    ·E pop() O(1) 均摊
    ·E peek() O(1)
    ·int getSize() O(1)
    ·boolean isEmpty() O(1)
6.. 栈的另外一个应用——括号匹配
  • 业务逻辑如下:
  • import java.util.Stack;
    
    class Solution {
    public boolean isValid(String s) {
    Stack<Character> stack = new Stack<>();
    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;
    }
    char topChar = stack.pop();
    if (topChar == '(' && c != ')') {
    return false;
    }
    if (topChar == '[' && c != ']') {
    return false;
    }
    if (topChar == '{' && c != '}') {
    return false;
    }
    }
    }
    return stack.isEmpty(); //这里很巧妙
    } //测试
    public static void main(String[] args){
    System.out.println((new Solution()).isValid("()"));
    System.out.println((new Solution()).isValid("()[]}{"));
    System.out.println((new Solution()).isValid("({[]})"));
    System.out.println((new Solution()).isValid("({)}[]")); }
    }

第二十三篇 玩转数据结构——栈(Stack)的更多相关文章

  1. 第二十七篇 玩转数据结构——集合(Set)与映射(Map)

          1.. 集合的应用 集合可以用来去重 集合可以用于进行客户的统计 集合可以用于文本词汇量的统计   2.. 集合的实现 定义集合的接口 Set<E> ·void add(E) ...

  2. 第三十三篇 玩转数据结构——红黑树(Read Black Tree)

    1.. 图解2-3树维持绝对平衡的原理: 2.. 红黑树与2-3树是等价的 3.. 红黑树的特点 简要概括如下: 所有节点非黑即红:根节点为黑:NULL节点为黑:红节点孩子为黑:黑平衡 4.. 实现红 ...

  3. Python开发【第二十三篇】:持续更新中...

    Python开发[第二十三篇]:持续更新中...

  4. 第二十五篇 玩转数据结构——链表(Linked List)

          1.. 链表的重要性 我们之前实现的动态数组.栈.队列,底层都是依托静态数组,靠resize来解决固定容量的问题,而"链表"则是一种真正的动态数据结构,不需要处理固定容 ...

  5. 第二十六篇 玩转数据结构——二分搜索树(Binary Search Tree)

          1.. 二叉树 跟链表一样,二叉树也是一种动态数据结构,即,不需要在创建时指定大小. 跟链表不同的是,二叉树中的每个节点,除了要存放元素e,它还有两个指向其它节点的引用,分别用Node l ...

  6. 第二十八篇 玩转数据结构——堆(Heap)和有优先队列(Priority Queue)

          1.. 优先队列(Priority Queue) 优先队列与普通队列的区别:普通队列遵循先进先出的原则:优先队列的出队顺序与入队顺序无关,与优先级相关. 优先队列可以使用队列的接口,只是在 ...

  7. 第二十四篇 玩转数据结构——队列(Queue)

          1.. 队列基础 队列也是一种线性结构: 相比数组,队列所对应的操作数是队列的子集: 队列只允许从一端(队尾)添加元素,从另一端(队首)取出元素: 队列的形象化描述如下图: 队列是一种先进 ...

  8. 第二十九篇 玩转数据结构——线段树(Segment Tree)

          1.. 线段树引入 线段树也称为区间树 为什么要使用线段树:对于某些问题,我们只关心区间(线段) 经典的线段树问题:区间染色,有一面长度为n的墙,每次选择一段墙进行染色(染色允许覆盖),问 ...

  9. C# 数据结构 栈 Stack

    栈和队列是非常重要的两种数据结构,栈和队列也是线性结构,线性表.栈和队列这三种数据结构的数据元素和元素的逻辑关系也相同 差别在于:线性表的操作不受限制,栈和队列操作受限制(遵循一定的原则),因此栈和队 ...

随机推荐

  1. Vuejs+elementUI项目,在进行打包时,要注意的问题

    注意:打包之前,需要注意修改一些地方 (1)若是前后端分离开发的,前端开发过程中可能会在api.js中设置访问路径为服务器所在电脑的ip:端口,打包前,最好将它改回localhost:8080 (2) ...

  2. Selenium chromeDriver启动时报错:session not created: This version of ChromeDriver only supports Chrome

    解决方案: 这是因为ChromeDriver与本地chrome浏览器的版本不一致导致 ChromeDriver下载地址:http://npm.taobao.org/mirrors/chromedriv ...

  3. 在linux系统中配置NVMe over FC

    在linux系统中配置NVMe over FC与配置NVMe over TCP类似,前5步操作请参考<在linux系统中配置NVMe over TCP>,网页连接如下: https://w ...

  4. ASP.NET Razor 常用示例

    1.在网页中显示@符号 使用@@即可使编译器不切换到c#,这样在网页中会显示一个@符号. 2.隐式表达式 也就是正常的razor语法,不能包含空格.(除了await 如:<p>@await ...

  5. vmware运行ubuntu虚拟机出现诡异的鼠标闪烁

    正在开心的写着AC自动机,突然发现鼠标消失了. 习惯性地动动鼠标,却还是没有反应,停止移动鼠标后鼠标却显现了出来??(吃惊.gif 在加载软件的时候,就算鼠标停止也会闪烁(其实这个虚拟机以前加载也会闪 ...

  6. VB断点拷贝大文件(WIN7系统需要更改某个API函数,具体我也忘了)

    小弟以前租碟在电脑上看VCD,有时候拷贝经典的影片到硬盘上可惜碰到比较粗糙的碟子就很难拷贝过去,因此编了个断点拷贝文件的程序.本程序用于拷贝大文件,并可在旧文件上接着拷贝本程序能在无法读取数据的情况下 ...

  7. 巨杉Tech | SequoiaDB虚机镜像正式上线

    数据库云化架构需求 随着云架构的发展和流行,在业务和应用进行“云化”的过程中,云数据库因为在整体架构中的重要地位,在云化改造中的重要性不言而喻.云数据库需要满足这些技术要求,除了在功能上的具体提升,在 ...

  8. jdk8-》stream⾥的map和filter函数使⽤

    map函数 将流中的每⼀个元素 T(入参) 映射为 R(返回值)(类似类型转换)    类似遍历集合,对集合的每个对象做处理.场景:转换对象,如javaweb开发中集合⾥⾯的DO对象转换为DTO对象 ...

  9. detach() 使用和.detach()和.data的区别 、cpu()函数的作用

    detach() 使用和.detach()和.data的区别 .cpu()函数的作用 待办 detach使用 https://blog.csdn.net/qq_27825451/article/det ...

  10. docker-部署zabbix4

    一.安装docker环境 安装依赖 yum install -y yum-utils device-mapper-persistent-data lvm2 设置yum源 yum-config-mana ...