前言
最近准备系统过一下数据结构,上学期学的时候只是粗略的应试,现在发现数据结构还是很重要的,自己用 C 手敲一遍顺便再复习下 C,顺便再写文章记录下心得体会啥的。
正文
之前一直误解线性表也是种数据结构,现在发觉线性表是
逻辑上的结构,顺序表和链表是线性表具体的表示形式,是真正的存储结构。
线性表
定义
- 线性表是具有
相同数据类型的 n (n>=0)个数据元素的有限序列,n=0 时为空表 - 是一种逻辑结构
特点
- 表中元素的个数有限
- 表中元素具有逻辑上的顺序性,表中元素有其先后次序
- 表中元素都是数据元素,每个元素都是单个元素
- 表中元素的数据类型都相同,这意味着每个元素占有相同大小的存储空间
- 表中元素具有抽象性,即仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容
顺序表
定义
- 用一组地址连续的存储单元依次存储线性表中的数据元素
- 是线性表的顺序存储
特点
- 逻辑位置与物理位置都相邻
操作
定义相关结构
假设数据均为 int 类型,结构体中存储数据、数据长度、数据最大长度
初始化
接收 SeqList * 类型的指针,因此在主函数传递时应传递定义的结构体的地址
插入
- 顺序表,其实是数组来存储,因此插入也就很简单,在指定位置插入一个数字,其他数字顺延就好了
- 在这之中要考虑几个问题:
- 判断插入位置合法性
- 判断数组空间是否够用
删除
- 删除同理插入,不过是前移罢了
- 只需考虑位置合法性即可
来点小小的疑问
- 不知大家有没有对插入和删除判断 index 合法性这块有不懂,我第一次看的时候是没搞明白为啥
插入是判断index > List->length + 1,删除是index > List->length- 请看 VCR
- 插入
- 删除
按值查找
遍历就完事了 xdm
测试打印函数
整体效果测试
wsl 终端输出中文乱码,试了好几个问答都没用...被迫改为英文...
时间复杂度
- 插入/删除主要花费时间在移动元素,平均复杂度为 O(n)
- 查找主要在遍历
- 按值: O(n)
- 按序号:O(1)
链表
单链表
定义
- 用一组任意的存储单元来存储线性表中的数据元素
- 是线性表的链式存储
特点
- 相比顺序表加了指针域浪费空间
- 非随机存取
操作
以下操作均建立在有头指针的情况下
定义相关结构
- 事先定义结构体名字以便定义 next 类型
- 事先起好 LNode * 类型的别名 LinkList 方便定义
初始化
第一次写这块时把 LinkList * 写成了 LinkList,导致出错,思考了好一会才明白
- 传的
LinkList等于是在 main 函数中定义的 List 指针所指的那片区域,我们要在这片区域里给值开辟空间,当然是要获取它所指的区域的值了,因此应该为LinkList *类型哎,高三暑假那会学 c 的时候给指针粗略看看就过了,大学来也没看过,原来在这等着我呢🥲
插入
什么叫后插?请看 VCR:
大多数情况下说的都是后插
删除
写这块时候也出了点问题,这是错误代码:
- 当时懒的定义一个新的 LinkList 类型的值,就直接这样操作
- 错误:
- 在已经释放了 p -> next,这部分内存可能已经被其他程序所用,但又将 data 作为了返回值
- 释放之前应该判断 p->next 不为空,否则 free(空指针)
- 释放后将该指针置空,以免出现悬空指针
- 啊多么痛的领悟~~🥲🥲
表长
遍历,没啥好说的
按序查找
没错,写这的时候也出了点小小的问题
- 当时判断条件写的 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 么...
测试打印函数
整体效果测试
时间复杂度
- 插入/按值查找/按序查找/删除都花时间在迭代上,时间复杂度均为 O(n)
对于单链表来说,在我们已知一个结点 p 的情况下,我们想要访问它的后继结点很容易,但如果要访问它的前驱结点又得从头遍历,于是为了弥补这个缺点。又得拿空间换时间
双链表
定义
- 相比单链表增加一个头指针指向直接前驱
差别
双链表因为多了一个头指针,因此在插入删除时操作有些许不同,这部分较简单,就不多解释了
插入
删除
测试输出
结语
这文章写的跟流水账一样,看着都难受
所有代码在 仓库