谈谈几个常见数据结构的原理

news2025/3/12 17:09:21

数组

数组是最常用的数据结构,创建数组必须要内存中一块 连续 的空间,并且数组中必须存放 相同 的数据类型。比如我们创建一个长度为10,数据类型为整型的数组,在内存中的地址是从1000开始,那么它在内存中的存储格式如下。

alt

由于每个整型数据占据4个字节的内存空间,因此整个数组的内存空间地址是1000~1039,根据这个,我们就可以轻易算出数组中每个数据的内存下标地址。利用这个特性,我们只要知道了数组下标,也就是数据在数组中的位置,比如下标2,就可以计算得到这个数据在内存中的位置1008,从而对这个位置的数据241进行快速读写访问,时间复杂度为O(1)。

随机快速读写是数组的一个重要特性,但是要随机访问数据,必须知道数据在数组中的下标。如果我们只是知道数据的值,想要在数组中找到这个值,那么就只能遍历整个数组,时间复杂度为O(N)。

链表

不同于数组必须要连续的内存空间,链表可以使用零散的内存空间存储数据。不过,因为链表在内存中的数据不是连续的,所以链表中的每个数据元素都必须包含一个指向下一个数据元素的内存地址指针。如下图,链表的每个元素包含两部分,一部分是数据,一部分是指向下一个元素的地址指针。最后一个元素指向null,表示链表到此为止。

alt

因为链表是不连续存储的,要想在链表中查找一个数据,只能遍历链表,所以链表的查找复杂度总是O(N)。

但是正因为链表是不连续存储的,所以在链表中插入或者删除一个数据是非常容易的,只要找到要插入(删除)的位置,修改链表指针就可以了。如图,想在b和c之间插入一个元素x,只需要将b指向c的指针修改为指向x,然后将x的指针指向c就可以了。

alt

相比在链表中轻易插入、删除一个元素这种简单的操作,如果我们要想在数组中插入、删除一个数据,就会改变数组连续内存空间的大小,需要重新分配内存空间,这样要复杂得多。

Hash表

前面说过,对数组中的数据进行快速访问必须要通过数组的下标,时间复杂度为O(1)。如果只知道数据或者数据中的部分内容,想在数组中找到这个数据,还是需要遍历数组,时间复杂度为O(N)。

事实上,知道部分数据查找完整数据的需求在软件开发中会经常用到,比如知道了商品ID,想要查找完整的商品信息;知道了词条名称,想要查找百科词条中的详细信息等。

这类场景就需要用到Hash表这种数据结构。Hash表中数据以Key、Value的方式存储,上面例子中,商品ID和词条名称就是Key,商品信息和词条详细信息就是Value。存储的时候将Key、Value写入Hash表,读取的时候,只需要提供Key,就可以快速查找到Value。

Hash表的物理存储其实是一个数组,如果我们能够根据Key计算出数组下标,那么就可以快速在数组中查找到需要的Key和Value。许多编程语言支持获得任意对象的 HashCode,比如Java 语言中 HashCode 方法包含在根对象 Object 中,其返回值是一个 Int。我们可以利用这个Int类型的HashCode计算数组下标。最简单的方法就是余数法,使用 Hash 表的数组长度对 HashCode 求余, 余数即为 Hash 表数组的下标,使用这个下标就可以直接访问得到 Hash 表中存储的 Key、Value。

alt

上图这个例子中,Key是字符串abc,Value是字符串hello。我们先计算Key的哈希值,得到101这样一个整型值。然后用101对8取模,这个8是哈希表数组的长度。101对8取模余5,这个5就是数组的下标,这样就可以把(“abc”,“hello”)这样一个Key、Value值存储在下标为5的数组记录中。

当我们要读取数据的时候,只要给定Key abc,还是用这样一个算法过程,先求取它的HashCode 101,然后再对8取模,因为数组的长度不变,对8取模以后依然是余5,那么我们到数组下标中去找5的这个位置,就可以找到前面存储进去的abc对应的Value值。

但是如果不同的Key计算出来的数组下标相同怎么办?HashCode101对8取模余数是5,HashCode109对8取模余数还是5,也就是说,不同的Key有可能计算得到相同的数组下标,这就是所谓的Hash冲突,解决Hash冲突常用的方法是链表法。

事实上,(“abc”,“hello”)这样的Key、Value数据并不会直接存储在Hash表的数组中,因为数组要求存储固定数据类型,主要目的是每个数组元素中要存放固定长度的数据。所以,数组中存储的是Key、Value数据元素的地址指针。一旦发生Hash冲突,只需要将相同下标,不同Key的数据元素添加到这个链表就可以了。查找的时候再遍历这个链表,匹配正确的Key。

如下图:

alt

