关于栈和队列

简介: 关于栈和队列

栈(Stack)


什么是栈


栈是一种特殊的线性表,它只允许在固定的一端进行插入和删除元素操作,进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。

栈中的数据元素遵守后入先出的原则。


压栈:栈的插入操作叫做进栈/压栈/入栈,入数据是在栈顶。

出栈:栈的删除操作叫做出栈。出数据也在栈顶。

da38b51681644a69896400838089dddc.png


出栈时,是C先出,然后是B,再是A .


注意:这个栈是一种数据结构,与JVM内存模型中的栈不同,JVM中的栈是指JVM中一段内存区域。


栈的使用


d9e1cb0becbc46a7bf70a5326d544e4c.png


public static void main(String[] args) {
  Stack<Integer> s = new Stack();  //尖括号里是我要存放的数据类型
  s.push(1);
  s.push(2);
  s.push(3);
  s.push(4);
  System.out.println(s.size()); // 获取栈中有效元素个数---> 4
  System.out.println(s.peek()); // 获取栈顶元素---> 4
  s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
  System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为2
  if(s.empty()){
  System.out.println("栈空");
  }else{
  System.out.println(s.size());
  }
}


831ecdcfa8854c1a9494056fa30b05c8.png


栈的模拟实现



a1e6da3e241446fa8a9efd43408c339b.png


可以看到,Stack继承了Vector,Vector是动态顺序表,与ArrayList类似,不同的是 Vector 是线程安全的。


import java.util.Arrays;
public class MyStack {
    int array[];     //数组用于存放数据
    int size = 0;    //size用于记录当前元素个数
    public MyStack() {  
        this(12);     //默认开辟大小为12的空间
    }
    public MyStack(int m) {
        array = new int[m];
    }
    //入栈
    public int push(int value) {
        if(size == array.length) {
            //扩容
            int[] copy;
            //复制array,并将其扩容一倍
            copy = Arrays.copyOf(array,2* array.length);
            array = copy;  //将array指向扩容后的数组
        }
        array[size] = value;
        size++;
        return value;
    }
    //出栈
    public int pop() {
        if(size == 0) {
            throw new RuntimeException("栈中没有元素");
        }
        int n = array[size-1];
        size--;
        return n;
    }
    //获取栈顶元素
    public int peek() {
        if(size == 0) {
            throw new RuntimeException("栈中没有元素");
        }
        return array[size-1];
    }
    //获取元素个数
    public int getSize() {
        return size;
    }
    //判断栈是否为空
    public boolean isEmpty() {
        return size == 0;
    }
}


队列(Queue)


什么是队列


队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出特性。

入队列:进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头


d29024028b34443f858f6f67da42e578.png


队列的使用


93be12953ddb4f4daee4e3eacbafcf3f.png


在Java中,Queue是个接口,底层是通过链表LinkedList实现的,LinkedList实现了Queue接口,我们可以通过LinkedList实例化对象,如:


Queue<Integer> queue = new LinkedList<>();


队列里的方法:

0f55d6c2c1a947358bce0e09ff2d3186.png

public static void main(String[] args) {
  Queue<Integer> queue = new LinkedList<>();
  queue.offer(1);
  queue.offer(2);
  queue.offer(3);
  queue.offer(4);
  queue.offer(5); // 从队尾入队列
  System.out.println(queue.size());  //获取元素个数
  System.out.println(queue.peek()); // 获取队头元素
  queue.poll();
  System.out.println(queue.poll()); // 从队头出队列,并将删除的元素返回
  if(queue.isEmpty()){
  System.out.println("队列空");
  }else{
  System.out.println(queue.size());
  }
}

输出:

f75b4ec2a8a34a069f13142b12869b01.png


队列的模拟实现


实现一个队列,我们可以用数组存储数据,也可以用链表存储数据,那究竟用哪种存储方式更好呢?


我们认为链表存储更好,因为链表的存储空间不连续,可以更好的利用存储空间(可以利用零碎的空间),而数组它的空间是连续的,万一空间不足就会出问题(不能利用零碎空间)。


