重生之数据结构与算法----数组链表
简介
数据结构的本质,只有两种结构,数组与链表。其它的都是它的衍生与组合算法的本质就是穷举。
数组
数组可以分为两大类,静态数组与动态数组。静态数组的本质是一段连续的内存,因为是连续的,所以我们可以采用偏移量的方式来对元素实现快速访问。而动态数组则是对静态数组的封装,使得更加方便操作元素。有了动态数组,后续的栈,哈希,队列都能更加优雅的实现。
静态数组
-
数组的超能力随机访问。只要任意一个索引,都能推测出元素的内存地址,而计算机的内存寻址能力为Log(1),所以数组的随机访问时间复杂度也同样为Log(1)
-
数组的局限性由于数组的大小是固定的,所以当数组满了,或者需要在中间插入/删除时。都需要移动元素,这时候时间复杂度就上升为Log(N)
动态数组
动态数组无法解决静态数组Log(N)的问题,它只是帮你隐藏了动态扩容与元素搬移的过程,以及更加易用的API。
数组随机访问的超能力源于数组连续的内存空间,而连续的内存空间就不可避免地面对元素搬移和扩缩容的问题
一个简单的动态数组
public class MyList<T>(){//真正存储数据的底层private T[] arr = new T[5];//记录元素的数量public int Count { get; private set; }/// <summary>/// 增/// </summary>/// <param name="item"></param>public void Add(T item){if (Count == arr.Length){//扩容Resize(Count * 2);}arr[Count] = item;Count++;}/// <summary>/// 删/// </summary>/// <param name="idx"></param>public void RemoveAt(int idx){if (Count == arr.Length / 4){//缩容Resize(arr.Length / 2);}Count--;for (int i = idx; i < Count; i++){arr[i] = arr[i + 1];}arr[Count] = default(T);}public void Remove(T item){var idx = FindIndex(item);RemoveAt(idx);}/// <summary>/// 改/// </summary>/// <param name="idx"></param>/// <param name="newItem"></param>public void Put(int idx,T newItem){arr[idx] = newItem;}/// <summary>/// 查/// </summary>/// <param name="item"></param>/// <returns></returns>public int FindIndex(T item){for(int i = 0; i < arr.Length; i++){if (item.Equals(arr[i]))return i;}return -1;}/// <summary>/// 扩容/缩容操作/// </summary>/// <param name="initCapacity"></param>private void Resize(int initCapacity){var newArray=new T[initCapacity];for(var i = 0; i < Count; i++){newArray[i] = arr[i];}arr = newArray;}}
数组的变种:环形数组
有人可能会问了?数组不是一段连续的内存吗?怎么可能是环形的?从物理角度出发,这确实不可能。但从逻辑角度出发,这是有可能的。其核心内容就是利用求模运算
public static void Run(){var arr = new int[] { 1, 2, 3, 4, 5, 6 };var i = 0;while (arr.Length > 0){Console.WriteLine(arr[i]);//关键代码在此,当i遍历到末尾时,i+1与arr.Length去余数变成0//从逻辑上完成了闭环i = (i + 1) % arr.Length;if ((i % arr.Length) == 0){Console.WriteLine("完成了一次循环,i归零");Thread.Sleep(1000);}}}
环形数组的关键在于,它维护了两个指针 start 和 end,start 指向第一个有效元素的索引,end 指向最后一个有效元素的下一个位置索引环形数组解决了什么问题?数组在头部增删从O(N),优化为O(1)
链表
链表分为单链表与双链表,单链表只有一个指针,指向next元素。双链表有两个指针,分别指向previous与next。除此之外并无其它区别。主要功能区别在于能否向前遍历。
为什么需要链表
前面说到,数组的本质是一段连续的内存,当元素移动/扩容时,需要one by one 移动,花销很大。那有没有一种能突破内存限制的数据结构呢?链表就应运而生。链表不需要连续内存,它们可以分配在天南海北,它们之间的联系靠next/prev链接,将零散的元素串成一个链式结构。
这么做有两个好处
-
提高内存利用率,分配在哪都可以。所以可以降低内存碎片
-
方便扩容与移动,只需要重新指向next/previous 即可实现增,删,改等操作,无需移动元素与扩容。
但万物皆有代价,因为链表的不连续性,所以无法利用快速随机访问来定位元素,只能一个一个的遍历来确定元素。因此链表的查询复杂度为Log(N)
一个简单的链表
public class MyLinkedList<T>
{public static void Run(){var linked = new MyLinkedList<string>();linked.AddLast("a");linked.AddLast("b");linked.AddLast("c");linked.AddLast("d");linked.Add(1, "bc");linked.Put(1, "aaaa");Console.WriteLine(linked.ToString()) ;}/// <summary>/// 虚拟头尾节点,有两个好处/// 1.无论链表是否为空, 两个虚拟节点都存在,避免很多边界值处理的情况。/// 2.如果要在尾部插入数据,如果不知道尾节点,那么需要复杂度退化成O(N),因为要从头开始遍历到尾部。/// </summary>private Node _head, _tail; public int Count { get; private set; }public MyLinkedList(){_tail = new Node();_head = new Node();_head.Next = _tail;_tail.Prev = _head;}public void AddLast(T item){var prev = _tail.Prev;var next = _tail;var node = new Node(item);node.Next = next;node.Prev = prev;prev.Next = node;next.Prev = node;Count++;}public void AddFirst(T item){var prev = _head;var next = _head.Next;var node=new Node(item);node.Prev= prev;node.Next= next;prev.Next= node;next.Prev = node;Count++;}public void Add(int idx,T item){var t = Get(idx);var next = t.Next;var prev = t;var node = new Node(item);node.Next = next;node.Prev = prev;prev.Next = node;next.Prev = node;}public void Remove(int idx){var t = Get(idx);var prev = t.Prev;var next = t.Next;prev.Next = next;next.Prev = next;t = null;Count--;}public void Put(int idx,T item){var t = Get(idx);t.Value= item;}private Node? Get(int idx){var node = _head.Next;//这里有个优化空间,可以通过idx在Count的哪个区间。从而决定从head还是从tail开始遍历for (int i = 0; i < idx; i++){node = node.Next;}return node;}public override string ToString(){var sb = new StringBuilder();var node = _head.Next;while (node != null && node.Value != null){sb.Append($"{node.Value}<->");node = node.Next;}sb.Append("null");return sb.ToString();}private class Node{public T? Value { get; set; }public Node Next { get; set; }public Node Prev { get; set; }public Node(){Value=default(T);}public Node(T value){Value = value;}}
}
链表的变种:跳表
在上面简单的例子中,查询的复杂度为O(N),插入的复杂度为O(1).主要消耗在查询操作,只能从头结点开始,逐个遍历到目标节点。所以我们优化的重点就在于优化查询。
上面的例子中,我们使用了虚拟头尾节点来空间换时间,提高插入效率。同样的,我们也可以采用这个思路来提高查询效率
跳表核心原理
index 0 1 2 3 4 5 6 7 8 9
node a->b->c->d->e->f->g->h->i->j
此时此刻,你想拿到h的节点,你需要从0开始遍历直到7。这时候你就想,如果我能提前知道6的位置就好了,这样我就只需要Next就能快速得到h
调表就是如此
indexLevel 0-----------------------8-----10
indexLevel 0-----------4-----------8-----10
indexLevel 0-----2-----4-----6-----8-----10
indexLevel 0--1--2--3--4--5--6--7--8--9--10
nodeLevel a->b->c->d->e->f->g->h->i->j->k
调表在原链表的基础上,增加了多层索引,每向上一层,索引减少一半,所以索引的高度是O(log N)
-
首先从最高层索引开始往下搜,索引7在[0,8]区间
-
从节点0开始,发现7在【4,8】,拿到节点4的地址
-
从节点4开始,发现7在【6,8】,拿到节点6的地址
-
从节点6开始,发现7在【6,7】,最终找到节点7
在搜索的过程中,会经过O(log N)层索引,所以时间复杂度为O(log N)
调表实现比较复杂,当新增与删除时,还需考虑索引的动态调整,需要保证尽可能的二分,否则时间复杂度又会退化为O(N)有点类似自平衡的二叉搜索数,不过相对来说比较简单。
文章转载自:叫我安不理
原文链接:重生之数据结构与算法----数组&链表 - 叫我安不理 - 博客园
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:
重生之数据结构与算法----数组链表
简介 数据结构的本质,只有两种结构,数组与链表。其它的都是它的衍生与组合算法的本质就是穷举。 数组 数组可以分为两大类,静态数组与动态数组。静态数组的本质是一段连续的内存,因为是连续的,所以我们可以采用偏移量的…...
计算机网络常见疑问
tcpip模型没有数据链路层,那课本学的五层模型数据链路层的流量控制可靠传输是事实还是理论? 在计算机网络中,TCP/IP模型与OSI五层模型的分层差异确实容易引发疑问,尤其是关于数据链路层(五层模型)的功能是…...
C++07(继承)
文章目录 面向对象之继承继承相关概念派⽣类声明派⽣类的成员访问属性派⽣类的构造函数与析构函数 面向对象编程编程思想面向对象编程涉及到两个重要的概念类类型的定义**类中数据成员的定义**构建对象成员访问成员访问修饰符——限制成员的可见性构造函数析构函数静态成员共用…...
文件上传漏洞:upload-labs靶场1-10
目录 文件上传漏洞介绍 定义 产生原因 常见危害 漏洞利用方式 upload-labs详解 pass-01 pass-02 pass-03 pass-04 pass-05 pass-06 pass-07 pass-08 pass-09 pass-10 文件上传漏洞介绍 定义 文件上传漏洞是指网络应用程序在处理用户上传文件时,没有…...
【Python/Pytorch】-- 创建3090Ti显卡所需环境
文章目录 文章目录 01 服务器上,存在三个anaconda,如何选择合适的,创建python环境?02 conda、anaconda、cuda、cudnn区别03 用到一些指令04 如何指定cuda的版本?05 conda跟pip的区别?06 pycharm控制台07 服…...
自然语言转SQL之Vanna.ai:AI集成数据库
自然语言转SQL之Vanna.ai:AI集成数据库 一、Vanna.ai是什么二、落地步骤:实现三层需求2.1 官方示例看效果2.2 对接自己的数据库2.3 完全本地化之路 三、构建自己的产品3.1 提问转SQL3.2 执行SQL查询实例2 要实现的功能就是:用中文语言同数据库…...
【零基础到精通Java合集】第二十二集:CMS收集器详解(低延迟的里程碑)
课程标题:CMS收集器详解——低延迟垃圾回收的经典实现(15分钟) 目标:掌握CMS核心工作原理、适用场景与调优策略,理解其在高并发场景下的价值与局限性 0-1分钟:课程引入与CMS设计目标 以“高速公路不停车收费”类比CMS核心思想:在用户线程运行的同时并发回收垃圾,最大…...
2025-03-04 学习记录--C/C++-PTA 习题5-5 使用函数统计指定数字的个数
合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。💪🏻 一、题目描述 ⭐️ 二、代码(C语言)⭐️ #include <stdio.h>int CountDigit( int number, int di…...
SP导入模型设置
法线贴图格式 Blender,Unity选择OpenGL UE,3DMax选择DirectX...
计算机网络——IP地址
一、IP地址是什么? 定义 IP地址是互联网协议(Internet Protocol)为每台联网设备分配的唯一标识符,由一串数字(IPv4)或字母与数字组合(IPv6)构成。 核心作用:定位设备位置…...
openharmony 软总线-设备发现流程
6.1 设备发现流程 6.1.1 Wi-Fi设备发现 6.1.1.1 Wi-Fi设备发现流程 Wi-Fi设备在出厂状态或者恢复出厂状态下,设备上电默认开启SoftAP模式,SoftAP的工作信道在1,6,11中随机选择,SoftAP的Beacon消息中携带的SSID eleme…...
零信任架构和传统网络安全模式的
零信任到底是一个什么类型的模型?什么类型的思想或思路,它是如何实现的,我们要做零信任,需要考虑哪些问题? 零信任最早是约翰金德瓦格提出的安全模型。早期这个模型也是因为在安全研究上考虑的一个新的信任式模型。他最…...
TCP/IP四层模型:从入门到精通
第一部分:基础概念 1.1 什么是TCP/IP? - TCP/IP 是互联网的基础通信协议簇,定义了数据如何在网络中传输和路由。 - 与OSI七层模型的对比:TCP/IP更简化,分为四层,注重实际应用。 1.2 四层模型结构 1. 应…...
二、QT和驱动模块实现智能家居-----问题汇总1
1、文件地址改变后必须在QT下更改地址 2、指定了QT内Kits下的Sysroot头文件地址,但是还是找不到头文件: 3、提示无法执行QT程序:先干掉之前的QT程序 ps //查看程序PIDkill -9 PID 4、无法执行QT程序 1)未设置环境变量 …...
10、HTTP/3有了解过吗?【中高频】
HTTP/2 虽然具有多个流并发传输的能力,但是传输层是 TCP 协议,依然存在缺陷: 队头阻塞,HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整、连续的,当「…...
基于https虚拟主机配置
一、https介绍 http 明文,80/tcp https 密文,443/tcp 二、安全性保障 1、数据安全性 数据加密 2、数据完整性 3、验证身份的真实性、有效性 三、数据安全性 手段:加密 发送方加密数据,接收方解密数据 对称加密算法 加密、解密数据…...
小白入坑向:Java 全栈系统性学习推荐路线之一
文章目录 一、 引言1.1 学习路径:1.2 技术栈全景概述1.3 前沿技术与趋势预判 二、 前端篇2.1 基础知识2.2 进阶技术2.3 前端框架与工具 三、 后端篇3.1 Java 基础3.2 进阶与新特性 四、 企业级框架与开发工具4.1 构建与项目管理4.2 核心框架4.3 数据持久层4.4 微服务…...
云原生存储架构:构建数据永续的新一代存储基础设施
引言:重新定义数据基础设施边界 蚂蚁集团基于Ceph构建的全闪存存储集群达到EB级规模,单集群IOPS突破1亿,延迟稳定在200μs内。Snowflake的存储计算分离架构使其数据湖查询速度提升14倍,存储成本降低82%。Gartner预测到2025年70%企…...
QTableWidget之表格列的隐藏与显示(折叠)
今天晚上花点时间研究一下表格列的显隐问题(类似与excel的隐藏列功能),在网络上搜罗了一通资料,没现成的例子作为借鉴,只能自己研究编写了。现在将过程记录下来,以便日后翻阅。 首先声明:因为时…...
Leetcode3146. 两个字符串的排列差
题目描述: 给你两个字符串 s 和 t,每个字符串中的字符都不重复,且 t 是 s 的一个排列。 排列差 定义为 s 和 t 中每个字符在两个字符串中位置的绝对差值之和。 返回 s 和 t 之间的 排列差 。 代码思路: 建立字符位置映射&…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