因为有Hash冲突的存在,所以“Hash表的时间复杂度为什么是O(1)?”这句话并不严谨,极端情况下,如果所有Key的数组下标都冲突,那么Hash表就退化为一条链表,查询的时间复杂度是O(N)。但是作为一个面试题,“Hash表的时间复杂度为什么是O(1)”是没有问题的。

数组和链表都被称为线性表,因为里面的数据是按照线性组织存放的,每个数据元素的前面只能有一个(前驱)数据元素,后面也只能有一个(后继)数据元素,所以称为线性表。但是对数组和链表的操作可以是随机的,可以对其上任何元素进行操作,如果对操作方式加以限制,就形成了新的数据结构。

栈就是在线性表的基础上加了这样的操作限制条件:后面添加的数据,在删除的时候必须先删除,即通常所说的“后进先出”。我们可以把栈可以想象成一个大桶,往桶里面放食物,一层一层放进去,如果要吃的时候,必须从最上面一层吃,吃了几层后,再往里放食物,还是从当前的最上面一层放起。

alt

栈在线性表的基础上增加了操作限制,具体实现的时候,因为栈不需要随机访问、也不需要在中间添加、删除数据,所以可以用数组实现,也可以用链表实现。那么在顺序表的基础上增加操作限制有什么好处呢?

我们上篇提到的程序运行过程中,方法的调用需要用栈来管理每个方法的工作区,这样,不管方法如何嵌套调用,栈顶元素始终是当前正在执行的方法的工作区。这样,事情就简单了。而简单,正是我们做软件开发应该努力追求的一个目标。

队列

队列也是一种操作受限的线性表,栈是后进先出,而队列是先进先出。

alt

在软件运行期,经常会遇到资源不足的情况:提交任务请求线程池执行,但是线程已经用完了,任务需要放入队列,先进先出排队执行;线程在运行中需要访问数据库,数据库连接有限,已经用完了,线程进入阻塞队列,当有数据库连接释放的时候,从阻塞队列头部唤醒一个线程,出队列获得连接访问数据库。

我在上面讲堆栈的时候,举了一个大桶放食物的例子,事实上,如果用这种方式存放食物,有可能最底下食物永远都吃不到,最后过期了。

现实中也是如此,超市在货架上摆放食品的时候,其实是按照队列摆放的,而不是堆栈摆放的。工作人员在上架新食品的时候,总是把新食品摆在后面,使食品成为一个队列,以便让以前上架的食品被尽快卖出。

数组、链表、栈、队列都是线性表,也就是每个数据元素都只有一个前驱,一个后继。而树则是非线性表,树是这样的。

alt

软件开发中,也有很多地方用到树,比如我们要开发一个OA系统,部门的组织结构就是一棵树;我们编写的程序在编译的时候,第一步就是将程序代码生成抽象语法树。传统上树的遍历使用递归的方式,而我个人更喜欢用设计模式中的组合模式进行树的遍历,具体我将会在设计模式部分详细讨论。

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/640538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【送书福利-第十期】清华社 IT BOOK 多得图书活动 ~!

大家好,我是洲洲,欢迎关注,一个爱听周杰伦的程序员。关注公众号【程序员洲洲】即可获得10G学习资料、面试笔记、大厂独家学习体系路线等…还可以加入技术交流群欢迎大家在CSDN后台私信我! 本文目录 一、前言二、内容介绍三、抽奖方…

第一章JUC概述

文章目录 什么是JUC为什么学习好多线程很重要硬件来说软件来说存在的问题 Java多线程相关概念一把锁两个并三个程进程和程序的联系线程和进程的联系管程 用户线程和守护线程JAVA线程 什么是JUC 在 Java 5.0 提供了 java.util.concurrent ( 简称JUC ) 包…

Spring Cloud Alibaba - Sentinel源码分析

目录 一、Sentinel核心源码分析 1、Sentinel核心概念 1.1、Node之间的关系 2、Sentinel源码入口 2.1、SlotChain解析 2.2、NodeSelectorSlot解析 2.3、ClusterBuilderSlot解析 一、Sentinel核心源码分析 Sentinel是分布式系统的防御系统。以流量为切入点,通过…

001安装Jenkins

安装JenkinsJenkins 是一个开源自动化服务器http://www.jenkins.io/zh/doc/book/installing/#%E7%B3%BB%E7%BB%9F%E8%A6%81%E6%B1%82 docker docker run \-u root \--rm \-d \-p 8080:8080 \-p 50000:50000 \-v jenkins-data:/var/jenkins_home \-v /var/run/docker.sock:/va…

传感器融合概念及对比

1.多传感器融合的定义 传感器数据融合的定义可以概括为把分布在不同位置的多个同类或不同类传感器所提供的局部数据资源加以综合,采用计算机技术对其进行分析,消除多传感器信息之间可能存在的冗余和矛盾,加以互补,降低其不确实性…

