• 线性表(顺序表,单链表,双链表,循环链表,静态链表)


    1.线性表的定义

    线性表是具有相同数据类型的n (n>=0)个数据元素的有限序列,其中n为表长,当n = 0时线性表是一个空表。
    若用L命名线性表,则其一般表示为 L = ( a 1 , a 2 , . . . , a i , a i + 1 , . . . , a n ) L= (a1, a2,... , a_i, a_{i+1}, ... , an) L=(a1,a2,...,ai,ai+1,...,an)

    1.几个重要的概念

    a i a_i ai是线性表中的“第i个”元素线性表中的位序
    注意:位序从1开始,数组下标从0开始。
    a 1 a_1 a1表头元素;
    a n a_n an表尾元素
    除第一个元素外,每个元素有且仅有一个直接前驱;
    除最后一个元素外,每个元素有且仅有一个直接后继.

    2.逻辑结构

    在这里插入图片描述

    2.线性表的基本操作

    “&”符号的作用:对参数的修改结果带回主函数

    ①InitList(&L):初始化表。构造一个空的线性表L,分配内存空间
    ②DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间。
    ③ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e.
    ④ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
    ⑤LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
    ⑥GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
    ⑦Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
    ⑧PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
    ⑨Empty(L):判空操作。若L为空表,则返回true,否则返回false。

    3.顺序表(线性表的顺序存储)

    顺序表:用顺序存储的方式实现线性表。
    顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
    顺序表的实现方式:静态分配和动态分配。

    1.静态分配

    存储空间是静态的。
    使用“静态数组”实现,大小一旦确定就无法改变。

    在这里插入图片描述

    2.动态分配

    C语言中提供malloc和free函数来动态申请和释放内存空间。
    使用动态数组实现。
    在这里插入图片描述

    3.顺序表的特点

    随机访问,即可以在(O1)时间内找到第i个元素。
    ②存储密度高,每个节点只存储数据元素。
    ③拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
    ④插入、删除操作不方便,需要移动大量元素。

    4.顺序表的基本操作

    1.插入

    在这里插入图片描述

    ①最好情况:新元素插入到表尾,不需要移动元素。
    i = n + 1 i = n+1 i=n+1,循环0次;最好时间复杂度=O(1)
    ②最坏情况:新元素插入到表头,需要将原有的n个元素全都向后移动。
    i = 1 i = 1 i=1,循环n次;最坏时间复杂度= O(n);
    ③平均情况:假设新元素插入到任何一个位置的概率相同,
    i = 1 , 2 , 3 , . . , l e n g t h + 1 的概率都是 p = 1 n + 1 i=1,2,3,.. , length+1的概率都是p=\frac{1}{n+1} i=1,2,3,..,length+1的概率都是p=n+11
    平均循环次数= n ( n + 1 ) 2 1 n + 1 = n 2 \frac{n(n+1)}{2}\frac{1}{n+1}=\frac{n}{2} 2n(n+1)n+11=2n平均时间复杂度= O(n)

    2.删除

    在这里插入图片描述

    ①最好情况:删除表尾元素,不需要移动其他元素。
    i = n,循环0次;最好时间复杂度=O(1)
    ②最坏情况:删除表头元素,需要将后续的n-1个元素全都向前移动。
    i = 1,循环n-1 次;最坏时间复杂度= O(n);
    ③平均情况:假设删除任何一个元素的概率相同,即 i = 1 , 2 , 3 , . . . , l e n g t h i= 1,2,3,... , length i=1,2,3,...,length的概率都是 p = 1 n p =\frac{1}{n} p=n1
    平均循环次数 = n ( n − 1 ) 2 1 n = n − 1 2 \frac{n(n-1)}{2}\frac{1}{n}=\frac{n-1}{2} 2n(n1)n1=2n1平均时间复杂度= O(n)

    3.查找

    1.按位查找

    GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
    由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第i个元素:“随机存取”特性。
    时间复杂度为O(1)

    2.按值查找

    LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
    最好情况:目标元素在表头,循环1次;最好时间复杂度=O(1)
    最坏情况:目标元素在表尾,循环n 次;最坏时间复杂度= O(n);
    平均情况:假设目标元素出现在任何一个位置的概率相同,都是 1 n \frac{1}{n} n1,平均循环次数= n ( n + 1 ) 2 1 n = n + 1 2 \frac{n(n+1)}{2}\frac{1}{n}=\frac{n+1}{2} 2n(n+1)n1=2n+1
    平均时间复杂度= O(n)

    4.链表(线性表的链式存储)

    1.单链表

    单链表由一个一个结点组成,每个结点除了存放数据元素外,还要存储指向下一个节点的指针。
    优点:不要求大片连续空间,改变容量方便。
    缺点:不可随机存取,要耗费一定空间存放指针。

    1.代码实现

    强调这是一个单链表:使用 L i n k L i s t LinkList LinkList
    强调这是一个结点:使用 L N o d e ∗ LNode * LNode
    在这里插入图片描述

    2.带头结点的实现

    头指针本身不存储数据,数据域为空,只有他指向的下一个结点开始存储数据。
    在这里插入图片描述

    3.不带头结点的实现

    不带头结点,写代码更麻烦。
    对第一个数据结点和后续数据结点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑。

    4.按位序插入

    Listlnsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。
    ①带头结点
    在这里插入图片描述
    平均时间复杂度:O(n)
    ②不带头结点
    在这里插入图片描述

    5.指定结点的后插操作

    在这里插入图片描述

    6.指定结点的前插操作

    ①使用带头指针的链表,循环查找元素的前驱,再对元素前驱进行后插操作。
    时间复杂度为O(n)
    ②将新增结点复制为需要进行前插操作的结点,再将待插入的数据的数据域复制给当前结点,连接两个结点即可。
    在这里插入图片描述
    时间复杂度为O(1)

    7.按位序删除

    ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
    找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点。

    ①带头结点
    在这里插入图片描述
    最环、平均时间复杂度:O(n)
    最好时间复杂度:O(1)

    8.指定结点的删除

    删除结点p,需要修改其前驱结点的next指针,
    方法1:传入头指针,循环寻找p的前驱结点
    方法2:偷天换日(类似于结点前插的实现)
    在这里插入图片描述
    时间复杂度为O(1),但如果p为最后一个结点时会存在空指针问题

    9.按位查找

    平均时间复杂度为O(n).

    在这里插入图片描述

    10.按值查找

    平均时间复杂度为:O(n)
    在这里插入图片描述

    11.求表的长度

    平均时间复杂度为:O(n)
    在这里插入图片描述

    2.单链表的建立

    1.尾插法

    时间复杂度:O(n)
    要设置一个指向表尾结点的指针。

    在这里插入图片描述

    2.头插法

    应用:链表的逆置
    在这里插入图片描述

    3.双链表

    在这里插入图片描述

    1.初始化

    增加了一个指向前驱的指针域。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    2.插入

    在这里插入图片描述

    3.删除

    在这里插入图片描述

    4.遍历

    在这里插入图片描述

    双链表不可随机存取,按位查找,按值查找操作都只能用遍历的方式实现。
    时间复杂度O(n)

    4.循环链表

    1.循环单链表

    表尾结点的next指针指向头结点。
    从一个结点出发可以找到其他任何一个结点。

    在这里插入图片描述

    1.初始化

    在这里插入图片描述

    2.基本操作

    ①从头结点找到尾部,时间复杂度为O(n)
    ②从尾部找到头部,时间复杂度为O(1)
    ③很多时候对链表的操作都是在头部或尾部,可以让L指向表尾元素(插入、删除时可能需要修改L)。

    2.循环双链表

    表头结点的prior指向表尾结点。
    表尾结点的next指向头结点。
    在这里插入图片描述

    1.初始化

    在这里插入图片描述

    2.基本操作

    ①插入
    在这里插入图片描述
    ②删除
    在这里插入图片描述

    5.静态链表

    1.定义

    静态链表:分配一整片连续的内存空间,各个结点集中安置。
    静态链表:用数组的方式实现的链表。

    优点:增、删操作不需要大量移动元素
    缺点:不能随机存取,只能从头结点开始依次往后查找。
    容量固定不可变
    适用场景:
    ①不支持指针的低级语言;
    ②数据元素数量固定不变的场景(如操作系统的文件分配表FAT)

    在这里插入图片描述

    2.代码实现

    在这里插入图片描述

    3.基本操作

    ①查找
    从头结点出发挨个往后遍历结点。时间复杂度为O(n)

    ②插入
    找到一个空的结点,存入数据元素
    从头结点出发找到位序为i-1的结点
    修改新结点的next
    修改i-1号结点的next

    ③删除
    从头结点出发找到前驱结点
    修改前驱结点的游标
    被删除结点next设为-2

    5.顺序表和链表的对比

    1.逻辑结构

    都属于线性表,都是线性结构

    2.存储结构

    ①顺序表(顺序存储)
    优点:支持随机存取、存储密度高。
    缺点:大片连续空间分配不方便,改变容量不方便。

    ②链表(链式存储)
    优点:离散的小空间分配方便,改变容量方便。
    缺点:不可随机存取,存储密度低。

    3.基本操作

    1.创建

    ①顺序表
    需要预分配大片连续空间。
    若分配空间过小,则之后不方便拓展容量;
    若分配空间过大,则浪费内存资源。
    ②链表
    只需分配一个头结点(也可以不要头结点,只声明一个头指针),之后方便拓展。

    2.销毁

    ①顺序表
    修改Length=0
    系统自动回收空间

    ②链表
    依次删除各个结点( free )
    需要手动free

    3.增删

    ①顺序表
    插入/删除元素要将后续元素都后移/前移
    时间复杂度O(n)
    时间开销主要来自移动元素
    若数据元素很大,则移动的时间代价很高

    ②链表
    插入/删除元素只需修改指针即可
    时间复杂度o(n)
    时间开销主要来自查找目标元素
    查找元素的时间代价更低

    4.查找

    ①顺序表
    按位查找:O(1)
    按值查找:O(n)若表内元素有序,可在 O ( l o g 2 n ) O(log_2n) O(log2n)时间内找到

    ②链表
    按位查找:O(n)
    按值查找:O(n)

    4.选择

    表长难以预估、经常要增加/删除元素—―链表。
    表长可预估、查询(搜索)操作较多――顺序表。

  • 相关阅读:
    OpenWrt kernel install分析(2)
    OSI七层参考模型和TCP/IP四层(五层)参考模型
    Hall定理(霍尔定理)证明及推广
    【基础理论】柯西分布
    28.java中的集合框架的相关面试题[20220730]
    如何使用ArcGIS Pro为栅格图添加坐标信息
    【mysql体系结构】--mysql的配置文件
    深度学习框架量化感知训练的思考及OneFlow的解决方案
    【笔者感悟】笔者的学习感悟【七】
    Nginx-负载均衡与动静分离
  • 原文地址:https://blog.csdn.net/qq_61888137/article/details/134147995