文章目录
前言
参考书籍:《数据结构》 胡学钢 张先宜 安徽大学出版社
一些基本概念
数据
信息的载体,可以传输到计算机中,被计算机识别、存储、处理的符号集合。
数据元素
数据的基本单元,数据里具有独立意义的个体。
如:成绩表中的学生成绩、家族关系中的个人
数据项
数据元素的最小分割单位,一个数据元素由若干数据项组成。
数据项 → 数据元素 → 数据
字段(域)
对数据元素的具体描述,一个数据元素里可以有多个字段
如:学生成绩单上的数学成绩、英语成绩、语文成绩
数据结构
多个数据元素在一起形成数据,数据结构就是这些元素之间的关系
数据结构三要素
逻辑结构、存储结构、运算
- 逻辑结构<数据之间的关系>
- 线性结构
- 非线性结构→树、图
- 集合<数据之间无关联>
- 存储结构(物理结构)
- 顺序存储
- 链式存储
- 索引存储
- 散列存储
- 运算
插删改查排
(插入、删除、修改、查找、排序)
算法
用来操作数据、解决程序问题的一种办法
选择不同的算法,处理问题的效率会有所不同
算法优劣评估
- 时间复杂度
- 用处:评估算法的执行效率
- 用法:用算法中基本语句的执行次数衡量
- 改进:为了方便计算,改用基本语句执行次数的数量级来衡量<用极限的比值形式评估>。
逻辑结构:线性结构
线性表
定义:线性表是最简单的一种线性结构,可以在任意位置对元素进行插入和删除,线性表由n个同类型元素组成。
关键词:线性结构,任意位置插删,同类型元素
顺序存储:使用一片地址连续的优先内存单元空间存储数据元素。
链式存储:将数据(data)和指针(next)包装成一个结点(node),通过指针将结点连接起来实现数据的跳转。
线性表的逻辑特性:线性表中的数据元素是1v1的关系,一个元素只能有一个直接前驱和一个直接后继(除了第一个和最后一个数据元素)
关键词:1v1 首尾相连 1前驱 1后继
顺序存储→顺序表
特点:占用一片连续的存储空间,逻辑地址连续的元素之间物理地址也连续
注意:顺序表的插删改
插入 | 删除 | 查找 |
---|---|---|
将i–n往后移------>将x插入第i个位置 | ①将i+1–n个元素往前移②顺序表长度-1 | |
移动元素较多 | 移动元素较多 | |
O(n) | O(n) | O(1) |
链式存储→链表
定义:通过指针连接链表中的各项元素,逻辑地址连续的元素之间物理地址不一定连续。每个数据元素中存储:1、本身信息;2、与其他元素之间的关系。
插入 | 删除 | 查找 |
---|---|---|
O(1) | O(1) | O(n) |
链表分类:单链表、循环链表、双(向)链表、静态链表 |
单链表
单链表—首尾相接—>循环单链表
单链表—增加指示前驱元素的指针—>双链表—首尾相接—>循环双链表
*头结点不计入链表长度
双链表
插入结点
①搜索插入位置的指针p,结点i
②创建新结点,装入数据x、指针
③将新结点的前驱指向i-1结点的后继,将新节点的后继指向i+1结点的前驱
④将i-1结点的后继指向新结点的前驱,将i+1结点的前驱指向新结点的后继
删除结点
①找到想要删除的i结点的指针p
②让前驱结点i-1的后继指针指向后继节点i+1
③让后继结点i+1的指针指向前驱结点i-1
④释放结点p free(p)
静态链表
静态链表的本质是数组。
在数组中增加一个(或两个)指针域,在这些指针域上存放下一个(或上一个)数据元素在数组中的下标,从而构造出单链表(或双链表)。
静态链表中的指针也被称为仿真指针。
顺序存储 vs 链式存储
顺序存储 | 链式存储 |
---|---|
逻辑相邻物理相邻 | 逻辑相邻物理不一定相邻 |
存储密度大,空间利用率高 | 存储密度小,空间利用率低 |
查找(静态操作)快,插入删除(动态操作)慢 | 查找(静态操作)慢,插入删除(动态操作)快 |
堆栈
概念
一种特殊的线性表,只允许数据元素在表的一端进行插入和删除操作,数据先进后出(FILO)或后进先出(LIFO)。
逻辑结构:1v1
存储结构:顺序存储------>顺序栈,链式存储------>链栈
栈顶:进行插入和删除的一端
栈底:另一端
空栈:没有元素的栈
入栈:数据插入
出栈:数据删除
上溢:栈满后还入栈
下溢:栈空后还出栈
顺序存储→顺序栈
以顺序结构存储数据的栈。栈顶指针top指向数组的末端
链式存储→链栈
用链式存储结构存储数据的栈。
队列
顺序存储→顺序队列
定义:用数组实现,分别设两个整型变量“front”和“rear”来表示“队首指针”和“队尾指针”
Q:为什么要设置队头指针?
A:便于出队
缺点:假溢出现象
假溢出现象是发生在顺序队列中的一种现象。在我们使用顺序队列进行入队出队操作时,经历了这样的一个过程:入队时,我们先将队尾指针后移一个,随后插入数据;出队时,我们直接将队首指针后移一格。如果此时入队和出队的操作同时在数组里发生,但是队尾指针已经退到了数组的末端,此时如果再有元素想要入队,顺序队列就会发生“溢出”,但这个溢出并不是真正的溢出,而是一种假溢出。这是由于前面可能已经在这个顺序队列中执行过出队操作,数组的前端就会出现很多闲置的元素空间。
解决方法——构造循环队列(队列元素平移法,设标记法略)
循环队列
将rear和front相连,形成逻辑上的循环。
判断队空队满
让front指向一个空元素,使长度为n的数组中只能储存n-1个元素。
这样子要队列中有元素,rear指针就永远不会和front指针重合。
如果队空,则 rear==front
如果队满,则rear=front-1
链式存储→链队列
堆栈vs队列
堆栈 | 队列 |
一种特殊的顺序表 | |
先进后出 |_| | 先进先出 | | |
只能在一端(栈顶)进行插入、删除 | 一端(队头)删除,一端(队尾)插入 |
栈和队列的应用
栈应用——递归、括号匹配
(待补全)栈与递归
递归:1、递归是一种重要的程序设计方法,如果一个程序的数据结构在定义的过程中调用了自己,就可以说这个数据结构是递归的。简单来讲就是函数在运行过程中调用自己。
2、递归可以将一个大型的问题精简为一个规模较小的问题,大大缩短了程序的代码量。
3、但是通常情况下效率不会太高。
4、通过栈将递归算法转化成非递归算法。递归—栈—>非递归
- 常见递归结构:斐波那契数列,链表的定义
- 在执行递归程序的时候,函数调用栈被称为”递归调用栈“,每进入一层递归就相应压入栈定,每结束一层递归就将的相应信息弹出栈。
缺点:如果递归的层数很多容易导致栈溢出,而且空间复杂度会变高 - 递归—栈—>非递归
自己定义手工栈
栈与括号匹配
栈可以用来检测左右括号是否匹配
设计思路:
- 将左括号入栈,遇到一个右括号就弹出一个左括号与之匹配
- 如果遇到不匹配的情况,停止扫描,匹配失败
- 如果左括号已经全部被弹出,而此时仍有右括号请求匹配,停止扫描,匹配失败
- 如果右括号已经扫描完毕,但栈中仍然有左括号,停止扫描,匹配失败
https://www.bilibili.com/video/BV1b7411N798?p=26
队列应用——打印、多人请求CPU
队列和打印
问题:CPU的处理速度远大于类似于打印机这样的硬件设施的执行速度,CPU如果一次性把数据全发给打印机,打印机就会来不及处理
解决:规定CPU先将数据发送到数据缓冲区里面,这个缓冲区就是一个队列,让数据在队列里按照顺序排队,如果缓冲区写满,就先暂停写入,让CPU转而去做其他的事情。打印机按照先进先出的规则打印数据,打印完后向CPU发送写入请求,CPU再继续向缓冲区写入数据。
成果:利用队列进行打印,既能提高打印的准确性,又能提高CPU的利用率。
队列和多人请求CPU
问题:多用户请求一个CPU资源的时候会发生争抢现象。
解决:为了解决这个问题,采用队列的方式对这些用户申请按照申请时间先后进行排队,操作系统会按照先进先出的顺序,先把CPU分配给队首的程序,执行一段时间间隔之后将该程序出队,放到队尾入队;再将CPU分配给下一个程序,同样是执行一段时间之后出队后从队尾入队,依次类推,实现程序的循环执行。
成果:用这种方法分配CPU资源,既可以同时满足所有用户的请求,同时执行多个程序,又可以充分利用CPU的资源。