记录好项目D2

记录好项目 你好呀,这里是我专门记录一下从某些地方收集起来的项目,对项目修改,进行添砖加瓦,变成自己的闪亮项目。修修补补也可以成为毕设哦 本次的项目是个旅游门户网站 技术栈:JSPjQueryAjaxechartsSpringSpring…

第四章LockSupport与线程中断

文章目录 线程中断机制面试题什么是中断机制?实现三种中断方式通过一个volatile变量实现通过AtomicBoolean(原子布尔型)通过Thread类自带的中断api方法实现 API源码分析当前线程的中断标识为true,是不是线程就立刻停止?后手案例-…

Vector-常用CAN工具 - Vector Hardware Manager

本文提供了有关 Vector 用于配置 Vector 接口的新工具Vector 硬件管理器(vHardwareManager) 的一些一般信息。 常见问题 1、什么是vHardwareManager? 2、哪些接口支持vHardwareManager? 3、什么时候需要vHardwareManager? 4、哪里可以下…

chatgpt赋能python:Python如何截图运行效果?

Python如何截图运行效果? 如果你是一位有10年python编程经验的工程师,那么你一定知道在编写程序时调试和调整非常重要。为了更好地调试程序,Python提供了许多进行程序运行效果截图的方法。本文将介绍几种常用的Python截图方法以及它们的优缺…

chatgpt赋能python:Python如何截图运行结果

Python如何截图运行结果 介绍 Python是一种高级编程语言,非常流行。它具有许多有用的功能和库,使其成为许多开发人员的首选编程语言之一。但是,当您运行Python程序并需要与他人共享结果时,您可能需要截图运行结果。在本文中&…

编译3D渲染引擎Horde3D

Horde3D是Github上一款开源的轻量级3D渲染引擎,同时它还支持多个平台。今天我们准备在Mac平台上交叉编译至Android平台。如果需要同时能编译Sample,那么还需要SDL2库。默认情况下,编译Horde3D时不强制下载SDL2,你可以选择强制下载…

chatgpt赋能python:Python怎么快速入门?

Python怎么快速入门? Python是一种易学易用的编程语言。它被广泛应用于各种领域,例如数据科学、自动化、Web开发、游戏开发等等。无论你是从事什么领域,在Python的快速入门上花费越少的时间越好。在本文中,我们将介绍Python的基础…

工作二--注意!!!

1、激活单元格,做数据回显 2、单元格退出编辑模式时,让 实时保存 3、获取数据时,用getGIUID 给数据 唯一id,以防数据名重复 数据结构:是结合接口的id 等 和 组件的数据结构 4、父子结构 下拉框中的 :value 把value值 …

网络安全如何6个月成功上岸?

学前感言: 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发. 3.有时多 google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答. 4.遇到实在搞不懂的,可以先放放,以后再来解…

Haproxy搭建Web高可用群集 【Keepalived+HAProxy 高可用 日志定义 内核优化】

Haproxy HAProxy是可提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,是免费、快速并且可靠的一种解决方案。HAProxy非常适用于并发大(并发达1w以上)web站点,这些站点通常又需要会话保持或七层处理。HAProxy的运行模式使得它可…

Oracle 查询优化改写(第一章)

第一章 单表查询 1.查询空值 2.将空值转换为实际值 不采用nvl()函数,而使用COALESCE函数语法为COALESCE(表达式1,表达式2,...,表达式n),n>2,此表达式的功能为返回第一个不为空的表达式,如果都为空则返回空值。 注…

每日算法总结——回溯算法、 LeetCode 131. 分割回文串

LeetCode 131. 分割回文串 131. 分割回文串 - 力扣(LeetCode) 什么是回溯算法? 回溯算法真的是解决排列问题的一大利器,其实很多时候自己不经意间就写出了回溯算法,但是一直没有一个系统的认识,今天做一…

B树:数据结构中的平衡之道

目录 引言:一、定义:二、特点:三、应用场景:总结: 引言: 在计算机科学领域中,数据结构是构建和组织数据的重要工具。其中,B树(B-tree)作为一种自平衡的搜索树…

用RDMA重新思考有状态流处理

摘要 远程直接内存访问 (RDMA) 硬件弥合了网络和主要内存速度之间的差距,从而验证了网络通常是分布式数据处理系统中的瓶颈的常见假设。然而,高速网络并没有提供“即插即用”的性能(例如,使用 IP-overInfiniBand)&…

第二章 数据处理篇:transforms

教程参考: https://pytorch.org/tutorials/ https://github.com/TingsongYu/PyTorch_Tutorial https://github.com/yunjey/pytorch-tutorial 详细的transform的使用样例可以参考:ILLUSTRATION OF TRANSFORMS 文章目录 为什么要使用transformstransforms方…