数据结构秘籍(一)线性数据结构
1.数组
数组(Array)是一种很常见的数据结构。它由相同类型的元素(element)组成,并且是使用一块连续的内存来存储。
我们直接可以利用元素的索引(index)计算出该元素对应的存储地址。
数组的特地那是:提供随机访问并且容量有限。
假如数组的长度为 n。
访问:O(1)//访问特定位置的元素
插入:O(n )//最坏的情况发生在插入发生在数组的首部并需要移动所有元素时
删除:O(n)//最坏的情况发生在删除数组的开头发生并需要移动第一元素后面所有的元素时
2.链表
2.1 链表简介
链表(LinkedList)虽然是一种线性表,但是并不会按线性的顺序存储数据,使用的不是连续的内存空间来存储数据。
链表的插入和删除操作的复杂度为O(1),只需要知道目标位置元素的上一个元素即可。但是,在查找一个节点或者访问特定位置的节点的时候复杂度为O(n).
使用链表结构可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但链表不会节省空间,相比于数组会占用更多的空间,因为链表中每个节点存放的还有指向其他节点的指针。除此之外,链表不具有数组随机读取的优点
2.2 链表分类
常见链表分类:
1.单链表
2.双向链表
3.循环链表
4.双向循环链表
假如链表中有n个元素。
访问:O(n)//访问特定位置的元素
插入删除:O(1)//必须要要知道插入元素的位置
2.2.1 单链表
单链表 单向链表只有一个方向,点解只有一个后继指针next指向后面的节点。因此,链表这种数据结构通常在物理内存上不是连续的。我们习惯性地把第一个结点叫头结点,链表通常有一个不保存任何值的head节点(头结点),通过头结点我们可以遍历整个链表。为节点通常指向null。

2.2.2 循环链表
循环链表其实是一种特殊的单链表,和单链表不同的是循环链表的尾结点不是指向null。而是指向链表的头结点。
2.2.3 双向链表
双向链表 包含两个指针,一个prev指向前一个结点,一个next指向后一个结点。

2.2.4 双向循环链表
双向循环链表 最后一个结点的next指向head,而head的prev指向最后一个结点,构成一个环。

2.3 应用场景
- 如果需要支持随机访问的话,链表没办法做到。
- 如果需要存储的数据元素的个数不确定,并且需要经常添加和删除数据的话,使用链表比较合适。
- 如果需要存储的数据元素的个数确定,并且不需要经常添加和删除数据的话,使用数组比较合适。
2.4 数组vs链表
- 数组支持随机访问,而链表不支持。
- 数组使用的是连续内存空间对 CPU 的缓存机制友好,链表则相反。
- 数组的大小固定,而链表则天然支持动态扩容。如果声明的数组过小,需要另外申请一个更大的内存空间存放数组元素,然后将原数组拷贝进去,这个操作是比较耗时的!
3.栈
3.1 栈简介
栈(Stack)只允许在有序的线性数据集合的一端(称为栈顶top)进行加入数据(push)和移除数据(pop)。因而按照 后进先出(LIFO, Last In First Out) 的原理运作。在栈中,push和pop的操作都发生在栈顶。
栈常用一维数组或链表来实现,用数组实现的栈叫做顺序栈,用链表实现的栈叫做链式栈。
假设堆栈中有n个元素。
访问:O(n)//最坏情况
插入删除:O(1)//顶端插入和删除元素

3.2 栈的常见应用场景
当我们要处理的数据只涉及在一端插入和删除数据,并且满足 后进先出(LIFO, Last In First Out) 的特性时,我们就可以使用栈这个数据结构。
3.2.1. 实现浏览器的回退和前进功能
我们只需要使用两个栈(Stack1 和 Stack2)和就能实现这个功能。比如你按顺序查看了 1,2,3,4 这四个页面,我们依次把 1,2,3,4 这四个页面压入 Stack1 中。当你想回头看 2 这个页面的时候,你点击回退按钮,我们依次把 4,3 这两个页面从 Stack1 弹出,然后压入 Stack2 中。假如你又想回到页面 3,你点击前进按钮,我们将 3 页面从 Stack2 弹出,然后压入到 Stack1 中。示例图如下:

