一文详解动态链表和静态链表的区别

1、引言

        本文主要是对动态链表和静态链表的区别进行原理上的讲解分析,先通过对顺序表和动态链表概念和特点的原理性介绍,进而引申出静态链表的作用,以及其概念。通过这些原理性的概述,最后总结归纳出动态链表和静态链表的区别。本文不对代码进行额外的讲解,只对原理进行分析以加深基础的认识,相关概念的代码应用读者可以另行在网上进行搜索详细学习。


2、顺序表和动态链表的特点

        首先需要明白的是,顺序表和链表都是线性表,即线性存储结构。使用线性表存储数据的方式可以这样理解,即“把所有数据用一根线串起来,再存储到物理空间中”。

        如下图左边将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构(简称顺序表);下图右边数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系,这种存储结构称为链式存储结构(简称链表)。可以看出每一个数据按照“一对一”的关系按照次序逐个排列。

图片

2.1 顺序表存储结构

        顺序表对数据的物理存储结构有要求,需预先申请一整块足够大的存储空间,然后将数据依次存储起来,数据之间紧密贴合,不留一丝空隙。如下图所示,顺序表数据的 '一对一' 逻辑关系就是数据按照次序连续存储到一整块物理空间上,一个数据存储在一个位置之后紧接着就是下一个数据存储在下一个连续的位置。其数据存储方式和数组非常相似。

图片

        使用顺序表存储数据之前,除了要申请足够大小的物理空间之外,为了方便后期使用表中的数据,顺序表还需要实时记录以下 2 项数据:(1)顺序表申请的存储容量,即总的空间大小,类似于数组的总大小;(2)顺序表的长度,也就是当前表中存储数据元素的个数。正常状态下,顺序表申请的存储容量要大于顺序表的长度。顺序表的结构表示如下:

typedef struct Table
{int * head;//声明了一个名为head的长度不确定的数组,也叫“动态数组”int length;//记录当前顺序表的长度int size;//记录顺序表分配的存储容量
}table;

2.2 链表存储结构

        链表的存储方式与顺序表截然相反,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置不是连续的,而是随机的。什么时候存储数据,才在什么时候申请存储空间。链表数据的 '一对一' 逻辑关系就是数之间通过指针来维持,一个数据存储在一个位置之后通过指针指向下一个数据存储在下一个位置。这样的链表,也称之为"动态链表"。

图片

        链表中每个数据的存储都由两部分组成:(1)数据元素本身,其所在的区域称为数据域;(2)指向直接后继元素的指针,所在的区域称为指针域。这两个部分组成了链表中的一个数据节点,链表实际存储的是一个一个的节点,链表数据节点的结构表示如下:

typedef struct Link
{char elem; //代表数据域struct Link * next; //代表指针域,指向直接后继元素
}link; //link为节点名,每个节点都是一个 link 结构体

2.3 顺序表和动态链表的比较

        根据以上介绍的顺序表和动态链表的存储结构特点,可以比较出两者在以下几个方面的区别:

(1)开辟空间的方式

        顺序表存储数据实行的是 "一次开辟,永久使用",即存储数据之前先开辟好足够的存储空间,空间一旦开辟后期无法改变大小(使用动态数组malloc的情况除外)。

而动态链表则不同,动态链表存储数据时一次只开辟存储一个节点的物理空间,如果后期需要还可以再申请。

        因此,若只从开辟空间方式的角度去考虑,当存储数据的个数无法提前确定,又或是物理空间使用紧张以致无法一次性申请到足够大小的空间时,使用动态链表更有助于问题的解决。

