数据结构
一、绪论
二、线性表
1.约瑟夫环问题
删除位置的计算
从线性表中起始位置index出发开始计数,当计数到k时(间隔k-1个数据),删除该位置上的元素;同时该位置又是下一次计数的起始位置:index=(index+k-1)%顺序表长度
三、栈和队列
1.栈
2.循环队列
用指针front表示队头,rear表示队尾
rear=(rear+1)%QueueSize
为了区分队满与队空,当队列中还有一个空位时认为队满:
队满:(rear+1)%QueueSize=front
队空:front=rear
为了让第一个元素入到下标0处,front和rear可以在-1或数组下标最大处。
任意时刻队列中元素的个数:(rear-front+QueueSize)%QueueSize
四、数组和字符串
1.模式匹配
1.1 BF算法:
设串S长度为n,串T长度为m,在匹配成功的情况下,考虑两种极端情况:
- 最好:不成功的匹配都发生在串T的第一个字符。
∑ i = 1 n − m + 1 P i ( i − 1 + m ) \sum_{i=1}^{n-m+1} P_i(i-1+m) ∑i=1n−m+1Pi(i−1+m)= ( n + m ) 2 \frac{(n+m)}{2} 2(n+m)=O(n+m)
( Pi 表示在第i个位置上匹配成功的概率,Pi= 1 n − m + 1 \frac{1}{n-m+1} n−m+11)
第i-1趟不成功比较了i-1次,第i趟成功比较了m次 - 最坏情况:不成功的匹配都发生在串T的最后一个字符。
∑ i = 1 n − m + 1 P i ( i ∗ m ) \sum_{i=1}^{n-m+1} P_i(i*m) ∑i=1n−m+1Pi(i∗m)= m ( n − m + 2 ) 2 \frac{m(n-m+2)}{2} 2m(n−m+2)=O(n*m)
在i-1趟不成功的匹配中比较了(i-1)×m次,第i趟成功的匹配共比较了m次,所以总共比较了i×m次。
1.2 KMP模式匹配算法:
时间复杂性:O(n+m)
2.矩阵压缩
2.1 对称矩阵的压缩存储
对于下三角:
- 矩阵行列标从1开始:
aij在一维数组中的序号= i×(i-1)/2+ j
∵一维数组下标从0开始
∴aij在一维数组中的下标:k= i×(i-1)/2+ j-1 - 矩阵行列标从0开始:
aij在一维数组中的序号= i×(i+1)/2+ j+1
∵一维数组下标从0开始
∴aij在一维数组中的下标:k= i×(i+1)/2+ j
2.2 上三角矩阵的压缩存储:
2.3 对角矩阵的压缩存储:
k=(3*(i-1)-1)+(j-i+1)
k=2i+j-3
3.数组存储
五、树和二叉树
1.二叉树
1.1 基本性质
- 性质1:二叉树中,若叶子结点的个数为n0,度为2的结点个数为n2,则n0=n2+1。
- 性质2:二叉树第i层上最多有2i-1个结点(i>=1)。
- 性质3:一棵深度为k的二叉树中最多有2k-1个结点。
- 性质4:具有n个结点的完全二叉树的深度为⌊log2n⌋+1。
- 性质5:对一棵具有n个结点的完全二叉树从1开始按层序编号,对于编号为i(1<=i<=n)的结点(简称结点i),
如果i>1,则结点i的双亲的编号为⌊i/2⌋,否则是根结点无双亲。
如果2i<=n,则结点i的左孩子的编号为2i,否则无左孩子。
如果2i+1<=n,则结点i的右孩子的编号为2i+1,否则结点i无右孩子。
答: 最少2h-1 和 最多2h-1
图片来自《设高为h的二叉树(规定叶子结点的高度为1)只有度为0和2的结点,则此类二叉树的最少结点数和最多结点数分别为:》
答案:至少2k-1 至多2k-1 序号2k-2+1
2.哈弗曼编码
等长编码: 所有编码都等长,表示n个不同的字符需要[log2n]位。
3.在2n个指针域中只有n-1个指针域用来存储孩子结点的地址,存在n+1个空指针,利用这些空指针存某种遍历的前序和后序结点,指向前驱和后继结点的指针称为线索,加上线索的二叉链表称为线索链表,加上线索的二叉树称为线索二叉树。
六、图
1.邻接矩阵时间和空间复杂度O(n2)
邻接表时间和空间复杂度O(n+e)
2.图的生成树唯一性不能确定,n 个顶点的生成树有 n-1条边。
3.对于含有 n 个顶点 e 条边的连通图,利用 Prim 算法求最小生成树的时间复杂度为O(n2),利用 Kruskal算法求最小生成树的时间复杂度为O(elog2e)。因为Prim 算法采用邻接矩阵做存储结构,适合于求稠密图的最小生成树;Kruskal 算法采用边集数组做存储结构,适合于求稀疏图的最小生成树。
4.可以一次性求所有结点之间的最短距离,只适合于小规模的图,时间复杂度为O(n3)
图规模小,用Floyd。如果边权值有负数,需要判断负圈。
图的规模大,且边的权值非负,用Dijkstra。
Dijkstra算法
用于求单源点最短路径问题。按照路径长度递增的顺序产生最短路径的方法,只适用于权值为正的情况。
5.AOE网与关键路径
ve[k]=max(ve[j]+len<vj,vk>)。初始化ve[0]=0
vl[k]=min(vl[j]-len<vk,vj>),vl[n-1]=ve[n-1]
e[i]=ve[k]
el[i]=vl[j]-len<vk,vj>
七、查找技术
查找算法的性能 :
平均查找长度:ASL=
∑
i
=
1
n
p
i
c
i
\sum_{i=1}^{n} p_ic_i
∑i=1npici
pi为查找第i个记录的概率,ci为查找第i个记录所需的比较次数。
1.顺序查找
查找算法性能
- 查找成功时:
pi= 1 n \frac{1}{n} n1,查找第i个记录需要n-i+1次比较。(因为是倒着比)
ASL= 1 n \frac{1}{n} n1 ∑ i = 1 n ( n − i + 1 ) \sum_{i=1}^{n} (n-i+1) ∑i=1n(n−i+1)= n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)* 1 n \frac{1}{n} n1= n + 1 2 \frac{n+1}{2} 2n+1=O(n) - 查找失败时:
比较次数是n-1次
ASL=O(n)
2.折半查找
判定树性质:
·任意结点的左右子树中结点个数最多相差1
·任意结点的左右子树的高度最多相差1
·任意两个叶子所处的层次最多相差1
与给定值的比较次数等于该记录结点在树中的层数。已知**判定树的深度为⌊log2n⌋+1 **,所以查找成功时,比较次数至多为⌊log2n⌋+1。
例:长度为11的有序表,比较3次就知道成功的情况有4种。(树的深度为4,第3层是满的)
查找算法性能
平均查找长度:
ASL=
∑
i
=
1
n
p
i
c
i
\sum_{i=1}^{n} p_ic_i
∑i=1npici=
1
n
\frac{1}{n}
n1
∑
j
=
1
k
j
∗
2
j
−
1
\sum_{j=1}^{k} j*2^{j-1}
∑j=1kj∗2j−1=log2(n+1)-1(约等于)
平均时间复杂度:O(log2n)
例:长度为11的有序表
查找成功时:
1
∗
2
0
+
2
∗
2
1
+
3
∗
2
2
+
4
∗
4
11
\frac{1*2^0+2*2^1+3*2^2+4*4}{11}
111∗20+2∗21+3∗22+4∗4=
33
11
\frac{33}{11}
1133=3
不成功时:
3
∗
4
+
4
∗
8
12
\frac{3*4+4*8}{12}
123∗4+4∗8=
44
12
\frac{44}{12}
1244=
3
11
\frac{3}{11}
113(因为和给定值进行的关键码的比较次数等于该路径上内部结点的个数,所以分子层数最大等于树的高度)
- 查找成功时:
ASL= 每 个 结 点 比 较 次 数 之 和 结 点 数 \frac{每个结点比较次数之和}{结点数} 结点数每个结点比较次数之和 - 查找失败时:
ASL= 空 指 针 处 比 较 次 数 之 和 空 指 针 数 \frac{空指针处比较次数之和}{空指针数} 空指针数空指针处比较次数之和 - 平均查找长度:
ASL= ∑ i = 1 n p i c i \sum_{i=1}^{n} p_ic_i ∑i=1npici= 1 n \frac{1}{n} n1 ∑ j = 1 k j ∗ 2 j − 1 \sum_{j=1}^{k} j*2^{j-1} ∑j=1kj∗2j−1=log2(n+1)-1=O(log2n)
3.二叉排序树
和给定值的比较次数等于给定值结点在二叉排序树中的层数,比较次数最少为1,最多不超过数的深度。具有n个结点的二叉排序树最大深度为n,最小为⌊log2n⌋+1
二叉排序树的查找性能取决于二叉排序树的形状:O(log2n) ~ O(n)
- 查找成功时:
ASL=(层数 * 该层结点个数) * 每个结点被查找的概率 - 查找失败时:
ASL=(底层 * 空结点个数)/(n+1)
底层指的是叶子结点的那层。 - 平均查找长度:
ASL= ∑ i = 1 n p i c i \sum_{i=1}^{n} p_ic_i ∑i=1npici= 1 n \frac{1}{n} n1 ∑ j = 1 k j ∗ 2 j − 1 \sum_{j=1}^{k} j*2^{j-1} ∑j=1kj∗2j−1=log2(n+1)-1=O(log2n)
4.平衡二叉树
平衡因子: 该结点左子树深度与右子树深度之差,即HL-HR
在平衡树中,结点的平衡因子可以是1,0,-1
5.B树
1.m阶B树:
·树中每个结点至多有m棵子树;
·若根结点不是终端结点,则至少有两棵子树;
·除根结点外,其他结点至少有⌈m/2⌉棵子树,即至少含有⌈m/2⌉-1个关键字;
…
注意:
6.散列表(hash)查找
散列查找的性能分析
1.散列查找的性能取决于什么?
产生冲突后的查找仍然是给定值与关键码进行比较的过程。
在查找过程中,关键码的比较次数取决于产生冲突的概率。
2.影响冲突产生的因素有:
·散列函数是否均匀
·处理冲突的方法
·散列表的装载因子
( α=表中填入的记录数/表的长度)
装填因子越小,只能说明发生冲突的可能性越小,不能说不会引起冲突。
3.
- 手工计算等概率情况下查找成功的平均查找长度公式:
ASL= 1 表 中 置 入 元 素 个 数 n \frac{1}{表中置入元素个数n} 表中置入元素个数n1 ∑ i = 1 n ( C i ) \sum_{i=1}^{n} (C_i) ∑i=1n(Ci)
Ci为置入每个元素时所需的比较次数。 - 手工计算在等概率情况下查找不成功的平均查找长度公式:
ASL= 1 哈 希 函 数 取 值 个 数 \frac{1}{哈希函数取值个数} 哈希函数取值个数1 ∑ i = 1 r ( C i ) \sum_{i=1}^{r} (C_i) ∑i=1r(Ci)
Ci为函数取值为i时确定查找不成功时的比较次数。(闭散列表查每一个位置到空的距离,开散列表查空指针)
《哈希表:线性探测法和链地址法求查找成功与不成功的平均查找长度》
散列表 | 堆积现象 | 结构开销 | 插入/删除 | 查找效率 | 估计容量 |
---|---|---|---|---|---|
开散列表 | 不产生 | 有 | 效率高 | 效率高 | 不需要 |
闭散列表 | 产生 | 没有 | 效率低 | 效率低 | 需要 |
4.散列表中地址链接法
- 查找成功时:
ASL=每个元素被访问的次数 - 查找失败时:
ASL=每个地址被访问的次数
5.折半查找判定树值需要考虑n的个数,结点为1~n;
·平衡二叉树,加点的每一步都要考虑平衡,不平衡就找最小不平衡子树,变换,继续加点;
·二叉排序树,根据每个元素的值建树,保证中序遍历有序
一棵高度为 h 的平衡二叉树,最少含有 个结点。
A 2h B 2 h -1 C 2 h +1 D 2 h -1
【解答】D
八、排序技术
1.插入排序
1.1直接插入排序
性能分析:
·最好的情况:初始序列是正序
比较次数:
n-1
移动次数:
2(n-1) (从第二项开始每一趟待排记录移到r[0]再移回来)
时间复杂度:
O(n)
辅助空间:
O(1)
·最坏的情况:初始序列为逆序
比较次数:
∑
i
=
2
n
i
\sum_{i=2}^n i
∑i=2ni=
(
n
+
2
)
(
n
−
1
)
2
\frac{(n+2)(n-1)}{2}
2(n+2)(n−1)
移动次数:
∑
i
=
2
n
i
+
1
\sum_{i=2}^n i+1
∑i=2ni+1=
(
n
+
4
)
(
n
−
1
)
2
\frac{(n+4)(n-1)}{2}
2(n+4)(n−1)
时间复杂度:
O(n2)
辅助空间:
O(1)
·平均情况:
比较次数:
∑
i
=
2
n
i
2
\sum_{i=2}^n \frac{i}{2}
∑i=2n2i=
(
n
+
2
)
(
n
−
1
)
4
\frac{(n+2)(n-1)}{4}
4(n+2)(n−1)
移动次数:
∑
i
=
2
n
i
+
1
2
\sum_{i=2}^n \frac{i+1}{2}
∑i=2n2i+1=
(
n
+
4
)
(
n
−
1
)
4
\frac{(n+4)(n-1)}{4}
4(n+4)(n−1)
时间复杂度:
O(n2)
辅助空间:
O(1)
1.2希尔排序
·希尔排序的趟数:
1:n/2
2:n/4
3:n/8
……
k:n/2k
所以
n
2
k
\frac{n}{2^k}
2kn>=1
希尔排序算法的时间性能是所取增量的函数,目前尚未找到最好的增量序列。
平均时间复杂度:
O(nlog 2n)~O(n2)
当n在某个特定范围内:最好的时间复杂度:
O(n1.3 ) 。最坏的时间复杂度:
O(n2)
辅助空间:
O(1)
2.交换排序
2.1冒泡排序
性能分析:
·最好的情况:初始序列是正序
比较次数:
n-1
移动次数:
0
时间复杂度:
O(n)
辅助空间:
O(1)
·最坏的情况:初始序列为逆序
比较次数:
∑
i
=
1
n
−
1
(
n
−
i
)
\sum_{i=1}^{n-1} (n-i)
∑i=1n−1(n−i)=
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1)
移动次数:
∑
i
=
1
n
−
1
3
(
n
−
i
)
\sum_{i=1}^{n-1}3(n-i)
∑i=1n−13(n−i)=
3
n
(
n
−
1
)
2
\frac{3n(n-1)}{2}
23n(n−1)(比较1次移动3次)
例如:
5-4-3-2-1
4-3-2-1 5
3-2-1 4 5
2-1 3 4 5
1 2 3 4 5
时间复杂度:
O(n2)
辅助空间:
O(1)
·平均情况:
时间复杂度:
O(n2)
辅助空间:
O(1)
2.2快速排序
性能分析:
执行时间取决于递归的深度
·最好的情况:每次划分左右侧子序列的长度都相等。
扫描时间:
O(n)
排序时间:
O(nlog2n)
·最坏的情况:初始序列为正序或逆序
比较次数:
∑
i
=
1
n
−
1
(
n
−
i
)
\sum_{i=1}^{n-1} (n-i)
∑i=1n−1(n−i)=
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1)=O(n2)
移动次数:
小于比较次数
时间复杂度:
O(n2)
·平均情况:
时间复杂度:
O(nlog2n)
辅助空间:
O(log2n)~O(n)
3.选择排序
3.1简单选择排序
性能分析:
·最好的情况:初始序列是正序
移动次数:
0
比较次数:
∑
i
=
1
n
−
1
(
n
−
i
)
\sum_{i=1}^{n-1} (n-i)
∑i=1n−1(n−i)=
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1)
·最坏的情况:初始序列为逆序
移动次数:
3(n-1)
比较次数:
∑
i
=
1
n
−
1
(
n
−
i
)
\sum_{i=1}^{n-1} (n-i)
∑i=1n−1(n−i)=
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1)
·平均情况:
时间复杂度:
O(n2)
辅助空间:
O(1)
3.2堆排序
性能分析:
·最好的情况、最坏的情况、平均情况:
时间复杂度:
O(nlog2n)
辅助空间:
O(1)
4.归并排序
二路归并排序
性能分析:
时间性能:
一趟归并操作是将r[1] ~ r[n]中相邻的长度为h的有序序列进行两两归并,并把结果存放到r1[1] ~ r1[n]中,这需要O(n)时间。整个归并排序需要进行(log2n)向下取整趟,因此,总的时间代价是O(nlog2n)。这是归并排序算法的最好、最坏、平均的时间性能。
空间性能:
算法在执行时,需要占用与原始记录序列同样数量的存储空间,因此空间复杂度为O(n)。
5.桶式排序
性能分析:
时间性能:
一次分配O(n)
一次收集O(m)
总:O(m+n)
空间性能:
O(m)
6.基数排序
性能分析:
时间性能:
多次分配,多次收集
一次分配的时间复杂性O(n)
一次收集的时间复杂性O(m), (m为每个子关键码的取值范围,上例为数的位数)
分配与收集的次数:数的位数(d)即O(d(n+m))
空间性能:
O(m)
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 |
---|---|---|---|---|
直接插入排序 | O(n2) | O(n) | O(n2) | O(1) |
希尔排序 | O(nlog2n) ~ O(n2) | O(n1.3) | O(n2) | O(1) |
起泡排序 | O(n2) | O(n) | O(n2) | O(1) |
快速排序 | O(nlog2n) | O(nlog2n) | O(n2) | 考虑递归:O(log2n) ~ O(n) 不考虑递归:O(1) |
简单选择排序 | O(n2) | O(n2) | O(n2) | O(1) |
堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) |
归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) |
桶排序 | O(m+n) | O(m+n) | O(m+n) | O(m) |
基数排序 | O(d(m+n) | O(d(m+n) | O(d(m+n) | O(m) |
分类
- 是否稳定:
稳定的:直接插入排序、起泡排序、归并排序、基数排序、桶式排序;
不稳定的:希尔排序、简单选择排序、快速排序和堆排序。
- 时间复杂度(基于比较的排序):
直接插入排序、简单选择排序、起泡排序O(n^2^)
(未改进,简单排序算法,直接插入排序最常用,特别是对于基本有序的序列)
快速排序、堆排序、归并排序O(nlog~2~n)
(快排最快,待排序列记录较多时归并比堆更快)
希尔排序在O(nlog~2~n) ~ O(n^2^)之间
- 空间复杂度(基于比较的排序)
O(n)归并排序
O(log~2~n) ~ O(n)快速排序
O(1)其他排序
对于基于比较的排序:
最好的情况下,直接插入排序和起泡排序最快;
最坏的情况下,堆排序和归并排序最快;
平均情况下,快速排序最快。