用链表实现队列/栈

简介: 本文介绍如何用链表实现栈和队列,利用双链表头尾操作均为O(1)的特性,高效实现栈的push/pop和队列的入队/出队操作,并引出数组实现队列时的性能问题,为后续优化埋下伏笔。

用链表实现栈
一些读者应该已经知道该怎么用链表作为底层数据结构实现队列和栈了。因为实在是太简单了,直接调用双链表的 API 就可以了。
注意我这里是直接用的 Java 标准库的 LinkedList,如果你用之前我们实现的 MyLinkedList,也是一样的。
import java.util.LinkedList;

// 用链表作为底层数据结构实现栈
public class MyLinkedStack {
private final LinkedList list = new LinkedList<>();

// 向栈顶加入元素,时间复杂度 O(1)
public void push(E e) {
    list.addLast(e);
}

// 从栈顶弹出元素,时间复杂度 O(1)
public E pop() {
    return list.removeLast();
}

// 查看栈顶元素,时间复杂度 O(1)
public E peek() {
    return list.getLast();
}

// 返回栈中的元素个数,时间复杂度 O(1)
public int size() {
    return list.size();
}

public static void main(String[] args) {
    MyLinkedStack<Integer> stack = new MyLinkedStack<>();
    stack.push(1);
    stack.push(2);
    stack.push(3);

    System.out.println(stack.peek()); // 3
    System.out.println(stack.pop()); // 3
    System.out.println(stack.peek()); // 2
}

}
提示
上面这段代码相当于是把双链表的尾部作为栈顶,在双链表尾部增删元素的时间复杂度都是 O(1),符合要求。
当然,你也可以把双链表的头部作为栈顶,因为双链表头部增删元素的时间复杂度也是 O(1),所以这样实现也是一样的。只要做几个修改 addLast -> addFirst,removeLast -> removeFirst,getLast -> getFirst 就行了。
用链表实现队列
同理,用链表实现队列也是一样的,也直接调用双链表的 API 就可以了:
import java.util.LinkedList;

// 用链表作为底层数据结构实现队列
public class MyLinkedQueue {
private final LinkedList list = new LinkedList<>();

// 向队尾插入元素,时间复杂度 O(1)
public void push(E e) {
    list.addLast(e);
}

// 从队头删除元素,时间复杂度 O(1)
public E pop() {
    return list.removeFirst();
}

// 查看队头元素,时间复杂度 O(1)
public E peek() {
    return list.getFirst();
}

// 返回队列中的元素个数,时间复杂度 O(1)
public int size() {
    return list.size();
}

public static void main(String[] args) {
    MyLinkedQueue<Integer> queue = new MyLinkedQueue<>();
    queue.push(1);
    queue.push(2);
    queue.push(3);

    System.out.println(queue.peek()); // 1
    System.out.println(queue.pop()); // 1
    System.out.println(queue.pop()); // 2
    System.out.println(queue.peek()); // 3
}

}
提示
上面这段代码相当于是把双链表的尾部作为队尾,把双链表的头部作为队头,在双链表的头尾增删元素的复杂度都是 O(1),符合队列 API 的要求。
当然,你也可以反过来,把双链表的头部作为队尾,双链表的尾部作为队头。类似栈的实现,只要改一改 list 的调用方法就行了。
文末思考
双链表他比较牛,队列和栈的 API 考不倒它。但是你想一下,数组实现队列的时候,会不会有问题?
队列 API 要求一端增加元素,一端删除元素,而数组的头部无论是增加还是删除元素,时间复杂度都是 O(n)。这种情况下,有没有办法优化呢?
你可以思考一下,下一章我会告诉你答案。

相关文章
|
1月前
|
机器学习/深度学习 文字识别 算法
报关单OCR识别-进出口海关报关单识别接口返回参数-文字识别
报关单识别接口基于OCR与深度学习技术,精准提取进出口报关单关键信息,输出结构化数据。支持API调用与私有化部署,适用于智能通关、跨境物流等场景,提升申报效率与准确性。
|
1月前
|
Java 索引 容器
环形数组技巧
环形数组通过模运算在逻辑上形成闭环,利用start和end双指针实现首尾O(1)增删。虽物理结构线性,但通过取余操作使指针循环,结合左闭右开区间设计,高效支持动态扩容缩容,适用于队列、双端队列等场景。
|
1月前
|
人工智能 自然语言处理 前端开发
SpringAI+DeepSeek大模型应用开发
SpringAI整合主流大模型,支持对话、函数调用与RAG,提供统一API,简化开发。涵盖多模态、流式传输、会话记忆等功能,助力快速构建AI应用。
|
1月前
|
存储 数据可视化 Java
用拉链法实现哈希表
本文详解哈希表中拉链法的实现原理,通过简化版与完整版Java代码,介绍如何用链表解决哈希冲突,支持泛型、动态扩容及增删查改操作,帮助深入理解哈希表底层机制。
|
1月前
|
Java API
用数组实现队列/栈
使用数组实现栈时,可将动态数组尾部作为栈顶,利用ArrayList的add和remove操作实现push、pop等,时间复杂度均为O(1)。若以头部为栈顶,则需借助环形数组CycleArray实现高效操作。同样,基于CycleArray可在首尾分别进行出队和入队,轻松实现队列功能,保证操作效率。
|
1月前
|
存储 缓存 算法
哈希表核心原理 哈希表等于Map吗?
哈希表不等于Map。Map是键值映射的接口,哈希表(如HashMap)是其一种实现。增删查改O(1)的前提是哈希函数高效且冲突处理得当。本文详解哈希表原理、哈希冲突解决、负载因子与key不可变性,助你深入理解底层机制。
|
1月前
|
存储 算法 索引
二叉树基础及常见类型
二叉树是最核心的数据结构之一,不仅是红黑树、堆、字典树等复杂结构的基础,更体现了递归的思维方式。掌握二叉树,等于掌握了算法与数据结构的钥匙。从满二叉树、完全二叉树到二叉搜索树,各类变体应用广泛。通过链式存储或邻接表均可实现,是刷题与实战的必备基础。
|
1月前
|
存储 自然语言处理 搜索推荐
05 | 倒排索引:如何从海量数据中查询同时带有「极」和「客」的唐诗?
本文通过唐诗检索的类比,深入浅出地讲解了正排索引与倒排索引的核心原理。正排索引以文档ID为键,适用于精确查找;而倒排索引则以关键词为键,指向包含该词的文档列表,极大提升了多关键词联合查询效率。文章详细介绍了倒排索引的构建过程、链表归并求交集的查询优化方法,并拓展到作者维度检索等实际应用场景,揭示其在搜索引擎、数据库全文检索等系统中的核心地位。
|
1月前
|
机器学习/深度学习 算法 搜索推荐
11|精准 Top K 检索:搜索结果是怎么进行打分排序的?
搜索引擎排序核心在于打分与Top K检索。本文详解三种打分算法:经典TF-IDF衡量词频与区分度;BM25引入文档长度、词频上限等优化,效果更优;机器学习则融合数百因子自动学习权重,适应复杂场景。最后通过堆排序高效实现Top K结果返回,提升性能。
|
1月前
|
存储 缓存 算法
02 | 非线性结构检索:数据频繁变化的情况下,如何高效检索?
通过树状结构或跳表组织数据,可实现高效二分查找。二叉检索树利用有序性快速缩小搜索范围,平衡时查询效率为O(log n);跳表则通过多层指针加速链表遍历,以概率平衡实现近似O(log n)性能,二者均优于数组在频繁更新场景下的表现。
28 0