线性表

144 阅读5分钟

前言

最近准备系统过一下数据结构,上学期学的时候只是粗略的应试,现在发现数据结构还是很重要的,自己用 C 手敲一遍顺便再复习下 C,顺便再写文章记录下心得体会啥的。

正文

之前一直误解线性表也是种数据结构,现在发觉线性表是逻辑上的结构,顺序表和链表是线性表具体的表示形式,是真正的存储结构

线性表

定义

  • 线性表是具有相同数据类型的 n (n>=0)个数据元素的有限序列,n=0 时为空表
  • 是一种逻辑结构

特点

  • 表中元素的个数有限
  • 表中元素具有逻辑上的顺序性,表中元素有其先后次序
  • 表中元素都是数据元素,每个元素都是单个元素
  • 表中元素的数据类型都相同,这意味着每个元素占有相同大小的存储空间
  • 表中元素具有抽象性,即仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容

顺序表

定义

  • 用一组地址连续的存储单元依次存储线性表中的数据元素
  • 是线性表的顺序存储

特点

  • 逻辑位置与物理位置都相邻

操作

定义相关结构

假设数据均为 int 类型,结构体中存储数据、数据长度、数据最大长度

image.png

初始化

接收 SeqList * 类型的指针,因此在主函数传递时应传递定义的结构体的地址

image.png

插入

  • 顺序表,其实是数组来存储,因此插入也就很简单,在指定位置插入一个数字,其他数字顺延就好了
  • 在这之中要考虑几个问题:
    • 判断插入位置合法性
    • 判断数组空间是否够用

image.png

删除

  • 删除同理插入,不过是前移罢了
  • 只需考虑位置合法性即可

image.png


来点小小的疑问

  • 不知大家有没有对插入和删除判断 index 合法性这块有不懂,我第一次看的时候是没搞明白为啥插入是判断 index > List->length + 1,删除index > List->length
  • 请看 VCR
    • 插入 image.png
    • 删除 image.png

按值查找

遍历就完事了 xdm

image.png

测试打印函数

image.png

整体效果测试

wsl 终端输出中文乱码,试了好几个问答都没用...被迫改为英文...

image.png

image.png

时间复杂度

  • 插入/删除主要花费时间在移动元素,平均复杂度为 O(n)
  • 查找主要在遍历
    • 按值: O(n)
    • 按序号:O(1)

链表

单链表

定义

  • 用一组任意的存储单元来存储线性表中的数据元素
  • 是线性表的链式存储

特点

  • 相比顺序表加了指针域浪费空间
  • 非随机存取

操作

以下操作均建立在有头指针的情况下

定义相关结构
  • 事先定义结构体名字以便定义 next 类型
  • 事先起好 LNode * 类型的别名 LinkList 方便定义

image.png

初始化

image.png

第一次写这块时把 LinkList * 写成了 LinkList,导致出错,思考了好一会才明白

  • 传的 LinkList 等于是在 main 函数中定义的 List 指针所指的那片区域,我们要在这片区域里给值开辟空间,当然是要获取它所指的区域的值了,因此应该为 LinkList * 类型

哎,高三暑假那会学 c 的时候给指针粗略看看就过了,大学来也没看过,原来在这等着我呢🥲

插入

image.png

什么叫后插?请看 VCR:

无标题-2023-03-19-1936.png

大多数情况下说的都是后插

删除

image.png

写这块时候也出了点问题,这是错误代码:
image.png

  • 当时懒的定义一个新的 LinkList 类型的值,就直接这样操作
  • 错误:
    • 在已经释放了 p -> next,这部分内存可能已经被其他程序所用,但又将 data 作为了返回值
    • 释放之前应该判断 p->next 不为空,否则 free(空指针)
    • 释放后将该指针置空,以免出现悬空指针
  • 啊多么痛的领悟~~🥲🥲
表长

遍历,没啥好说的

image.png

按序查找

image.png

没错,写这的时候也出了点小小的问题

  • 当时判断条件写的 p -> next != NULL,想着不是找第 index 个嘛,结束条件自然是 p -> next 为 NULL,然后发现书上代码是 p != NULL,正常长度内的测试也都没问题,但在 index 超出目前已有元素的长度时两者就不一样了,书上写的就停着不走了,我的话返回最后一个结点

我的麻麻耶,让我来分析一手:

  • 当 index = length + 1 时,循环运行 index - 1 次,p 指向最后一个结点,最后一个结点的指针域指向 NULL,此时:
    • p -> next != NULL 为 false , p != NULL 为 true
    • 这种情况因为后面 i < index 的限制循环不会再执行,两者都正常输出,循环结束
  • 但是如果 index > length + 1 呢?eg: index = length + 3
    • 当 index = length + 1 时,与上述相同,p -> next 导致无论 index 多大都只返回最后一个结点
    • 下次循环,没有了 i < index 的限制, p != NULL 导致循环会继续执行,此时 p = NULL
    • 再下次循环,p != NULL 为 false,循环结束

So?总结一下,两个都可以,正常来讲两种条件都可以,不过都应该加个 if 来处理 index 超出的情况就是了

按值查找

我是不太懂为什么要返回整个结点,不应该返回 index 么...

image.png

测试打印函数

image.png

整体效果测试

image.png

image.png

时间复杂度

  • 插入/按值查找/按序查找/删除都花时间在迭代上,时间复杂度均为 O(n)

对于单链表来说,在我们已知一个结点 p 的情况下,我们想要访问它的后继结点很容易,但如果要访问它的前驱结点又得从头遍历,于是为了弥补这个缺点。又得拿空间换时间


双链表

定义

  • 相比单链表增加一个头指针指向直接前驱

差别

双链表因为多了一个头指针,因此在插入删除时操作有些许不同,这部分较简单,就不多解释了

插入

image.png

删除

image.png

测试输出

image.png

image.png

结语

这文章写的跟流水账一样,看着都难受
所有代码在 仓库