专题六:数据结构与算法基础
数组与矩阵
数组(按行存储、按列存储)
数组类型 | 存储地址计算 |
---|---|
一维数组a[n] | a的存储地址为:a+i*len |
二维数组a[m][n] | a[i][j]的存储地址(按行存储)为:a+(i*n+j)*len *a[i][j]的存储地址(按列存储)为:a+(j*m+i)len |
:::color1 已知5行5列的二维数组a中的各元素占两个字节,求元素a[2][3] 按行优先存储的存储地址?
*a+(2*5+3)2
:::
稀疏矩阵(代入验证排除法)
稀疏矩阵 | 要点 |
---|---|
上三角矩阵 | 在矩阵中下标分别为i和j的元素,对应的一维数组的下标计算公式为:(2n-i+1)×i/2+j |
下三角矩阵 | 在矩阵中下标分别为i和j的元素,对应的一维数组的下标计算公式为:(i+1)*i/2+j |
线性结构(线性表、栈、队列、串)
线性表与链表



顺序存储与链式存储(对比)
性能类别 | 具体项目 | 顺序存储 | 链式存储 |
---|---|---|---|
空间性能 | 存储密度 | =1,更优 | <1 |
容量分配 | 事先确定 | 动态改变,更优 | |
时间性能 |
读运算 | 0(1),更优 | O(n),最好情况为1,最坏情况为n |
查找运算 | 0(n) | 0(n) | |
插入运算 | O(n),最好情况为0,最坏情况为n | 0(1),更优 | |
删除运算 | 0(n) | 0(1),更优 |
队列与栈


广义表表(长度、深度、head/tail)
:::color1 广义表是n个表元素组成的有限序列,是线性表的推广通常用递归的形式进行定义,记做:LS(ao,a…,a)
:::
注:其中LS是表名,a是表元素,它可以是表(称做子表),也可以是数据元素(称
为原子)。其中n是广义表的长度(也就是最外层包含的元素个数),n=0的广义表
为空表:而递归定义的重数就是广义表的深度,直观地说,就是定义中所含括号的重
数(原子的深度为0,空表的深度为1)。
树的问题
树与二叉树的基本概念

:::color1 结点 (圆形)
分支 (结点下面的线)
结点的度:该节点的子树数目
树的度:各结点度的最大值
叶子结点:度为0的结点-无分支
分支结点(非终端结点):度不为0的结点-有分支
内部结点:除根以外的分支结点
层次:根为第1层,以此类推
树的高度:一棵树的最大层次数-总层数
父结点、子结点、兄弟结点
:::
满二叉树和完全二叉树

:::color1 二叉树是n(n≥0)个结点的有限集合,它或者是空树(n=0),或者是由1个根结点及两棵不相交的且分别称为左、右子树的二叉树所构成。可见,二叉树同样具有递归性质。
:::
二叉树的遍历

:::color1 层次遍历 (从上至下,从左至右)
前序遍历【根-左-右】
中序遍历【左-根-右】
后序遍历【左-右-根】
:::
树转二叉树(孩子-兄弟表示法)
:::color1 孩子结点-左子树结点
兄弟结点-右孩子结点
:::
查找二叉树(排序二叉树)左小于根,右大于根

