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

数组

数组是最常用的数据结构,创建数组必须要内存中一块 连续 的空间,并且数组中必须存放 相同 的数据类型。比如我们创建一个长度为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 多平台发布

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【lvs集群】HAProxy搭建Web集群

HAProxy搭建Web集群 一、 HAProxy简介1.1HAProxy主要特性1.2HAProxy负载均衡策略非常多,常见的有如下8种1.3LVS、Nginx、HAproxy的区别1.4常见的Web集群调度器 二、Haproxy搭建 Web 群集haproxy服务器部署节点服务器部署 三、定义监控页面与定义日志3.1定义监控页面…

Multimodal fusion via cortical network inspired losses(第一次优质论文分享)

Multimodal fusion via cortical network inspired losses 论文介绍1. 论文研究的任务是什么?2. 论文关注/拟解决的问题是什么?3. 论文提出什么方法如何解决这个问题?4. 如何设计实验 来证明 所提方法确实解决了 拟解决的问题? 论…

kotlin协程flow retry功能函数返回失败后重试(4)

kotlin协程flow retry功能函数返回失败后重试&#xff08;4&#xff09; import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlockingfun main(args: Array<String>) {var count 0 //重试计数runBlocking {load().onEach…

RetinaNet网络介绍

前言 上一篇博文我们介绍了Focal Loss&#xff0c;原理也比较简单&#xff0c;有不了解的小伙伴可以先跳转到之前的博文了解一下。Focal Loss介绍。这篇博文我们来看下Focal Loss的出处&#xff1a;Focal Loss for Dense Object Detection&#xff0c;这篇论文提出了RetainNet之…

chatgpt赋能python:Python怎么建服务器?

Python怎么建服务器&#xff1f; 作为一名具有10年Python编程经验的工程师&#xff0c;我深入研究了Python的一些高级特性&#xff0c;其中包括Python如何建立服务器的方法。Python是一个高级的编程语言&#xff0c;可以轻松创建服务器应用程序&#xff0c;并为您的网站提供高…

低秩矩阵(Low-Rank)的意义

&#xff11;&#xff0e;回顾基础&#xff1a; 矩阵的秩度量的是矩阵行列之间的相关性&#xff0c;如果各行各列都是线性无关的&#xff0c;矩阵就是满秩。非零元素的行或列决定了秩的大小。&#xff0f;&#xff0f;划重点&#xff0c;秩可以度量矩阵自身相关性 讲个小故事…

windows 服务程序和桌面程序集成(七)效果演示及源程序下载

系列文章目录链接 windows 服务程序和桌面程序集成&#xff08;一&#xff09;概念介绍windows 服务程序和桌面程序集成&#xff08;二&#xff09;服务程序windows 服务程序和桌面程序集成&#xff08;三&#xff09;UDP监控工具windows 服务程序和桌面程序集成&#xff08;四…

计算机提示“找不到vcruntime140.dll,无法继续执行代码可”以这样子修复

首先&#xff0c;对于那些不熟悉的人来说&#xff0c;vcruntime140.dll是一个关键文件&#xff0c;用于在Windows操作系统上运行使用C语言编写的大型应用程序。如果你正在运行或安装这样的应用程序&#xff0c;但找不到vcruntime140.dll文件&#xff0c;那么你的应用程序可能无…

Maven私服

Maven 私服是一种特殊的远程仓库&#xff0c;它是架设在局域网内的仓库服务&#xff0c;用来代理位于外部的远程仓库&#xff08;中央仓库、其他远程公共仓库&#xff09;。 建立了 Maven 私服后&#xff0c;当局域网内的用户需要某个构件时&#xff0c;会按照如下顺序进行请求…

低代码崛起:会让程序员饭碗不保,人工智能或成其催化剂

人工智能技术目前发展的趋势如何 关于人工智能技术的评价&#xff0c;大众的评价几乎算是较为一致的&#xff0c;都认为其已成为人类有史以来最具革命性的技术之一。当然了&#xff0c;可能目前的我们还是很难想象机器自主决策所产生的影响&#xff0c;但可以肯定的是&#xff…

