[Java/数据结构]线性表之栈与队列

画师:竹取工坊

大佬们好!我是Mem0rin!现在正在准备自学转码。

如果我的文章对你有帮助的话,欢迎关注我的主页Mem0rin,欢迎互三,一起进步!


文章目录


主要内容是用前面学到的线性表实现栈和队列的实现和使用。我们只针对data为整数的情况进行模拟实现,如果需要了解泛型的具体实现请参考官方文档。

一、栈

1.栈的概念

想象一堆叠起来的餐盘,只能从上面放,从上面取出:

我们把这样"先进后出"的结构称作栈。这与我们之前学到的顺序表删除末尾元素是类似的。

事实上,从栈在集合结构的位置,我们也可以看到栈是动态顺序表 Vector 的子类,因此 Stack 类我们会用 ArrayList 模拟实现。:

2.节点设计

我们采用数组的方式构建,类似于 ArrayList

java 复制代码
    int[] array;
    int size;

    int DEFAULT_ARRAY_LENGTH = 3;

    public MyStack(){
        array = new int[DEFAULT_ARRAY_LENGTH];
    }

3.方法的实现

常用的栈的方法如下:

方法 功能
E push(E e) 将e入栈,并返回e
E pop() 将栈顶元素出栈并返回
E peek() 获取栈顶元素
int size() 获取栈中有效元素个数
boolean empty() 检测栈是否为空

模拟实现如下,基本和 ArrayList 类似,因此不再赘述,下面放出经典的出栈和入栈的图解,希望能有所帮助:

java 复制代码
public int push(int e){
        ensureCapacity();
        array[size++] = e;
        return e;
    }
    public int pop(){
        int e = peek();
        size--;
        return e;
    }
    public int peek(){
        if(empty()){
            throw new RuntimeException("栈为空,无法获取栈顶元素");
        }
        return array[size-1];
    }
    public int size() {

        return size;
    }
    public boolean empty(){
        return 0 == size;
    }
    private void ensureCapacity(){
        if(size == array.length){
            array = Arrays.copyOf(array, size*2);
        }
    }

4.栈的应用

栈的一个经典应用是逆波兰式求四则运算