:::color1 二叉排序树
左孩子小于根
右孩子大于根
左孩子<根<右孩子
:::
:::success 特点:
1)二叉查找树的中序遍历序列为从小到大排列的序列。
2)值最小的结点无左子树,值最大的结点无右子树。
3)每一层从左到右进行遍历的序列为从小到大排列的序列。
:::
最优二叉树(哈夫曼树、哈夫曼编码)
:::warning 每个节点的度只能为2或者0;
叶子结点数是N时,中间节点数为N-1,总结点树为2*N-1
哈夫曼编码:任意一个叶节点的编码都不是其他叶节点编码的前缀。
哈夫曼树应用于编码(左小右大
:::
:::color4 叶子结点的路径长度:结点到根的分支线数量
树的路径长度:所有叶子结点路径长度之和
权:叶子结点的数值
叶子结点的带权路径长度:权重*路径
树的带权路径长度(树的代价):所有叶子结
点带权路径之和
:::
图
图的概念及存储
概念(有向图、无向图)
:::color2 完全图
★在无向图中,若每对顶点之间都有一条边相连,则称该图为完全图(completegraph).
★在有向图中,若每对顶点之间都有二条有向边相互连接,则称该图为完全图。
:::
存储(邻接矩阵、邻接表)
:::color2 用一个n阶方阵R来存放图中各结点的关联信息,其矩阵元素Rij定义
为:
:::
:::color2 首先把每个顶点的邻接顶点用链表示出来,然后用一个一维数组来顺序存储上面每个链表的头指针
:::
图的遍历
遍历方法 | 说明 | 示例 | 图例 |
---|---|---|---|
深度优先 | 1.首先访问出发顶点V 2.依次从V出发搜素V的任意 一个邻接点W; 3.若W未访问过,则从该点出 发继续深度优先遍历 它类似于树的前序遍历。 |
V1,V2,V4,V8,V5,V3,V6,V7 | ![]() |
广度优先 | 1.首先访问出发顶点V 2.然后访问与顶点V邻接的全部未访问顶点W、X、Y... 3.然后再依次访问W、X、Y..邻接的未访问的顶点; |
V1,V2,V3,V4,V5,V6,V7,V8 |
图的最小生成树(普里姆算法、克鲁斯卡尔算法)

普里姆算法:从一个顶点出发,依次寻找最近的点,并且不能形成环路。
鲁斯卡尔算法:从最短的边开始选取,依次选取权重小的边,但不能组成环路。
图的拓扑排序
在有向图中,若顶点表示活动,用有向边表示活动之间的优先关系(先后关系),则称这样的有向图为活动图,简称AOV网。
:::color2 按照箭头方向把有向图的节点连起来就会形成一个序列。如果该序
列没有违反途中节点的先后顺序,就叫做top序列,也就是先后序列。
上图的拓扑序列有:02143567,01243657,02143657,01243567。
:::
算法的特性
:::color2 有穷性:执行有穷步之后结束。
确定性:算法中每一条指令都必须有确切的含义,不能含糊不清。
√输入(>=0)
√输出(>=1)
有效性:算法的每个步骤都能有效执行并能得到确定的结果。例如a=0,b/a就无效
:::
顺序查找与二分查找
顺序查找
:::color2 顺序查找的思想:将待查找的关键字为key的元素从头到尾与表
中元素进行比较,如果中间存在关键字为key的元素,则返回查找
成功;否则,返回查找失败。
查找成功时,顺序查找的平均查找长度为(等概率情况下):

:::
二分查找
:::color2 二分法查找的基本思想是:(设R[low,,high]是当前的查找区)
(1)确定该区间的中点位置:mid=[(low+high)/2];
(2)将待查的k值与R[mid].key比较,若相等,则查找成功并
返回此位置,否则需确定新的查找区间,继续二分查找,具体方法
如下。
若R[mid].key>k,则由表的有序性可知R[mid,.,n].key均大于k,因此
若表中存在关键字等于k的结点,则该结点必定是在位置mid左边的子表
R[low.,mid-1]中。因此,新的查找区间是左子表R[low...,high],其
中high=mid-1。
:::
散列表查找(Hash表)
基本概念
:::color2 散列表查找的基本思想是:已知关键字集合U,最大关键字为m,设计一
个函数Hash,它以关键字为自变量,关键字的存储地址为因变量,将关
键字映射到一个有限的、地址连续的区间T[0..n-1](n<<m)中,这个
区间就称为散列表,散列查找中使用的转换函数称为散列函数。
:::
例:记录关键码为(3,8,12,17,9),取m=10(存储空间为10),p=5,散列函数h=key%op。
散列表冲突的解决方法(线性探测法、伪随机数法)
:::color2 开放定址法是指当构造散列表发生冲突时,使用某种探测手段,产生一个
探测的散列地址序列,并且逐个查找此地址中是否存储了数据元素,如果
没有,则称该散列地址开放,并将关键字存入,否则继续查找下一个地址。
只要散列表足够大,总能找到空的散列地址将数据元素存入。
:::
排序
基本概念
排序的概念:稳定与不稳定排序 排序方法分类
插入类排序:直接插入排序、希尔排序;
交换类排序:冒泡排序、快速排序;
选择类排序:简单选择、排序堆排序;
直接插入排序
:::color1 直接插入排序:即当插入第i个记录时,R1,R2,",R均已排好序,因此,将第i个记录R依次与Ri-1,“,R2,R进行比较,找到合适的位置插入。它简单明了,但速度很慢。
直接插入排序是一种稳定的排序方法,时间复杂度为0(n2)。在排序过程中仅需要一个元素的辅助空间,空间复杂度为0(1)。适用于基本有序的情况,此时时间复杂度近乎线性,即0(n)。
:::
希尔排序(先分组排序、再直接插入排序)
:::color1 希尔(Shell)排序:先取一个小于n的整数d作为第一个增量,把文件的全部记录分成d个组。所有距离为d的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d<d重复上述的分组和排序,直至所取的增量d=1(d<d-1<"<d<d),即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一
种分组插入方法。
希尔排序是一种不稳定的排序方法,据统计分析其时间复杂度约为0(n1.3)。在排序过程中仅需要一个元素的辅助空间用于数组元素的交互,空间复杂度为0(1)。
:::
直接选择排序
:::color1 直接选择排序的过程是,首先在所有记录中选出排序码最小的记录,把它与第1个记录交换,然后在其余的记录内选出排序码最小的记录,与第2个记录交换…·依次类推,直到所有记录排完为止。直接选择排序是一种不稳定的排序方法,其时间复杂度约为0(n²)。在排序过程中仅需要一个元素的辅助空间用于数组元素的交互,空间复杂度为0(1)。
:::
堆排序

堆的概念
:::color1 设有n个元素的序列{K,K2,",Kn},当且仅当满足下述关系之一时,称之为堆。
(1)K≤K2且K≤K2i+1
(2)K≥K2;且K≥K2i+10
其中(1)称为小顶堆,,(2)称为大顶堆
:::
堆排序(适合得到前K个最大/小的数)
:::color1 堆排序的基本思想为:先将原始序列调整为堆序列后得到堆顶元素,再将剩下的序列重新调整为堆--重构堆,得到此时的堆顶元素,依此类推,直到所有元素均输出为止,此时元素输出的序列就是一个有序序列。
对于大量的记录来说,堆排序的效率很高。
堆排序的算法步骤如下(以大顶堆为例):
(1)初始时将顺序表R[1..n]中元素建立为一个大顶堆,堆顶位于R[1],待序区为R[1..n]。
(2)循环执行步骤3~步骤4,共n-1次。
(3)假设为第i运行,则待序区为R[1..n-i+1],将堆顶元素R[1]与待序区尾元素R[n-i+1]交换,此时顶点元素被输出,新的待序区为R[1..n-i]。
(4)待序区对应的堆已经被破坏,将之重新调整为大顶堆。
:::
冒泡排序
:::color1 冒泡排序的基本思想是,通过相邻元素之间的比较和交换,将排序码较小的元素逐渐从底部移向顶部。由于整个排序的过程元素就像水底下的气泡一样逐渐向上冒,因此称为冒泡算法。冒泡排序是一种稳定的排序方法,其时间复杂度约为0(n2)。在排序过程中仅需要一个元素的辅助空间用于数组元素的交互,空间复杂度为0(1)。
:::
快速排序(分治法)
:::color1 快速排序采用的是分治法,其基本思想是将原问题分解成若干个规模更小但结构与原问题相似的子问题。通过递归地解决这些子问题,然后再将这些子问题的解组合成原问题的解。
在O(nlog2n)时间量级上,平均性能最好。
:::
归并排序(二路合并)
:::color1 归并也称为合并,是将两个或两个以上的有序子表合并成一个新的有序表。若将两个有序表合并成一个有序表,则称为二路合并。合并的过程是:比较A[i]和A[j]的排序码大小,若A[i]的排序码小于等于A[]的排序码,则将第一个有序表中的元素A[i]复制到R[K]中,并令i和k分别加1;如此循环下去,直到其中一个有序表比较和复制完,然后再将另一个有序表的剩余元素复制到R中。归并排序是一种稳定的排序方法,其时间复杂度约为O(nlog2n)。在排序过程中仅需要一个元素的辅助空间用于数组元素的交互,空间复杂度为0(n)。
:::
基数排序(从最小位到最大位依次排序)
:::color1 基数排序是一种借助多关键字排序思想对单逻辑关键字进行排序的方法。基数排序不是基于关键字比较的排序方法,它适合于元素很多而关键字较少的序列。基数的选择和关键字的分解是根据关键字的类型来决定的,例如关键字是十进制数,则按个位、十位来分解。基数排序是一种稳定的排序方法,其时间复杂度约为O(d(n+rd))。在排序过程中仅需要一个元素的辅助空间用于数组元素的交互,空间复杂度为O(rd)。
:::
排序算法对比
类别 | 排序方法 | 时间复杂度**<font style="color:#DF2A3F;"></font>** |
空间复杂度 | 稳定性 | |
---|---|---|---|---|---|
平均情况 | 特殊情况 | 辅助存储 | |||
插入排序 | 直接插入 | 0(n2) | 基本有序最优O(n) | 0(1) | 稳定 |
Shell排序 | O(n1.3) | --- | 0(1) | 不稳定 | |
选择排序 | 直接选择 | 0(n2) | --- | 0(1) | 不稳定 |
堆排序 | O(nlog2n) | --- | 0(1) | 不稳定 | |
交换排序 | 冒泡排序 | 0(n2) | --- | 0(1) | 稳定 |
快速排序 | O(nlog2n) | 基本有序最差 O(n2) | 0(1) | 不稳定 | |
归并排序 | O(nlog2n) | --- | 0(n) | 稳定 | |
基数排序 | O(d(n+rd)) | --- | 0(rd) | 稳定 |
时间复杂度与空间复杂度
:::color1 时间复杂度是指程序运行从开始到结束所需要的时间。通常分析时间复杂度的方法是从算法中选取一种对于所研究的问题来说是基本运算的操作,以该操作重复执行的次数作为算法的时间度量。一般来说,算法中原操作重复执行的次数是规模n的某个函数T(n)。由于许多情况下要精确计算T(n)是困难的,因此引入了渐进时间复杂度在数量上估计一个算法的执行时间。其定义如下:
如果存在两个常数c和m,对于所有的n,当n≥m时有f(n)≤cg(n),则有f(n)=O(g(n))。也就是说,随着n的增大,f(n)渐进地不大于g(n)。例如,一个程序的实际执行时间为T(n)=3n3+2n²+n,则T(n)=0(n²)
常见的对算法执行所需时间的度量:
0(1)<0(1ogn)<0(n)<0(nlogn)<0(n2)<0(n3)<0(2)
空间复杂度是指对一个算法在运行过程中临时占用存储空间大小的度量。一个算法的空间复杂度只考虑在运行过程中为局部变量分配的存储空间的大小
:::
主定理式求算法时间复杂度
:::color1 主定理)设a≥1和b>1为常数,f(n)为函数,T(n)为定义在非负整数上的递归式T(n)=aT(n/b)+f(n),其中,n/b指[n/b]或[n/b],那么T(n)可能有如下的渐进紧致界。
(1)若对于某常数e>0,有f(n)=O(nlogba-6),则T(n)=@(nlogba)。
(2)若f(n)=(noba1gn),则T(n)=(nosba1gk+1n)。
(3)若对于某常数e>0,有f(n)=Ω(nlogba+),且对于常数c<1与所有足够大的n有af(b/n)<cf(n),则T(n)=O(f(n)。
如果上面的主定理式算出来ε=0,那么基本上:T(n)=nlogn(可用递归树法和
代换法验证)
:::
递归式求算法时间复杂度
形如T(n)=T(n-1)+f(n),那么用展开式求解T(n)
其他类型
分治法
:::color1 特征:把一个问题拆分成多个小规模的相同子问题,一般可用递归
解决。
经典问题:斐波那契数列、归并排序、快速排序、矩阵乘法、二分
搜索、大整数乘法、汉诺塔
:::
贪心法(一般用于求满意解)
:::color1 特征:局部最优,但整体不见得最优。每步有明确的、既定的策略。
经典问题:
背包问题(如装箱)、多机调度、找零钱问题,最小生成树问题
(普里姆算法、克鲁斯卡尔算法)
:::
动态规划法(用于求最优解)“最优子结构”和递归式
:::color1 特征:划分子问题,使用数组存储子问题结果,利用查询子问题结果
构造最终问题结果。(一般自顶向下时间复杂度为0(2"),自底向上时间
复杂度为0(na)效率更高)
经典问题:斐波那契数列、矩阵乘法、背包问题、LCS最长公共子序
列
:::
回溯法
:::color1 特征:系统地搜索一个问题的所有解或任一解。
经典问题:N皇后问题、迷宫、背包问题
(回溯法对解空间做深度优先探索,分支限界法对解空间做广度优先探索)
:::