ELF文件结构和实战分析

文章目录 示例编译运行 ELF文件格式ELF HeaderELF Section Header Table (节头表)sh_typesh_flagssh_link、sh_info 节链接信息 ELF Sections节的分类.text节.rodata节.plt节&#xff08;过程链接表&#xff09;.data节.bss节.got.plt节&#xff08;全局偏移表-过程链接表&…

ArkTS语言HarmonyOS/OpenHarmony应用开发-message事件刷新卡片内容

开发过程 在卡片页面中可以通过postCardAction接口触发message事件拉起FormExtensionAbility&#xff0c;然后由FormExtensionAbility刷新卡片内容。 common&#xff1a;公共文件 通过点击button按钮&#xff0c;刷新卡片内容。代码示例&#xff1a; WidgetCard.ets let stor…

内网渗透—Linux上线

内网渗透—Linux上线 1. 前言2. 下载插件3. CS配置3.1. 客户端配置3.1.1. 导入插件文件3.1.2. 配置监听 3.2. 服务端配置3.2.1. 导入配置文件 3.3. 生成木马3.3.1. 修改cna文件3.3.2. 修改后效果 3.4. 执行木马 1. 前言 默认情况下CS是不支持上线Linux的&#xff0c;只支持上线…

learn C++ NO.6——类和对象(4)

1.再谈构造函数 1.1.构造函数体赋值 在创建类的对象时&#xff0c;编译器回去调用类的构造函数&#xff0c;来各个成员变量一个合适的值。 class Date { public:Date(int year,int month,int day){_year year;_month month;_day day;}private:int _year;int _month;int _…

软件测试必备7大技能

一、测试用例的编写 1.在测试中最重要的文档&#xff0c;他是测试工作的核心&#xff0c;是一组在测试时输入输出的标准&#xff0c;是软件需求的具体对照。编写测试用例&#xff0c;是测试人员的基本功&#xff0c;真正能写好的人并不多。 2.测试用例包含的内容&#xff1a;…

【小白向】树莓派连接手机热点后 设置静态IP

树莓派连接手机热点后 设置静态IP 1.连接至手机热点2.查看当前 IP 地址3.修改 dhcpcd.conf 文件4.重启网络服务5.检查网络设置 1.连接至手机热点 在树莓派上打开 Wi-Fi 设置&#xff0c;并选择你要连接的手机热点&#xff0c;输入密码连接热点&#xff0c;确保你已经成功连接至…

Telerik Report Server R2 2023

Telerik Report Server R2 2023 仪表报告项-使用仪表或类似表盘的显示提供数据的可视化表示。 报告项上的AccessibleRole属性-ARIA(可访问的富Internet应用程序)支持已显著改进。在Web上&#xff0c;当启用了辅助功能时&#xff0c;呈现的报表项包含预定义的辅助功能角色。这样…

哈希表--想彻底了解哈希表,看这一篇文章就可以了

为了一次存储便能得到所查记录&#xff0c;在记录的存储位置和它的关键字之间建立一个确定的对应关系H&#xff0c;已H&#xff08;key)作为关键字为key的记录在表中的位置&#xff0c;这个对应关系H为哈希&#xff08;Hash&#xff09;函数&#xff0c; 按这个思路建立的表为哈…

创建可引导的 macOS 安装器(可启动U盘)

Apple官网下载的macOS镜像&#xff0c;只是一个安装包&#xff0c;不带引导不能直接安装到空白mac机器的。 1、首先&#xff0c;你必须要有台能正常运行macOS的mac pc。 2、下载macOS Sierra 10.12 El Capitan 10.11 Yosemite 10.10 Mountain Lion 10.8 Lion 10.7 点按以…

Ada Tutorial(2)SPARK Examiner + SPARK Prover

文章目录 代码 Task1.adb代码 task3.adbtask4.adb 在Ada和SPARK中&#xff0c;SPARK_Mode是一个编译指示&#xff0c;它表示随后的代码将使用SPARK语言规则进行编译和分析。 在with SPARK_Mode > On的影响下&#xff0c;编译器会在编译过程中应用SPARK语言规则&#xff0c;它…