3.3.2 检查符号是否成对出现
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断该字符串是否有效。
有效字符串需满足:
1. 左括号必须用相同类型的右括号闭合。
2. 左括号必须以正确的顺序闭合。
比如 "()"、"()[]{}"、"{[]}" 都是有效字符串,而 "(]"、"([)]" 则不是。
这个问题实际是 Leetcode 的一道题目,我们可以利用栈 Stack 来解决这个问题。
- 首先我们将括号间的对应规则存放在
Map中,这一点应该毋容置疑; - 创建一个栈。遍历字符串,如果字符是左括号就直接加入
stack中,否则将stack的栈顶元素与这个括号做比较,如果不相等就直接返回 false。遍历结束,如果stack为空,返回true。
public boolean isValid(String s){// 括号之间的对应规则HashMap<Character, Character> mappings = new HashMap<Character, Character>();mappings.put(')', '(');mappings.put('}', '{');mappings.put(']', '[');Stack<Character> stack = new Stack<Character>();char[] chars = s.toCharArray();for (int i = 0; i < chars.length; i++) {if (mappings.containsKey(chars[i])) {char topElement = stack.empty() ? '#' : stack.pop();if (topElement != mappings.get(chars[i])) {return false;}} else {stack.push(chars[i]);}}return stack.isEmpty();
}
3.2.3 反转字符串
将字符串中的每个字符先入栈再出栈就可以了。
3.2.4 维护函数调用
最后一个被调用的函数必须先完成执行,符合栈的 后进先出(LIFO, Last In First Out) 特性。
例如递归函数调用可以通过栈来实现,每次递归调用都会将参数和返回地址压栈。
3.2.5深度优先遍历
在深度优先搜索过程中,栈被用来保存搜索路径,以便回溯到上一层。
3.3 栈的实现
栈既可以通过数组实现,也可以通过链表来实现。不管基于数组还是链表,入栈、出栈的时间复杂度都为 O(1)。
下面我们使用数组来实现一个栈,并且这个栈具有push()、pop()(返回栈顶元素并出栈)、peek() (返回栈顶元素不出栈)、isEmpty()、size()这些基本的方法。
public class MyStack {private int[] storage;//存放栈中元素的数组private int capacity;//栈的容量private int count;//栈中元素数量private static final int GROW_FACTOR = 2;//不带初始容量的构造方法。默认容量为8public MyStack() {this.capacity = 8;this.storage=new int[8];this.count = 0;}//带初始容量的构造方法public MyStack(int initialCapacity) {if (initialCapacity < 1)throw new IllegalArgumentException("Capacity too small.");this.capacity = initialCapacity;this.storage = new int[initialCapacity];this.count = 0;}//入栈public void push(int value) {if (count == capacity) {ensureCapacity();}storage[count++] = value;}//确保容量大小private void ensureCapacity() {int newCapacity = capacity * GROW_FACTOR;storage = Arrays.copyOf(storage, newCapacity);capacity = newCapacity;}//返回栈顶元素并出栈private int pop() {if (count == 0)throw new IllegalArgumentException("Stack is empty.");count--;return storage[count];}//返回栈顶元素不出栈private int peek() {if (count == 0){throw new IllegalArgumentException("Stack is empty.");}else {return storage[count-1];}}//判断栈是否为空private boolean isEmpty() {return count == 0;}//返回栈中元素的个数private int size() {return count;}}
验证:
MyStack myStack = new MyStack(3);
myStack.push(1);
myStack.push(2);
myStack.push(3);
myStack.push(4);
myStack.push(5);
myStack.push(6);
myStack.push(7);
myStack.push(8);
System.out.println(myStack.peek());//8
System.out.println(myStack.size());//8
for (int i = 0; i < 8; i++) {System.out.println(myStack.pop());
}
System.out.println(myStack.isEmpty());//true
myStack.pop();//报错:java.lang.IllegalArgumentException: Stack is empty.
4 队列
4.1 队列简介
队列是先进先出 (FIFO,First In, First Out) 的线性表。在具体应用中通常用链表或者数组来实现,用数组实现的队列叫作 顺序队列 ,用链表实现的队列叫作 链式队列 。队列只允许在后端(rear)进行插入操作也就是入队 enqueue,在前端(front)进行删除操作也就是出队 dequeue
队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。
假设队列中有n个元素。
访问:O(n)//最坏情况
插入删除:O(1)//后端插入前端删除元素
4.2 队列分类
4.2.1 单队列
单队列就是常见的队列, 每次添加元素时,都是添加到队尾。单队列又分为 顺序队列(数组实现) 和 链式队列(链表实现)。
顺序队列存在“假溢出”的问题也就是明明有位置却不能添加的情况。
假设下图是一个顺序队列,我们将前两个元素 1,2 出队,并入队两个元素 7,8。当进行入队、出队操作的时候,front 和 rear 都会持续往后移动,当 rear 移动到最后的时候,我们无法再往队列中添加数据,即使数组中还有空余空间,这种现象就是 ”假溢出“ 。除了假溢出问题之外,如下图所示,当添加元素 8 的时候,rear 指针移动到数组之外(越界)。
为了避免当只有一个元素的时候,队头和队尾重合使处理变得麻烦,所以引入两个指针,front 指针指向对头元素,rear 指针指向队列最后一个元素的下一个位置,这样当 front 等于 rear 时,此队列不是还剩一个元素,而是空队列。——From 《大话数据结构》

4.2.2 循环队列
循环队列可以解决顺序队列的假溢出和越界问题。解决办法就是:从头开始,这样也就会形成头尾相接的循环,这也就是循环队列名字的由来。
还是用上面的图,我们将 rear 指针指向数组下标为 0 的位置就不会有越界问题了。当我们再向队列中添加元素的时候, rear 向后移动。

顺序队列中,我们说 front==rear 的时候队列为空,循环队列中则不一样,也可能为满,如上图所示。解决办法有两种:
- 可以设置一个标志变量
flag,当front==rear并且flag=0的时候队列为空,当front==rear并且flag=1的时候队列为满。 - 队列为空的时候就是
front==rear,队列满的时候,我们保证数组还有一个空闲的位置,rear 就指向这个空闲位置,如下图所示,那么现在判断队列是否为满的条件就是:(rear+1) % QueueSize==front。
4.2.3 双端队列
双端队列 (Deque) 是一种在队列的两端都可以进行插入和删除操作的队列,相比单队列来说更加灵活。
一般来说,我们可以对双端队列进行 addFirst、addLast、removeFirst 和 removeLast 操作。
4.2.4 优先队列
优先队列 (Priority Queue) 从底层结构上来讲并非线性的数据结构,它一般是由堆来实现的。
- 在每个元素入队时,优先队列会将新元素其插入堆中并调整堆。
- 在队头出队时,优先队列会返回堆顶元素并调整堆。
关于堆的具体实现可以看堆这一节。
总而言之,不论我们进行什么操作,优先队列都能按照某种排序方式进行一系列堆的相关操作,从而保证整个集合的有序性。
虽然优先队列的底层并非严格的线性结构,但是在我们使用的过程中,我们是感知不到堆的,从使用者的眼中优先队列可以被认为是一种线性的数据结构:一种会自动排序的线性队列。
4.3 队列的常见应用场景
当我们需要按照一定顺序来处理数据的时候可以考虑使用队列这个数据结构。
- 阻塞队列: 阻塞队列可以看成在队列基础上加了阻塞操作的队列。当队列为空的时候,出队操作阻塞,当队列满的时候,入队操作阻塞。使用阻塞队列我们可以很容易实现“生产者 - 消费者“模型。
- 线程池中的请求/任务队列: 线程池中没有空闲线程时,新的任务请求线程资源时,线程池该如何处理呢?答案是将这些请求放在队列中,当有空闲线程的时候,会循环中反复从队列中获取任务来执行。队列分为无界队列(基于链表)和有界队列(基于数组)。无界队列的特点就是可以一直入列,除非系统资源耗尽,比如:FixedThreadPool 使用无界队列 LinkedBlockingQueue。但是有界队列就不一样了,当队列满的话后面再有任务/请求就会拒绝,在 Java 中的体现就是会抛出java.util.concurrent.RejectedExecutionException 异常。
- 栈:双端队列天生便可以实现栈的全部功能(push、pop 和 peek),并且在 Deque 接口中已经实现了相关方法。Stack 类已经和 Vector 一样被遗弃,现在在 Java 中普遍使用双端队列(Deque)来实现栈。
- 广度优先搜索(BFS),在图的广度优先搜索过程中,队列被用于存储待访问的节点,保证按照层次顺序遍历图的节点。
- Linux 内核进程队列(按优先级排队)
- 现实生活中的派对,播放器上的播放列表;
- 消息队列
- 等等……
相关文章:
数据结构秘籍(一)线性数据结构
1.数组 数组(Array)是一种很常见的数据结构。它由相同类型的元素(element)组成,并且是使用一块连续的内存来存储。 我们直接可以利用元素的索引(index)计算出该元素对应的存储地址。 数组的特…...
推荐律师事务管理系统(SpringCloud+mysql+rocketmq+deepseek)
1.深圳慧钛科技有限公司成立于2024年7月24日,官网地址:深圳慧钛律师事务管理系统(官网)-案件管理系统-律所档案管理-律所管理软件-律师办案系统-电子签章-律所印章-律师办公软件、律师办公系统、律所OA 。系统访问地址:深圳慧钛律…...
mysql怎样优化where like ‘%字符串%‘这种模糊匹配的慢sql
一 问题描述 工作中经常遇到这种模糊匹配的慢sql: select * from 表名 where 字段 like %字符串%; 由于前面有%,导致无法走该字段上的索引。 二 解决办法 ① 给该字段创建一个全文索引 CREATE FULLTEXT INDEX 索引名 ON 表名 (字段名); ② 改写sq…...
SpringSecurity基于JWT实现Token的处理
前面介绍了手写单点登录和JWT的应用,本文结合SpringSecurity来介绍下在SpringBoot项目中基于SpringSecurity作为认证授权框架的情况下如何整合JWT来实现Token的处理。 一、认证思路分析 SpringSecurity主要是通过过滤器来实现功能的!我们要找到SpringSecurity实现认证和校验…...
让AI“看见”光影变幻!华为云专利解锁动态光源渲染新境界
华为云计算技术有限公司(申请人,申请号:202311653495.3)通过一项创新专利,首次实现隐式对象模型与显式渲染管线深度融合,让动态光源下的图像渲染真实度与灵活性兼得! 一、技术深度解析 技术背景…...
Linux(centos)系统安装部署MySQL8.0数据库(GLIBC版本)
前言 MySQL 是一款开源的关系型数据库管理系统(RDBMS),主要用于结构化数据的存储、管理和检索。 一、检查环境 安装前检查服务器glibc版本,下载对应版本包 rpm -qa | grep glibc mysql安装包及依赖包已整理好,…...
Redis缓存一致性难题:如何让数据库和缓存不“打架”?
标题:Redis缓存一致性难题:如何让数据库和缓存不“打架”?(附程序员脱发指南) 导言:当数据库和缓存成了“异地恋” 想象一下:你刚在美团下单了一份麻辣小龙虾,付款后刷新页面&#…...
【R包】pathlinkR转录组数据分析和可视化利器
介绍 通常情况下,基因表达研究如微阵列和RNA-Seq会产生数百到数千个差异表达基因(deg)。理解如此庞大的数据集的生物学意义变得非常困难,尤其是在分析多个条件和比较的情况下。该软件包利用途径富集和蛋白-蛋白相互作用网络&…...
PyCharm 的使用 + PyCharm快捷键 + 切换中文界面
2025 - 02 - 27 - 第 62 篇 Author: 郑龙浩 / 仟濹 【PyCharm的使用】 文章目录 如何使用Pycharm1 新建工程,新建 .py 文件,运行2 常用快捷键3 其他快捷键 - DeepSeek 总结如下**代码编辑****导航与定位****查找与替换****运行与调试****代码重构****其…...
1.68M 免安装多格式图片批量转 webp 无广告软件推荐
软件介绍 今天要给大家分享一款超实用的图片处理工具,它能实现多格式图片向 webp 格式的转换,无论是 jpg、png、tif、gif 还是 webp 格式自身的图片,都能批量且借助多线程技术进行转换。 直接打开就能用,体积小巧,仅 …...
总结gcc与msvc在标准库实现上的不同
1. std::string::data()的返回类型区别 在C17以及之前的标准中,std::string::data()仅有一个返回类型const char *,MSVC遵守了这个规定。而GCC很早就有非标准扩展,重载了一个 char *data() noexcept;C20标准引入了这个非标准扩展。...
《Qt窗口动画实战:Qt实现呼吸灯效果》
Qt窗口动画实战:Qt实现呼吸灯效果 在嵌入式设备或桌面应用中,呼吸灯效果是一种常见且优雅的UI动画,常用于指示系统状态或吸引用户注意。本文将介绍如何使用Qt动画框架实现平滑的呼吸灯效果。 一、实现原理 利用Qt自带的动画框架来实现&…...
Rider 安装包 绿色版 Win/Mac/Linux 适合.NET和游戏开发者使用 2025全栈开发终极指南:从零配置到企业级实战
下载链接: https://pan.baidu.com/s/1cfkJf6Zgxc1XfYrVpwtHkA?pwd1234 导语:JetBrains Rider以跨平台支持率100%、深度.NET集成和智能代码分析能力,成为2025年全栈开发者的首选工具。本文涵盖环境配置、核心功能、框架集成、性能调优、团队…...
CVE-2025-1094: 通过 WebSocket 的 SQL 注入到 RCE
该存储库包含一个针对 CVE-2025-1094 的概念验证(PoC)漏洞利用,该漏洞存在于 PostgreSQL 中,允许通过 WebSocket 劫持将 SQL 注入(SQLi)攻击升级为远程代码执行(RCE)。 概述 该漏洞利用 PostgreSQL 中的 SQL 注入漏洞,注入恶意代码读取敏感文件(如 /etc/passwd),…...
详解Tomcat下载安装以及IDEA配置Tomcat(2023最新)
目录 步骤一:首先确认自己是否已经安装JDK步骤二:下载安装Tomcat步骤三:Tomcat配置环境变量步骤四:验证Tomcat配置是否成功步骤五:为IDEA配置Tomcat 步骤一:首先确认自己是否已经安装JDK jdk各版本通用安…...
AI如何通过大数据分析提升制造效率和决策智能化
人工智能(AI)与大数据技术的融合,不仅重新定义了生产流程,更让企业实现了从“经验驱动”到“数据智能驱动”的跨越式升级。 从“模糊经验”到“精准洞察” 传统制造业依赖人工经验制定生产计划,但面对复杂多变的市…...
开源程序wordpress在海外品牌推广中的重要作用
WordPress作为全球最流行的开源内容管理系统(CMS),在全球网站搭建中占据超过40%的市场份额。其强大的功能、灵活性和易用性使其成为企业进行海外品牌推广的首选平台。以下是WordPress在海外品牌推广中的重要性分析: 1. 多语言支持与本地化 WordPress通…...
kafka-关于ISR-概述
一. 什么是ISR ? Kafka 中通常每个分区都有多个副本,其中一个副本被选举为 Leader,其他副本为 Follower。ISR 是指与 Leader 副本保持同步的 Follower 副本集合。ISR 机制的核心是确保数据在多个副本之间的一致性和可靠性,同时在 …...
Android 8.0 (API 26) 对广播机制做了哪些变化
大部分隐式广播无法通过静态注册接收,除了以下白名单广播: ACTION_BOOT_COMPLETED ACTION_TIMEZONE_CHANGED ACTION_LOCALE_CHANGED ACTION_MY_PACKAGE_REPLACED ACTION_PACKAGE_ADDED ACTION_PACKAGE_REMOVED 需要以动态注册方案替换: cl…...
使用 Polars 进行人工智能医疗数据分析(ICU数据基本测试篇)
引言 在医疗领域,数据就是生命的密码,每一个数据点都可能蕴含着拯救生命的关键信息。特别是在 ICU 这样的重症监护场景中,医生需要实时、准确地了解患者的病情变化,以便做出及时有效的治疗决策。而随着医疗技术的飞速发展&#x…...
超过DeepSeek、o3,Claude发布全球首个混合推理模型,并将完成新一轮35亿美元融资...
Anthropic于2025年2月25日发布全球首个“混合推理”AI模型Claude 3.7 Sonnet,并在融资层面取得重大进展,计划完成35亿美元的新一轮融资,估值将达615亿美元。以下是核心信息整理: 技术突破:双思维模型与代码能力 1. 混合…...
# C# 中堆(Heap)与栈(Stack)的区别
在 C# 中,堆和栈是两种不同的内存分配机制,它们在存储位置、生命周期、性能和用途上存在显著差异。理解堆和栈的区别对于优化代码性能和内存管理至关重要。 1. 栈(Stack) 1.1 定义 栈是一种后进先出(LIFO࿰…...
OmniParser v2本地部署(2)部署omnitool(包含自动化控制工具)
1 配置omniparserserver 1.1 配置conda环境、下载依赖和权重 我建议按照OmniParser v2本地部署(1)部署OmniParser_v2模型先设置一次,其中所创造的conda环境,和这一步相似 1.2 启动omniparserserver 进入OmniParser/omnitool/o…...
“深入解析 SQL Server 子查询:从基础到应用”
目录 引言什么是子查询? 子查询的定义子查询的类型 子查询的使用 标量子查询多行子查询多列子查询相关子查询 子查询的性能优化子查询的实际案例总结 引言 在 SQL Server 中,子查询是一种强大的工具,允许我们在一个查询中嵌套另一个查询&am…...
音频进阶学习十六——LTI系统的差分方程与频域分析一(频率响应)
文章目录 前言一、差分方程的有理式1.差分方程的有理分式2.因果系统和ROC3.稳定性与ROC 二、频率响应1.定义2.幅频响应3.相频响应4.群延迟 总结 前言 本篇文章会先复习Z变换的有理分式,这是之前文章中提过的内容,这里会将差分方程和有理分式进行结合来看…...
JavaWeb-ServletContext应用域接口
文章目录 ServletContext接口简介获取一个ServletContext对象ServletContext接口中的相关方法获取应用域配置参数关于应用域参数的配置要求getContextPath获取项目路径getRealPath获取真实路径log系列方法添加相关日志增删查应用域属性 ServletContext接口简介 ServletContext…...
为什么@Autowired 在属性上被警告,在 setter 方法上不被警告
在 Spring 开发中,Autowired 注解常用于实现依赖注入。它可以应用于类的 属性、构造器 或 setter 方法 上。然而,当 Autowired 注解在 属性 上使用时,IntelliJ IDEA 等 IDE 会给出 Field injection is not recommended 的警告,而在…...
SQL命令详解之操作数据表
操作数据表 操作数据表是数据库管理系统中用于存储、管理和操作数据的核心结构。数据表通常由行和列组成,每一列代表一种数据类型(例如,整数、字符、日期等),而每一行代表一条记录(即数据项&a…...
Linux 下使用tracepath进行网络诊断分析
简介 tracepath 命令是 Linux 中的一个网络诊断工具,类似于 traceroute ,但专门用于跟踪到目标主机的网络路径,同时自动处理路径MTU发现。这是一种简单的方法,可以找出机器和远程目的地之间的跃点,同时还可以识别沿途…...
四、表关系与复杂查询
一、表关系设计与约束 1. 表关系类型与实现 关系类型实现方式示例场景一对一共享主键 或 外键唯一约束用户 ↔ 用户详细信息一对多外键约束部门 ↔ 员工多对多中间表 联合主键学生 ↔ 课程 2. 核心约束类型 -- 完整表创建示例(含约束) CREATE TABLE…...