(2)空间利用率

        顺序表的空间利用率高,而动态链表的空间利用率低。

        顺序表用一段连续的存储单元依次存储线性表的数据元素,物理空间上是连续的;动态链表用一组任意的存储单元存放线性表的元素,逻辑上连续(通过指针维持),但物理空间上不一定连续。

        动态链表在物理空间上不一定连续是由于每次只申请一个节点的空间,且空间的位置是随机的。这种申请存储空间的方式会产生很多空间碎片,一定程度上造成了空间浪费。不仅如此,由于动态链表中每个数据元素都必须携带至少一个指针,因此,动态链表对所申请空间的利用率没有顺序表高。

(3)插入元素的容量

        顺序表中,空间不够时需要扩容,扩容会有一定的消耗,扩容后可能存在一定的空间浪费;动态链表没有容量的概念,按需申请空间。

(4)存储密度

        顺序表中,其存储密度均为1,因为数组空间只用来存数据元素。而在动态链表中,每个节点除了存储数据元素自身外,还会至少存储直接后继的存储位置信息。相对于顺序表,动态链表的存储密度要低得多。

(5)时间复杂度

针对随机访问性能来说:

        顺序表随机访问一个元素可以用下标的方式直接访问,无需遍历整个表,时间复杂度为O(1);动态链表随机访问一个元素,需要从头到尾遍历,直到寻找到该元素,时间复杂度为O(N)。

针对任意位置插入或者删除元素来说:

        顺序表可能需要按顺序搬移大量元素后进行元素的插入或删除,效率较低,时间复杂度为O(N);动态链表中数据元素之间的逻辑关系靠的是节点之间的指针,因此在动态链表中插入或删除元素只需修改指针的指向,不需搬移大量元素,时间复杂度为O(1)。

        因此涉及访问元素的操作,而元素的插入、删除和移动操作极少的场景时,适合用顺序表;涉及元素的插入、删除和移动,而访问元素的需求很少的场景时,适合用动态链表。


3、为什么会有静态链表

        单站在时间复杂度的角度上来看,是否能够有一种数据结构既能够像顺序表一样快速的访问数据元素,又可以像动态链表一样可以方便的插入、删除和移动数据元素?

        静态链表就是这样一种数据结构,其属于一种线性存储结构,分配一整片连续的内存空间,各个节点集中安置,逻辑结构上相邻的数据元素,存储在指定的一块内存空间中,数据元素只允许在这块内存空间中随机存放。数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整型变量(称为"游标",和指针功能类似)维持(和链表类似)。在将数据存放到数组中时,给各个数据元素配备一个整形变量,此变量用于指明各个元素的直接后继元素所在数组中的位置下标。

        如图中a[0]~a[6]为数组下标,分配的内存空间中绿色数字为数组的数据元素,红色数字就为数组的游标变量,表示当前节点的下一个节点的数组下标。因此下标为x的数组中存放当前的数组数据元素和后继元素所在数组中的位置下标。因此可以看出静态链表是用数组来实现链式存储结构,静态链表实际上就是一个结构体数组。

图片

        如上图:a[1]中存放的数据元素值为2,通过a[1]中存放的游标变量4可找到后继元素所在的数组a[4];a[4]中存放的数据元素值为5,通过a[4]中存放的游标变量3可找到后继元素所在的数组a[3];a[3]中存放的数据元素值为7,通过a[3]中存放的游标变量6可找到后继元素所在的数组a[6]。以此类推,直到某元素的游标变量为0即可停止(注意:a[0]为头结点,其不存储数据元素)。

        但是从上图中,可以看出数组中间有未使用过的空间即没有数据元素的数组成员,这样岂不是浪费了存储空间?为了使我们创建的空间能够得到充分的利用,我们还需要一条连接各个空闲位置的链表,方便我们的随取随用,这条链表也被称为备用链表。

        备用链表的作用是回收数组中未使用或之前使用过(目前未使用)的存储空间,留待后期使用。也就是说,静态链表使用数组申请的物理空间中,存有两个链表,实现不同的功能,一个用来连接数据,另一个用来连接数组中的空闲空间。

