栈数据结构详解

简介: 栈(stack)是一种线性数据结构,栈中的元素只能先入后出(First In Last Out,简称FILO)。最早进入的元素存放的位置叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶 (top)。本文是对堆结构的通透介绍

1.栈的定义

栈(stack)是一种线性数据结构,栈中的元素只能先入后出(First In Last Out,简称FILO)。最早进入的元素存放的位置叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶 (top)。

栈的插入操作,叫做入栈(push)。存入栈的元素之间没有任何具体的关系,只有到来的时间的先后顺序。

入栈图

出栈图

入栈操作涉及的单个数据的进入,所以时间复杂度为 O(1),同时入栈过程中只需要单个的临时存储空间,所以空间复杂度为 O(1)。

2.栈的存储原理

栈既可以用数组来实现,也可以用链表来实现

栈的数组实现如下:

数组实现的栈也叫顺序栈或静态栈

栈的链表实现如下:

3.栈的操作-数组实现

入栈

入栈操作(push)就是把新元素放入栈中,只允许从栈顶一侧放入元素,新元素的位置将会成为新的栈顶

出栈(弹栈)

出栈操作(pop)就是把元素从栈中弹出,只有栈顶元素才允许出栈,出栈元素的前一个元素将会成为新的栈顶。

public class ArrayStack {
private int[] array; // 存储栈元素的数组
   private int top; // 栈顶指针,指向栈顶元素在数组中的位置
   private int capacity; // 栈的最大容量

   // 构造函数,创建一个指定容量的栈
   public ArrayStack(int capacity) {
       this.capacity = capacity;
       this.array = new int[capacity];
       this.top = -1; // 栈为空,栈顶指针为-1
   }

   // 将给定的元素推送到栈的顶部
   public void push(int item) {
       if (isFull()) { // 如果栈已满,抛出异常
           throw new RuntimeException("Stack is full");
       }
       array[++top] = item; // 将元素放入栈顶,同时栈顶指针+1
   }

   // 从栈的顶部删除元素并返回该元素
   public int pop() {
       if (isEmpty()) { // 如果栈为空,抛出异常
           throw new RuntimeException("Stack is empty");
       }
       return array[top--]; // 返回栈顶元素,同时栈顶指针-1
   }

   // 返回栈顶元素但不删除它
   public int peek() {
       if (isEmpty()) { // 如果栈为空,抛出异常
           throw new RuntimeException("Stack is empty");
       }
       return array[top]; // 返回栈顶元素,但不修改栈顶指针
   }

   // 如果栈为空,则返回true,否则返回false
   public boolean isEmpty() {
       return top == -1; // 栈顶指针为-1表示栈为空
   }

   // 如果栈已满,则返回true,否则返回false
   public boolean isFull() {
       return top == capacity - 1; // 栈顶指针+1等于栈容量表示栈已满
   }

   // 返回栈中元素的数量
   public int size() {
       return top + 1; // 栈顶指针+1即为栈中元素的数量
   }

   // 清空栈中的所有元素
   public void clear() {
       top = -1; // 将栈顶指针重置为-1,表示栈为空
   }
}

在上面的示例中,我们定义了一个ArrayStack类,该类使用数组实现栈。该类具有以下主要方法:

push(item):将给定的元素推送到栈的顶部。 pop():从栈的顶部删除元素并返回该元素。 peek():返回栈顶元素但不删除它。 isEmpty():如果栈为空,则返回true,否则返回false。 isFull():如果栈已满,则返回true,否则返回false。 size():返回栈中元素的数量。 clear():清空栈中的所有元素。

测试代码

public class ArrayStackTest {
   public static void main(String[] args) {
       // 创建一个容量为5的栈
       ArrayStack stack = new ArrayStack(5);

       // 测试栈的基本操作
       stack.push(1); // 入栈1
       stack.push(2); // 入栈2
       stack.push(3); // 入栈3
       System.out.println("栈顶元素为:" + stack.peek()); // 输出栈顶元素3
       stack.pop(); // 出栈3
       System.out.println("栈顶元素为:" + stack.peek()); // 输出栈顶元素2
       stack.push(4); // 入栈4
       stack.push(5); // 入栈5
       System.out.println("栈是否为空:" + stack.isEmpty()); // 输出false
       System.out.println("栈是否已满:" + stack.isFull()); // 输出true
       System.out.println("栈中元素个数为:" + stack.size()); // 输出4

       // 测试异常情况
       try {
           stack.push(6); // 入栈6,栈已满,抛出异常
       } catch (RuntimeException e) {
           System.out.println("异常信息:" + e.getMessage());
       }
       stack.pop(); // 出栈5
       stack.pop(); // 出栈4
       stack.pop(); // 出栈2
       stack.pop(); // 出栈1
       System.out.println("栈是否为空:" + stack.isEmpty()); // 输出true
       try {
           stack.pop(); // 出栈空栈,抛出异常
       } catch (RuntimeException e) {
           System.out.println("异常信息:" + e.getMessage());
       }
   }
}

