第二十三篇 玩转数据结构——栈(Stack)

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("({)}[]"));
    
        }
    }

原文地址:https://www.cnblogs.com/xuezou/p/9276961.html

时间: 2024-08-27 15:35:02

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

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

1.. 集合的应用 集合可以用来去重 集合可以用于进行客户的统计 集合可以用于文本词汇量的统计 2.. 集合的实现 定义集合的接口 Set<E> ·void add(E) // 不能添加重复元素 ·void remove(E) ·boolean contains(E) ·int getSize() ·boolean isEmpty() 集合接口的业务逻辑如下: public interface Set<E> { void add(E e); void remove(E e); bo

第二十三篇 责任与义务

第二十三篇  责任与义务 我们来到这个世界,本身就有自身的责任与义务.如果没有责任与义务,我们就失去了生活的意义,因为一个人必须活出价值才有意义:没有价值地活着,那就成为了宇宙的累赘.以这样的方式来为亲人们讲解我们的"责任与义务"非常有必要.如果一个人活在这个世界上都不明白自己的责任与义务是什么,那就会失去方向.这一篇我就为亲人们简单地讲解我们在这个宇宙中的责任与义务. 人类作为高智慧物种,如果没有责任与义务也就不会出现在宇宙中.因为创造者在创造我们人类这个物种的时候就给到了定位:也是

第二十三篇:Windows中的ACPI

一直不太清楚ACPI驱动在WINDOWS中的作用. 甚至不了解ACPI协议的作用. 于是, 随便翻了下"格蠹汇编"中的第24章, 如何跟踪ACPI代码, 算是对ACPI有了一个初步性的了解. ACPI向OS报告硬件信息, 而OS通过ACPI控制硬件. ACPI就是OS与硬件/固件之间的一个标准接口协议. ACPI通过ASL(ACPI SOURCE LANGUAGE)来描述系统硬件的属性与方法, ASL最终会被编译为AML(ACPI MACHINE LANGUAGE)的字节码(BYTEC

Egret入门学习日记 --- 第二十三篇(书中 9.9~9.11 节 内容)

第二十三篇(书中 9.9~9.11 节 内容) 今天,来9.9节. 重点: 1.VSlider的声明和使用. 操作: 1.VSlider的声明和使用. 其实和HSlider的使用方式差不多. 至此,9.9节 内容结束. 开始 9.10节. 重点: 1.配合TextureMerger,生成艺术字图集资源. 2.导入艺术字资源,并使用. 3.调整艺术字间距. 操作: 1.配合TextureMerger,生成艺术字图集资源. 打开TextureMerger,选择Bitmap Font . 点击添加字符

第二十六篇 玩转数据结构——二分搜索树

1.. 二叉树 跟链表一样,二叉树也是一种动态数据结构,即,不需要在创建时指定大小. 跟链表不同的是,二叉树中的每个节点,除了要存放元素e,它还有两个指向其它节点的引用,分别用Node left和Node right来表示. 类似的,如果每个节点中有3个指向其它节点的引用,就称其为"三叉树"... 二叉树具有唯一的根节点. 二叉树中每个节点最多指向其它的两个节点,我们称这两个节点为"左孩子"和"右孩子",即每个节点最多有两个孩子. 一个孩子都没有

第二十二篇 玩转数据结构——构建动态数组

1.. 数组基础 数组就是把数据码成一排进行存放. Java中,数组的每个元素类型必须相同,可以都为int类型,string类型,甚至是自定义类型. 数组的命名要语义化,例如,如果数组用来存放学生的成绩,那么命名为scores就比较合适. 索引(index)是数组中的一个重要概念,它是我们给数组中的每个元素分配的编号,从0开始,依次递增.如果数组中存放了n个元素,第一个元素的索引是0,最后一个元素的索引是n-1. 通过索引,我们可以对数组中的元素进行快速访问,例如,我们访问索引为2的元素也就是数

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

1.. 队列基础 队列也是一种线性结构: 相比数组,队列所对应的操作数是队列的子集: 队列只允许从一端(队尾)添加元素,从另一端(队首)取出元素: 队列的形象化描述如下图: 队列是一种先进先出(First In First Out)的数据结构: 2.. 队列的实现 任务目标如下: Queue<E> ·void enqueue(E) //入队 ·E dequeue() //出队 ·E getFront() //查看队首元素 ·int getSize() //查看队列中元素的个数 ·boolean

python全栈开发基础【第二十三篇】线程

一.什么是线程 线程:顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位. 多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源.(一个进程里面开多个线程(共享同一个进程里面的内存空间)) 例如,北京地铁与上海地铁是不同的进程,而北京地铁里的13号线是一个

C# 数据结构 栈 Stack

栈和队列是非常重要的两种数据结构,栈和队列也是线性结构,线性表.栈和队列这三种数据结构的数据元素和元素的逻辑关系也相同 差别在于:线性表的操作不受限制,栈和队列操作受限制(遵循一定的原则),因此栈和队列也称为受限制的线性表. 栈的定义:操作在表的尾端进行的线性表,栈顶:TOP,栈底:Bottom.栈中没有数据:空栈Empty Stack 表示方法:S=(a1,a2,a3,a4……..an)a1为栈底的元素,an为栈顶的元素.这N个数据按照先后顺序插入到栈内,找出栈内数据则相反 遵循的原则(Las