图片

        为了适应这个,会存在一个“潜规则”,默认备用链表的表头位于数组下标为0(a[0])的位置,而数据链表的表头位于数据下标为1(a[1])的位置。备用链表的数组成员中仅存放游标。如上图所示:备用链表的连接顺序依次是:a[0]、a[2]、a[5],数据链表的连接顺序依次是a[1]、a[3]、a[4]、a[6]。

        静态链表中设置备用链表的好处是:可以清楚地知道数组中是否有空闲位置,以便数据链表添加新数据时使用。在静态链表的插入和删除操作中,都会与备用链表有着联系,当进行插入时,则是用备用链表上面取得一个节点作为待插入的新节点,反之,当在删除时,则将从链表上删除下来的节点链接到备用链表上面。整个过程中,我们需要做的工作就是更新游标的值。

        可见静态链表由数据链表的数据链表的各个节点和的备用链表的各个节点组成,静态链表节点的结构表示如下:

typedef struct List
{int data;//数据域int cur;//游标
}list;

4、动态链表和静态链表的区别

        通过以上对动态链表和静态链表原理概念和各自特点的介绍,我们可以对两者的区别有一个更深的认知。

(1)链表中数据“一对一”的关系

        动态链表是靠指针来维持。

        静态链表是靠游标来维持。

(2)内存空间申请

        使用动态链表存储数据,不需要预先申请内存空间,而是在需要的时候才向内存申请。也就是说,动态链表存储数据元素的个数是不限的,想存多少就存多少。

        使用静态链表存储数据,需要预先申请足够大的一整块内存空间,也就是说,静态链表存储数据元素的个数从其创建的那一刻就已经确定,后期无法更改。

(3)物理地址

        动态链表malloc 或 free 函数动态申请或释放内存,在长度上没有限制。因为是动态申请内存的,所以每个节点的物理地址不连续

        静态链表类是似于数组方法实现的,在物理地址上是连续的,而且需要预先分配地址空间大小,所以静态链表的初始长度一般是固定的。

(4)操作方式

        使用动态链表只需操控一条存储数据的链表。当表中添加或删除数据元素时,只需要通过 malloc 或 free 函数来申请或释放空间。

        静态链表是在固定大小的存储空间内随机存储各个数据元素,使用静态链表存储数据,需要操控两条链表,一条是存储数据的数据链表,另一条是记录空闲空间位置的备用链表,便于随时分配给新添加元素使用。当表中添加或删除数据元素时,需要更新数据链表和备用链表的对应节点的值。

(5)元素的访问

        动态链表随机访问一个元素,需要从头到尾遍历,直到寻找到该元素,时间复杂度为O(N)。

        静态链表随机访问一个元素,可以类似像数组通过下标的方式,通过游标来访问,时间复杂度为O(1)。

(6)元素的插入和删除

        动态链表插入或删除元素时不用做元素的移动,修改指针域即可。

        静态链表插入或删除元素时不用做元素的移动,可以通过修改游标的值来达到。


↓↓↓更多技术内容和书籍资料获取,入群技术交流敬请关注“明解嵌入式”↓↓↓ 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java下正面解除警告Unchecked cast: ‘java.lang.Object‘ to ‘java.util.ArrayList‘

就是我在反序列化时&#xff0c;遇到这样一个警告&#xff1a; Unchecked cast: java.lang.Object to java.util.ArrayList<com.work1.Student>然后我去网上查&#xff0c;有些人说用SuppressWarnings(“unchecked”)去忽略警告&#xff0c;但是我觉得作为一名合格的程序…

小谈设计模式(20)—组合模式

小谈设计模式&#xff08;20&#xff09;—组合模式 专栏介绍专栏地址专栏介绍 组合模式对象类型叶节点组合节点 核心思想应用场景123 结构图结构图分析 Java语言实现首先&#xff0c;我们需要定义一个抽象的组件类 Component&#xff0c;它包含了组合节点和叶节点的公共操作&a…

二项分布以及实现