比如一个四则运算表达式为 2 + (3 - 1) * (5 / 6),转换成逆波兰式就是 231-56/*+,简单的理解就是把计算符号丢到了两个数的后面,这样的好处在于可以避免括号的存在。

我们求解逆波兰式的原则是,当遍历到计算符号的时候,就读取计算符号前面两个数字,计算结果,再放回原来的式子中,以此循环。

比如上面的式子,我们往后遍历的时候,碰到 - 的时候,就往前找前面的两个数,找到了3,1,3为前数,因此是 3 - 1 = 2 ,并把得到的结果2放回了原式,得到 2256/*+,以此往复。

栈在这个问题中非常方便,遇到数字则入栈,遇到计算符号就把前面的数字出栈,再把计算的结果重新入栈,直到最后遍历完栈内只剩一个数字,这就是逆波兰式的计算结果。

代码留给读者~(才不是自己代码太丑不敢贴)

二、队列

顾名思义是类似于排队的数据结构,顺序是"先进先出",图例如下:

队列的集合位置如下:

可以看到队列(Queue)接口是由双向链表 LinkedList 类进行实现的,在使用的过程中也依赖于 LinkedList 的实例化(因为接口无法实例化),因此在这篇文章里队列也依靠双向链表实现。

其实用数组实现队列也是可行的,在Java中也有用顺序表实现队列接口的类,这样的思想相当于是设置一个数组,如果出队,则把数组的head++,如果入队,就把数组的last++,但是这样会带来一定的内存浪费!比如如果我出队了100个元素,那么就有400个字节被浪费!因此更常用的,我们会用链式结构进行模拟。

1.队列的节点设计

和双向链表一样,我们用ListNode的内部类进行队列的节点构造。特别的,为了出队和入队操作的方便,我们设置了 firstlast 指针指向队首和队尾。

java 复制代码
public static class ListNode{
	ListNode next;
	ListNode prev;
	int value;
	ListNode(int value){
		this.value = value;
	}
}
ListNode first; // 队头
ListNode last; // 队尾
int size = 0;

2.方法实现

常用的队列方法如下:

方法 功能
boolean offer(E e) 入队列
E poll() 出队列
peek() 获取队头元素
int size() 获取队列中有效元素个数
boolean isEmpty() 检测队列是否为空

入队和出队的操作一样可以参考下图:

java 复制代码
// 入队列---向双向链表位置插入新节点
    public void offer(int e){
        ListNode newNode = new ListNode(e);
        if(first == null){
            first = newNode;
            // last = newNode;
        }else{
            last.next = newNode;
            newNode.prev = last;
            // last = newNode;
        }
        last = newNode;
        size++;
    }
    // 出队列---将双向链表第一个节点删除掉
    public int poll(){
    // 1. 队列为空
    // 2. 队列中只有一个元素----链表中只有一个节点---直接删除
    // 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除
        int value = 0;
        if(first == null){
            throw new RuntimeException("队列为空");
        }else if(first == last){
            last = null;
            first = null;
        }else{
            value = first.value;
            first = first.next;
            first.prev.next = null;
            first.prev = null;
        }
        --size;
        return value;
    }
    // 获取队头元素---获取链表中第一个节点的值域
    public int peek(){
        if(first == null){
            throw new RuntimeException("队列为空");
        }
        return first.value;
    }
    public int size() {
        return size;
    }
    public boolean isEmpty(){
        return first == null;
    }

3.双端队列

接口Deque的双端队列可以实现两端的入队和出队,同样也依赖于 LinkedList 的实例化实现接口。

三、循环队列

循环队列是一个特殊的队列,因为他占据的空间固定,并且出队的空间可以被重新利用。

通常用数组进行实现,在数组中,我们用 len 储存队列的长度,并且指定 head 作为队列的头,指向第一个元素,用 rear 指向下一个元素入队的位置,也就是队列的尾的后一个位置。

得到的初步结构如下:

java 复制代码
	  private int rear = 0;
    private int head = 0;
    private int len = 0;
    int[] q;
	//构造一个长度为k的循环队列
    public MyCircularQueue(int k) {
        this.len = k + 1;
        q = new int[len];
    }

为什么 len 是 k + 1后面进行说明

我们再继续完成队列满和空的判断,首先当队列空的时候,不妨以初始状态为例,此时 head == rear,因此得到 isEmpty() 方法:

java 复制代码
    public boolean isEmpty() {
        return rear == head;
    }

但是在判断队列是否满的时候出现了问题,如图,如果我们把整个队列都填满才算队列是满的话,那么 rear 就会指向队首的 head,这就和 isEmpty()的判断条件一样了,那我们又怎么判断队列是满还是空呢?

解决方法在前面其实已经给出了,我们在队列中开辟一个"过渡节点",这个节点不存放值,而是把队尾和队首隔开,从而当队列满的时候,指向的是过渡节点而不是队首。

那么现在的满的判断条件是 (rear + 1) % len = head,比如上图中len为8,head指向的索引为0的话,那么 rear 指向的索引就是7,满足条件,如果head 为7的话,rear指向的是6,同样也符合条件。

headrear 的移动也遵循这样的模式,也就是 head = (head + 1) % lenrear = (rear + 1) % len

这样我们还可以写出出队和入队的方法:

java 复制代码
    public boolean enQueue(int value) {
        if (isFull()) {
            return false;
        }
        q[rear] = value;
        rear = (rear + 1) % len;
        return true;
    }
    
    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        head = (head + 1) % len;
        return true;
    }

最后是查询队首和队尾元素的值的方法,rear 需要往前倒一个节点,同时为了防止索引越界,因此我们采用了很常用的 (rear - 1 + len) % len,通过加上额外的len来避免分类讨论:

java 复制代码
    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return q[head];
    }
    
    public int Rear() {
        if (isEmpty()) {
            return -1;
        }
        return q[(rear - 1 + len) % len];
    }

二叉树东西有点多,有空更。

相关推荐
上天_去_做颗惺星 EVE_BLUE2 小时前
Go 语言入门实战指南
开发语言·后端·golang
平安的平安2 小时前
Python + AI Agent 智能体:从原理到实战,构建自主决策的 AI 助手
开发语言·人工智能·python
Mr_Xuhhh2 小时前
深入理解Java数组:从定义到高阶应用
开发语言·python·算法
古城码农2 小时前
Windows平台MSVC编译的FFmpeg库
开发语言·qt
冰暮流星2 小时前
javascript之dom查询操作2
开发语言·javascript·ecmascript
小陈工2 小时前
Python Web开发入门(九):权限管理与角色控制实战
服务器·开发语言·前端·数据库·python·安全·sqlite
孙华贵2 小时前
python编程怎么赚钱
开发语言·python
东离与糖宝2 小时前
告别Python!Java本地部署Gemma 4:Maven一键集成
java·人工智能
tryCbest2 小时前
Python之Falsk开发框架(第四篇)- Flask 知识总结与完整博客系统实战
开发语言·python·flask