前言
-
非常重要,上午综合部分有,下午设计部分也有,而且是下午题中最难部分;
-
本章讲数据结构和算法的基础,应对下午题的具体方法之后有章节再讲
-
但是这个是基础,一定要好好学
目录
-
数组与矩阵
-
线性表
-
广义表
-
树与二叉树
-
图
-
排序与查找
-
算法基础及常见的算法
简述
-
数组和矩阵:数组下标转化,稀疏矩阵,上下三角
-
线性表:栈和队列基本特性和相关应用,重要
-
广义表:了解基本概念和操作
-
树和二叉树:必考,基本特性,用来解决什么问题;以及有特色特点的二叉树基本情况
-
图:了解基本概念即可
-
排序和查找:算法比较多一个一个展开;选择题特性和流程,空间时间复杂度; 下午题相关程序
数组
-
了解
-
存储地址的计算
-
二维数组中按行存储和按列存储的区别;
-
-
和其他章节知识点有相关性:
-
页面置换算法进行页面置换时缺页问题
-
有时要结合按行还是按列讨论;
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQGHK81q-1676206809290)(https://binying-img.oss-cn-hangzhou.aliyuncs.com/blogImg/Image%20%5B1%5D.png)]
一维数组
-
一维数组,假设每位一个字节,则a[1]=a+1
- 其中a表示的是一个地址
二维数组
行存储和列存储
- 按行存储与按列存储,都是再系统内线性存储的
- 例题 :a+(2*5+3)*2
稀疏矩阵
- 一个矩阵中,大部分数据都是0就叫稀疏矩阵
相关应用
-
图可以用矩阵存储;
- 如存储无向图实际上就是使用三角矩阵;
如何解题
-
1.记公式
-
2.带入法
例题
答案:A
数据结构的定义
定义
- 数据结构:计算机存储和组织数据的方式
逻辑结构
-
线性:一个结构中,若所有节点都只有前后节点而没有其他分支节点就是线性结构
- 无论是使用链式存储结构还是顺序存储结构都是线性结构
-
树和图:线性结构的基础上增加了分支,和贿赂
- 树没有环路,图可能有
-
广义来讲:图包含树,树包含线性结构
线性表
-
常见的有顺序层次结构与链式存储结构
- 顺序表(数组),链表
三种链表的区别
-
单方向访问
- 单链表:定位某元素时要从头开始
-
单方向可循环访问
- 循环链表:可以从任何一个点进入,最后一个节点指向头节点
-
双向访问(不考)
- 双向链表(头尾不相连):从头元素进去后可在任何一个节点找到上一个或下一个节点
单链表的相关操作
-
单链表:
-
分为有头节点的和没头节点的;
-
引入空值头节点好处,使所有元素处理方式一样;
-
如果没有头节点则需要对首个元素特殊处理
-
-
-
删除:
- p->next=q->next
-
插入:
-
s->next = p -> next
-
p->next = s
-
链式存储和顺序存储的对比
- 许多都是算法时间复杂度是O((n+1)/2)或者O((n+2))/2),但是在n极大是不用考虑常数级,所以一般缩写掉常数;
-
插入运算处,顺序存储最好情况为0,表示的是
-
数组为空时插入或插入到数组最后一个位置时,需要挪动的数为0个;
-
反之最坏为n指的是n,数组又n个数时,将新数插入第一个位置,需要挪动n个数;
-
写入速度极快,不计入时间考虑;
-
-
空间性能
- 链式结构中,一个节点除了存储数据还要存储节点,所以空间性能<1
-
查找运算
- 数据无序则复杂度一样,否则顺序更快
队列与栈
-
队列:先进先出的方式
-
栈:先进后出
-
相当于放东西进箱子
-
最先放进箱子的东西压箱底了,要把上面都拿出来才能出来
-
循环队列
-
使用head和tail指针表示队列中的非空位置
-
起始时,head和tail指针都指向0号位
-
若进来一个则tail指针往后移一位
- tail指针永远指向空位置
-
若出去一个则head往后移一位
- head指针
-
-
指针在队列中会不断循环移动:
- 即,当head或tail指针指向最后一个位置时,若再后移一位则回到起始位;
-
问题:队列刚好存满的时候,尾指针又指向头指针;
两种解决办法:
1.实时记录队列是否已满
2.少存一位:用(tail+1)%size=head为条件,判断是否以满(常用)
习题
以某abc入栈,出栈顺序可能多种多样;a进后马上出、a进后不出……
答案:D,不用考虑e4,将abcd的e1到e3都试一次 e2、e3、e1无法做到
广义表
-
长度
-
最外层包含多少个元素;
- 例:下图中LS1长度为3
-
-
深度
-
嵌套的层次
-
例:LS1深度为2;
-
如果b替换成(b1,b2)则为三层
-
-
-
表头:第一个元素
-
表尾:去掉表头的所有元素的集合称为表尾;
习题
例题2:
head(head(tail(LS1)))
树与二叉树
树的属性
-
结点的度:一个节点的孩子节点数
-
树的度:所有结点中的最高度就是树的度
-
叶子结点:没有孩子结点的结点
-
分支结点:不是叶子结点
-
内部结点:不是叶子结点也不是根结点
-
父子结点:相对关系,不同结点父子节点不同
-
兄弟结点:有同一个父结点的结点就是兄弟结点
-
层次:
二叉树
二叉树的特例
-
满二叉树:没有缺失
-
即,每层的节点数都有 2^(i-1)个
- i为结点所在的层次
-
-
完全二叉树:最高层缺失右侧但左边排满,其他层没有缺失
- 即除了叶结点所有节点都有2^(i-1)个
二叉树遍历的四种方式
-
前/中/后序遍历和层次遍历
-
其中前中后序时根据根节点访问顺序来定义的
-
层次遍历则是从根节点开始逐层往下遍历
-
前中后序中根节点的访问顺序,结合下图二叉树解释
-
前:根左右,1、2、4、5、7、8、3、6
-
中:左根右,4、2、7、8、5、1、3、6
-
后:左右根,4、8、7、5、2、6、3、1
反向构造二叉树
-
如果有中序和(前、后序)的一种就能推出二叉树情况;
- 如果没有中序,就算前后续都有也不行,必须要有中序;
考法:
-
1.给两种序列构造出相应二叉树或者给出第三种序列
-
2.给表达式,求表达式的 逆波兰式(后缀表达式)
答案:
树转二叉树
- 数据结构中,树主要围绕二叉树展开;一般把普通树转换为二叉树讨论
快速转换法
- 第一个子节点线保留,兄弟结点间连直线;最后旋转整颗树
查找二叉树(排序二叉树)
特征
-
二叉树
-
所有非叶节点都满足下列关系
-
左孩子小于根
-
右孩子大于根
-
插入节点方式
-
若插入的键值存在,则不再插入
-
若查找二叉树为空树,则以新节点构建查找二叉树,该节点为该二叉树的根节点
-
若查找二叉树不空,且键值不在二叉树中那么
-
从根节点开始对比,若小于当前对比的节点就与该节点的左子节点比较,否则于右子节点比较
-
当前比较的节点为叶结点时
-
若键值比叶结点小则将该键值作为该叶节点的左子节点
-
若键值比叶结点大则将该键值作为该叶节点的右子节点
-
-
删除节点的方式
-
若待删除节点是叶节点则直接删除
-
若待删除节点只有一个子节点,则将该子节点与待删除节点的父节点直接连接
-
若待删除节点P有两个子节点,则在其左子树上用中序遍历寻找最大的结点S,用结点S的值代替结点P的值,然后删除结点S
-
适用范围:结点S必须属于上述1、2两种情况之一并按照情况处理S的子节点
- (即,S必须是叶结点或S只有一个子节点)
-
最优二叉树(哈夫曼树)
-
一种工具,用于哈夫曼编码
-
哈夫曼编码:
-
压缩编码方式,让原始信息更短,是一种无损压缩方式
-
多媒体领域常用
-
-
基本概念
-
树的路径长度:所有路径累加起来的大小
-
权:某个叶子结点,有一个数值,代表了某个数值出现的频度
-
带权路径的长度:带权路径的长度*权值
- 22、34、3*8
-
树的带权路径长度(树的代价):所有带权路径长度的和;
构建
-
构成哈夫曼树就是让树的带权路径长度最短
- 如下图中,左1比左2的树的带权路径大
-
构造方法:
-
选出最小的两个权值构成一颗小树;
-
两个权值的和作为树的根节点
-
有两个相同的权值时选哪个无所谓
-
-
将这两个值在数组中划掉;
-
将这两个值的和加入组中;
-
不断重复
-
-
构造实例(右边的树):
-
5和3最小;将8(两数之和)加入数组中;
-
最小为7和8,将15加入组中;
-
最小为8和11将19加入组中
-
线索二叉树
- 意义:很多结点(叶结点)的指针都是空闲的,将这些空指针按照(前中后)序的之一的方式填写指针指向的位置,可以更快的遍历这颗树;
平衡二叉树
-
同一个序列排序二叉树可能有多颗;一颗排序二叉树树左右越平衡查找越快;
- 首先要是排序二叉树
某个结点平衡度计算
-
算法: 左节点深度减去右结点深度
- 深度:有几层深度就是多少
-
所有叶子节点平衡度都是0;
- 因为左子树深度为0,右子树深度也为0,所以相减也是0;
-
例子:
-
左图树
-
权值为7的结点
-
左子树深度为2,右子树深度也为2
-
所以平衡度为0
-
-
权值为39的结点
-
左子树深度为3,右子树深度为0
-
所以平衡度为3,不满住 -1、0、1
-
所以不是平衡二叉树
-
-
-
构造方法
-
构造二叉树时,把数组从小到大排列
-
取中位数为整棵树的根节点
图
完全图
-
无向图中:没对顶点间都有一条边相连,则称改图为完全图
-
有向图中:没对顶点间都有两条有向边相互连接,才能称为完全图
图的存储
邻接矩阵
-
用一个n阶方阵R来存放一个n个结点的图,Rij为矩阵中的i行j列个元素
-
若第i个结点和第j个结点之间有连线则将Rij赋值为1
-
否则为0
-
-
对于无向图来说,邻接矩阵具有对称性,可以只存上三角或下三角矩阵
邻接表
-
将所有顶点及其邻接顶点组成一条链
-
每个顶点都有一条链
-
链表的结构体为:
-
顶点编号
-
距离
-
next
-
-
-
将所有链表的头指针存储在一个一维数组中
图的遍历
深度与广度的遍历方式
-
深度:一直往下走,没有下一个了就回退一步;
-
广度:读完某结点的所有子节点,再读子节点的子节点,如果有某结点已经被读过了就不读;
邻接表的图的遍历
拓扑排序
考法
-
给出一个图,
-
求一个拓扑序列
-
判断哪一个是拓扑序列
-
拓扑序列
-
从入度位0的结点开始,将所有结点都经历一次,直到出度为0的结点
- 走过的所有结点的顺序就是拓扑序列
-
拓扑序列需要满足依赖关系,
-
如4号结点依赖与1、2结点
-
那么1、2必须在4之前
-
但是1、2的顺序没有要求
-
图的最小生成树
- 把图的所有边去掉,只留下若干条边;把所有结点连贯起来:留下的边权值都是较小的;使权值相加是最小的;
两种构造算法
-
普利姆算法
-
算法
-
所有结点初始都视为蓝点集,
-
从一个结点出发,定该结点为红点集;
-
找出红点集到蓝点集最短的距离,即选最短的边;
-
将边连接的点加入红点集;
-
再找和红点集最近的边;(重复上述步骤)
-
-
注意事项:
-
不能形成环
-
不能红点集和红点集相连
-
-
-
克鲁斯卡尔算法:
- 选n-1条最短边,不能形成环路
算法
特性
-
有穷性
-
确定性
- 即使是随机函数其实也是有确定性,只不过是系统内部自己输入了参数,我们看不见;
-
输入 不少于0
-
输出 不少于1
-
有效性:每个步骤都能得到确定的结果
复杂度(重点)
-
考得比较多的是时间复杂的,必考
- 下午设计题会有,要求求时间复杂的;
计算原则
-
用常数次数表达算法,都极为常数级 O(1)
-
一个算法中有多个部分,每个部分的复杂度不同时;取最高值表示整个算法的复杂的;
-
n重循环的复杂的时n^n
-
对树的处理中,时长会有O(log2N)
-
例子:平衡度高的排序二叉树,查找一个结点是
- 可以视为,有几层就比较多少次结点:
-
如果有三层;7个结点;则最多要比较3次;可看为Log2(7);
-
查找
-
每次筛选掉一半的算法 复杂的一般为O(log2N)
- 树、和二分法等方式
顺序查找
-
n极大时可以忽略时间复杂度重点常数;
- 所以虽然实际复杂的为O(n+1)/2,单能省略为 O(n/2)
-
虽然且实际时间复杂度为O(1/2N),但是1/2N也看为N级
-
所以时间复杂的为O(N)
二分查找
- 内容必须是有序排列;
例子:
注意事项
-
带小数时取整;
-
6号已经比较过了,不用再比较了;所以第二次比较从1-5;而不是1-6
散列表
-
根据某种规则直接将内容放入指定位置;需要查找某值时,直接算出来结果即可找到,而不是靠遍历;
- 理想很丰满显示很骨感,方法很好但是因为经常产生冲突,整体效率降低;
排序
排序算法的两个特征
-
是否稳定:
- 例:黑色的21在排完序后依旧在红色21之前,称为稳定排序
-
内排序:内存中排序称为内排序
插入类排序-直接插入排序
-
算法:插入第i个记录时,假设前面的记录都已经排好序了,这个时候将第i个记录放入大小合适的位置即可
-
例(下图):
-
第一步:插入57,前面没有数,放在第一位即可
-
第二步:插入68,已经拍好的序列为57,所以将68插入在57后面
-
第三步:插入59,已经排好的序列为[57、68],所以将59插入57后面
-
第四步:插入52,已经排好的序列为[57、59、68],所以将52插入到57前面
-
-
-
缺陷:排序速度比较慢
插入类排序-希尔排序
-
算法:
-
设定一个小于记录总数N的d1,将所有距离为d1的倍数的记录放入同一组中,在各组内进行直接插入排序;
-
设定一个取第二个增量 d2<d1,重复上述排序,直至dn=1;
-
-
好处:每次使用直接插入排序时,数列都处于基本有序状态,不会有需要进行及其多次排序的数
选择排序-直接选择排序
- 每一次选出最小的;放到对应位置
选择排序-堆排序
-
所有排序中最复杂的几种
-
堆排序应为用到了树的结构,效率会比直接选择排序高很多;
- 特别适合用在,N个元素,取前几名的情况;每一轮就能选出一个最大值;
概念
-
小顶堆:对于每一个结点,孩子结点都小于父节点的完全二叉树
-
大顶堆:对于每一个结点,孩子结点都大于父节点的的完全二叉树
如何建堆
-
建立一个完全二叉树:
-
找到最后一个非叶子结点,判断该结点的子节点是否都比它小,若不是则将子节点中最大的值与该节点替换;
-
找到倒数第二个非子结点,进行上述判断;
-
每一次替换之后,若不是被替换到了叶子结点,都需要再和现在的子节点对比(曾今的孙子结点);
堆排序
-
首先建立一个堆
-
去除堆顶元素
-
将最后一个元素放在堆顶再建立堆
-
将堆顶元素和,用最后一个叶结点的值替换(原来的叶结点删除)
-
将该堆重新排序
- 将刚放上去的叶结点,一直和现在的子节点对比
-
-
再输出堆顶元素,直到所有元素都输出完
1.首先要有一个大/小顶堆
2.取出堆顶元素
3.将最后一个元素放在堆顶
4.一直和子节点比较,如果比它大就换到上面去;
交换类排序-冒泡排序
-
若有N个元素,则进行N轮比较
-
每轮都能将一个最大/小的元素放到对应位置上
-
每轮排序都是,从头/尾开始
-
例:从小到大排序时
-
若n号位元素比m号位元素更大,就将n号位元素和m号位元素替换位置
-
否则不操作
-
下一次排序,从m号位置元素开始,和m+1号元素对比
-
不断循环,直到最后一位元素,此时视为一轮结束
-
-
-
- 可参考文章:https://blog.youkuaiyun.com/lu_1079776757/article/details/80459370
交换类排序-快速排序
- 利用l分治法思想;
算法
-
随便选一个序列中的数,作为基准
-
将基准和最后一个元素对比,若基准大,则将基准和最后一个元素换位置
-
将基准和前面第一个元素比较,若基准小,则将基准和第一个元素换位(此时第一个元素到了最后一个位置,因为之前换过一次)
-
.基准在后面时,和前面的对应数比较,若数比它大就和基准互换位置
-
基准在前面时,和后面的对应数比较,若数比它小就和基准互换位置
-
-
经过一轮之后就能得到两个数列,前面的数列比基准小,后面的数列比基准大
-
每个小数列再用递归的方式再快速排序;
归并排序
算法
-
把序列两两划分为子表
-
对每个子表进行排序
-
把两个或两个以上有序的子表合成,并在合成时排序,得到一个新的两倍大的有序表;
- 为了操作简单一般从两两归并;
-
将两倍大的子表继续合并,直到所有子表合并为一个子表
算法常见思路
把问题不断缩小;因为一个问题不断扩大时往往不是按照线性增长而是按照几何倍速增长;
基数排序
-
思想:拆分多个关键字;根据关键字排序得到新序列;
- 对新序列再按新关键字进行排序……
例子
各算法的时间复杂度
- 利用到二分法和树的,时间复杂的一般为O(nLog2N)