文章目录 前言所谓二项分布就是只会产生两种结果的概率 1.概念 前言 所谓二项分布就是只会产生两种结果的概率 1.概念 下面是一个二项分布的的theano实现 import numpy as np import theano import theano.tensor as T from theano.tensor.nnet import conv from theano.ten…

[图论]哈尔滨工业大学(哈工大 HIT)学习笔记16-22

视频来源&#xff1a;2.7.1 补图_哔哩哔哩_bilibili 目录 1. 补图 1.1. 补图 2. 双图 2.1. 双图定理 3. 图兰定理/托兰定理 4. 极图理论 5. 欧拉图 5.1. 欧拉迹 5.2. 欧拉闭迹 5.3. 欧拉图 5.4. 欧拉定理 5.5. 伪图 1. 补图 1.1. 补图 &#xff08;1&#xff09;…

Redis最常见应用场景

缓存&#xff08;Cache&#xff09; Redis的第一个应用场景是Redis作为缓存对象来加速Web应用的访问。 在该场景下&#xff0c;有一些存储于数据库中的数据会被频繁访问&#xff0c;如果频繁的访问数据库&#xff0c;数据库负载会升高&#xff0c;同时由于数据库IO比较慢&…

实时人脸五观检测:基于libfacedetection(CNN模型)

一、前言 随着人工智能技术的不断发展,人脸检测已成为计算机视觉领域的重要应用之一。人脸检测是一种将输入图像中的人脸位置和轮廓提取出来的技术,广泛应用于人脸识别、智能监控、人机交互等领域。利用libfacedetection开源的人脸检测库,实现人脸检测。 libfacedetection…

【网络安全---sql注入(2)】如何通过SQL注入getshell?如何通过SQL注入读取文件或者数据库数据?一篇文章告诉你过程和原理。

前言 本篇博客主要是通过piakchu靶场来讲解如何通过SQL注入漏洞来写入文件&#xff0c;读取文件。通过SQL输入来注入木马来getshell等&#xff0c;讲解了比较详细的过程&#xff1b; 如果想要学习SQL注入原理以及如何进行SQL注入&#xff0c;我也写了一篇详细的SQL注入方法及…

【再识C进阶4】详细介绍自定义类型——结构体、枚举和联合

学习目标&#xff1a; 在上一篇博客中&#xff0c;我们已经详细地学习了字符分类函数、字符转换函数和内存函数。那这一篇博客和上一篇博客的关系不是那么相连。 这一篇博客主要介绍一下自定义类型&#xff0c;因为在解决实际问题时&#xff0c;由于世界上的因素有很多&#xf…

Windowsold文件夹作用以及删除方法

引言 2021年6月24日&#xff0c;微软正式发布全新操作系统Windows 11。Windows 11系统于2021年10月5日开始全面推送。2021年10月以后生产的电脑已经预装Windows 11系统。刚开始会有一部分人不适应win 11系统&#xff0c;会选择退回win10。现在win11已经推出了稳定版&#xff0…

【C++】Vector -- 详解

一、vector的介绍及使用 1、vector的介绍 https://cplusplus.com/reference/vector/vector/ vector 是表示可变大小数组的序列容器。 就像数组一样&#xff0c;vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问&#xff0c;和数组一…

JS使用setInterval导致堆溢出

问题描述 使用setInterval运行较长一段时间后出现堆溢出的情况。 代码类似于 setInterval(sendHeartbeat, 30000);function sendHeartbeat() {axios.get(url).then(res > {console.log("success")}).catch(err > {console.error(err.message);}) }在一些老…

golang gin框架1——简单案例以及api版本控制

gin框架 gin是golang的一个后台WEB框架 简单案例 package mainimport ("github.com/gin-gonic/gin""net/http" )func main() {r : gin.Default()r.GET("/ping", func(c *gin.Context) {//以json形式输出&#xff0c;还可以xml protobufc.JSON…