public class MyQueue {
    //双向链表节点
    public static class ListNode {
        ListNode next;  //记录下一个节点
        ListNode prev;  //记录上一个节点
        int val;      //记录该节点存储的值
        ListNode(int val){
            this.val = val;
        }
    }
    ListNode head; // 队头
    ListNode last; // 队尾
    int size = 0;
    //入队列
    public void offer(int val) {
        ListNode node = new ListNode(val);  //先实例化这个节点出来
        if(last == null) {   //链表中未存放元素时
            last = node;
            head = last;
        }else {
            last.next = node;
            node.prev = last;
            last = node;
        }
        size++;   //链表元素个数加一
    }
    //出队列
    public int poll() {
        if(isEmpty()) {  //判断队列是否为空
            throw new RuntimeException("队列为空,无法出元素");
        }
        int m = head.val;
        head = head.next;
        head.prev = null;
        size--;
        return m;
    }
    //判断队列是否为空
    public boolean isEmpty() {
        return size == 0;
    }
    //获取对头元素
    public int peek() {
        if(isEmpty()) {  //判断队列是否为空
            throw new RuntimeException("队列为空,无法获取元素");
        }
        return head.val;
    }
    //获取元素个数
    public int getSize() {
        return size;
    }
}


循环队列


我们有时还会使用一种队列叫循环队列。环形队列通常使用数组实现。

队头添加元素,队尾删除元素,每块空间都能反复利用,有效节省空间。


front 指向队列的第一个元素,即array[front]为队列的第一个元素,初始值为0;


rear 指向最后一个元素的后一个元素,初始值为0;


f3f9bc4de4894ccb97bcf2e58cb30d76.png


从上图可以看出队列为空的条件:front == rear

当队列存放一个元素时,rear向前走一步,如下图:


2b63978d2e534062abcc96cd73e801bc.png


那队列啥时候满了呢?当情况为下图时吗?


cc5848fcdadb400f8f33346114373052.png


如果这就是满的话,但如何判断呢?是 front == rear 吗?

显然,这与判断队列为空相矛盾,我们不应该这样判断,那应如何判断呢?

我们有两种方法:


  1. 通过添加 size 属性记录(用size记录元素个数)
  2. 牺牲一个位置来表示满

我们就以牺牲一个位置为例吧,如果我们牺牲一个位置来表示满,当情况为下图时:


aadf30050ee1421787152f4426ec22cf.png


此情况就表示满,条件即是:(rear + 1) % array.length == front


class MyCircularQueue {
    int front = 0;
    int rear = 0;
    int[] array;
    public MyCircularQueue(int k) {  //设计一个可以存放k个数据的数组
        array = new int[k+1];     //为什么要设计k+1个大小呢?
    }                           //因为在队尾后面需要牺牲一个空间,方便判满
    //向循环队列插入一个元素。如果成功插入则返回真。
    public boolean enQueue(int value) {
        if(isFull()) {
            return false;
        }
        array[rear] = value;
        rear = (rear + 1) % array.length;
        return true;
    }
    //从循环队列中删除一个元素。如果成功删除则返回真。
    public boolean deQueue() {
        if(isEmpty()) {
            return false;
        }
        front = (front + 1) % array.length;
        return true;
    }
    //从队首获取元素。如果队列为空,返回 -1 。
    public int Front() {
        if(isEmpty()) {
            return -1;
        }
        return array[front];
    }
    //获取队尾元素。如果队列为空,返回 -1 。
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        return array[(rear+array.length-1)%array.length];
    }
    //检查循环队列是否为空。
    public boolean isEmpty() {
        if(rear == front) {
            return true;
        }
        return false;
    }
    //检查循环队列是否已满。
    public boolean isFull() {
        if((rear+1) % array.length == front) {
            return true;
        }
        return false;
    }
}


双端队列 (Deque)


双端队列:指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。


Deque是一个接口,使用时必须创建LinkedList或ArrayDeque的对象。


在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口


Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
  Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

相关文章
|
2月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
161 77
|
13天前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
26 11
|
27天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
2月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
58 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
2月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
53 9
|
2月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
48 7
|
4月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
108 5
|
4月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
400 9
|
4月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
66 1
|
4月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
140 21