4.栈的操作-链表实现

出栈操作(pop)就是把元素从栈中弹出,只有栈顶元素才允许出栈,出栈元素的前一个元素将会成为新的栈顶。

代码实现

import java.util.LinkedList;

public class LinkedStack<T> {

   private LinkedList<T> stackList; // 创建LinkedList类型的变量stackList,用于存储栈元素

   // 构造函数,创建一个空的LinkedList对象
   public LinkedStack() {
       stackList = new LinkedList<>();
   }

   // 将元素添加到栈的顶部,即链表的头部
   public void push(T item) {
       stackList.addFirst(item);
   }

   // 弹出并返回栈顶元素,同时将其从链表中删除
   public T pop() {
       if (stackList.isEmpty()) { // 如果栈为空,则抛出异常
           throw new RuntimeException("Stack is empty");
       }
       return stackList.removeFirst(); // 否则弹出并返回栈顶元素
   }

   // 返回栈顶元素,但不弹出它
   public T peek() {
       if (stackList.isEmpty()) { // 如果栈为空,则抛出异常
           throw new RuntimeException("Stack is empty");
       }
       return stackList.getFirst(); // 否则返回栈顶元素
   }

   // 判断栈是否为空
   public boolean isEmpty() {
       return stackList.isEmpty();
   }

   // 返回栈的元素数量
   public int size() {
       return stackList.size();
   }
}

该类有一个泛型类型T,用于存储栈元素。在构造函数中,我们创建了一个空的LinkedList对象来存储栈元素。

push方法将元素添加到栈的顶部,即链表的头部。

pop方法弹出并返回栈顶元素,同时将其从链表中删除。

peek方法返回栈顶元素,但不弹出它。

isEmpty方法返回栈是否为空。

size方法返回栈的元素数量。

请注意,如果尝试从空栈中弹出或查看元素,则会抛出RuntimeException。在实际应用中,可能需要使用不同的异常类型来更好地处理这种情况。

测试以上代码

public class LinkedStackTest {
   public static void main(String[] args) {
       // 创建一个字符串类型的栈
       LinkedStack<String> stack = new LinkedStack<>();

       // 测试isEmpty方法,输出true
       System.out.println("Stack is empty: " + stack.isEmpty());

       // 测试push方法,添加元素到栈顶
       stack.push("first");
       stack.push("second");
       stack.push("third");

       // 测试size方法,输出3
       System.out.println("Stack size: " + stack.size());

       // 测试peek方法,输出"third"
       System.out.println("Top element: " + stack.peek());

       // 测试pop方法,弹出并输出"third"
       System.out.println("Popped element: " + stack.pop());

       // 再次测试peek方法,输出"second"
       System.out.println("Top element: " + stack.peek());

       // 测试pop方法,弹出并输出"second"
       System.out.println("Popped element: " + stack.pop());

       // 测试size方法,输出1
       System.out.println("Stack size: " + stack.size());

       // 测试isEmpty方法,输出false
       System.out.println("Stack is empty: " + stack.isEmpty());
   }
}

5.栈的应用场景

子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中, 直到子程序执行完后再将地址取出,以回到原来的程序中。 递归的调用:可以用来在函数调用的时候存储断点, 储存下一个指令的地址外,也将参数、区域变量等数据存入栈中。 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。 二叉树的遍历。 图形的深度优先(depth一first)搜索法。

相关文章
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
45 1
|
15天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
132 77
|
15天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
37 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
15天前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
39 9
|
15天前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
30 7
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
91 5
|
2月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
104 21
|
2月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
2月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
63 4
|
2月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
62 0

热门文章

最新文章