算法基础
分治法
许多有用的算法在结构上是递归的:为了解决一个给定的问题,算法一次或多次递归地调用其自身以解决紧密相关的若干子问题。
这些算法典型地遵循分治法的思想:将原问题分解为几个较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。
随机算法
如果一个算法的行为不仅由输入决定,而且也由随机数生成器产生的数值决定,则称这个算法是随机的。
样例:
生日悖论:一个屋子里人数必须要达到多少人,才能使其中两人生日相同的机会达到50%?
- 两人生日相同的概率是1/n
- 采用指示器随机变量
- 若屋子里至少有
2
n
\sqrt{2n}
2n+1个人,我们就可以期望至少有两人生日相同。
对于n=365,k=28.
排序
希尔排序又称缩小增量排序法,是一种基于插入思想的排序方法。
堆排序
对树形选择排序的改进。
优先队列
优先队列是一种用来维护由一组元素构成的集合S的数据结构,其中的每一个元素都有一个相关 的值,称为关键字
- 最大优先队列
- 最小优先队列
快速排序
交换类排序
冒泡排序(相邻排序法)
通过对相邻的数据元素进行交换,逐步将待排序序列变成有序序列的过程。
- 最好情况划分
- 平衡划分
- 最坏情况分析
线性时间排序
三种线性时间复杂度的排序算法:
- 计数排序
- 基数排序
- 桶排序
决策树模型
比较排序可以被抽象为一棵决策树。
决策树是一棵完全二叉树,它可以表示在给定输入规模情况下,某一特定排序算法对所有元素的比较操作。
计数排序假设n个输入元素中的每一个都是在0到k区间内的一个整数,其中k为某个整数。
计数排序的基本思想是:对每一个输入元素x,确定小于x的元素个数。
基数排序是先按最低有效位进行排序。
桶排序
桶排序将[0,1)区间划分为n个相同大小的子区间,或称为桶。
然后,将n个输入数分别放到各个桶中。
因为输入数据是均匀、独立地分布在[0,1)区间上,所以一般不会出现很多数,落在同一个桶中的情况。为得到输出结果,对每个桶中的数进行排序,然后遍历每个桶,按照次序把各个桶中的元素列出来即可。
中位数与顺序统计量
最大值与最小值
数据结构
线性表是n个类型相同的数据元素的有限序列。
栈和队列
在栈中,被删除的是最近插入的元素,后进后出
压入(push)
弹出(pop)
- 顺序存储的栈为顺序栈
- 链式存储的栈为链栈
顺序栈利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时由于栈操作的特殊性,还必须附设一个位置指针top(栈顶指针)来动态地指示栈顶元素在顺序栈中的位置。
空栈:top=-1
递归
递归是在定义自身的同时又出现了对自身的引用。
如果一个函数在定义体内直接调用自己,则称为直接递归函数
如果一个函数经过一系列的中间调用语句,通过其他函数间接调用自己,则称为间接递归函数。
递归定义函数
二阶斐波那契数列定义为:
F i b ( n ) = { 0 , 若 n = 0 1 , 若 n = 1 F i b ( n − 1 ) + F i b ( n − 2 ) , 若 n > 1 Fib(n)=\begin{cases} 0, 若n=0 \\1,若n=1\\Fib(n-1)+Fib(n-2),若n>1 \end{cases} Fib(n)=⎩⎪⎨⎪⎧0,若n=01,若n=1Fib(n−1)+Fib(n−2),若n>1
阿克曼函数定义为:
A
c
k
(
m
,
n
)
=
{
n
+
1
,
当
m
+
0
时
A
c
k
(
m
−
1
,
1
)
,
当
m
≠
0
,
n
=
0
时
A
c
k
(
m
−
1
,
A
c
k
(
m
,
n
−
1
)
,
当
m
≠
0
,
n
≠
0
时
Ack(m,n)=\begin{cases} n+1,当m+0时 \\Ack(m-1,1),当m\neq0,n=0时\\Ack(m-1,Ack(m,n-1),当m\neq0,n\neq0时 \end{cases}
Ack(m,n)=⎩⎪⎨⎪⎧n+1,当m+0时Ack(m−1,1),当m=0,n=0时Ack(m−1,Ack(m,n−1),当m=0,n=0时
用C语言描述:
int ack(int m ,int n)
{
if(m==0)
return n+1;
else if (n==0)
return ack(m-1,1);
else
return ack(m-1,ack(m,n-1));
}
n阶汉诺塔问题
假设有三个分别命名为X,Y,Z的塔座,在塔座X上插有n个直径大小各不相同、从小到大编号为1,2,···,n的圆盘。现要求将塔座X上的n个圆盘移动到塔座Z上,并仍按同样顺序叠排。圆盘移动时必须遵守以下规则:
- 每次只能移动一个圆盘
- 圆盘可以插在X,Y和Z中的任何一个塔座上。
- 任何时刻都不能将一个较大的圆盘压在较小的圆盘之上。
汉诺塔递归算法
void hanoi(int n,char x,char y,char z)
{
if(n==1)
move(x,1,z)
else
{
hanoi(n-1,x,z,y);
move(x,n,z);
hanoi(n-1,y,x,z);
}
}
在队列中,被删去的总是在集合中存在时间最长的那个元素,先进先出
入队(enqueue)
出队(dequeue)
队头队尾:
在队列中,允许插入的一端称为队尾,允许删除的一端称为队头。
链队列
循环队列
链表
链表中的各对象按线性顺序排列,链表的顺序是由各个对象里的指针决定的。
十字链表
动态规划
动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题。动态规划算法对每个子子问题只求解一次,将其保存在一个表格中。
动态规划方法通常用来求解最优化问题。
最优子结构
如果一个问题的最优解包含其子问题的最优解,我们就称此问题具有最优子结构性质。
无权最短路径:找到一条从u到
υ
\upsilon
υ的边数最少的路径。这条路径必然是简单路径,因为如果路径中包含环,将环去掉显然会减少边的数量。
无权最长路径:找到一条从u到
υ
\upsilon
υ的边数最多的简单路径。
如果递归算法反复求解相同的子问题,称最优化问题具有重叠子问题性质。
贪心算法
做出局部最优的选择,寄希望这样的选择能导致全局最优解。
0-1背包问题:小偷抢劫商店,背包容纳重量一定,商品重量、价值不同,求带走商品总价值最高。
分数背包问题:设定与上题相同,但对每个商品,小偷可以拿走其一部分,而非二元选择。
贪心策略:首先尽可能拿走每磅价值最高的商品,然后拿走每磅价值第二高的商品,以此类推,直到达到重量上限。
贪心策略可以求解分数背包问题,而不能求解0-1背包问题。
拟阵
斐波那契
图算法
对于图G = (V,E),可以用两种标准表示方法表示。
一种表示法将图作为邻接链表的组合,另一种表示法则将图作为邻接矩阵看待。
二叉树
把满足一下两个条件的树称为二叉树
- 每个结点的度都不大于2
- 每个结点的孩子结点次序不能任意颠倒
二叉树的性质:
- 在二叉树第i层上至多有 2 i − 1 2^{i-1} 2i−1个结点(i ≧ \geqq ≧ 1)
- 深度为k的二叉树至多有 2 k 2^k 2k-1个结点(i ≧ \geqq ≧ 1)
二叉树的遍历
用L、D、R分别表示遍历左子树、访问根节点、遍历右子树,
按先左后右的方式,有三种:
先序遍历(DLR):
若二叉树为空,则为空操作,否则依次执行以下三个操作:
- 访问根结点
- 按先序遍历左子树
- 按先序遍历右子树
中序遍历(LDR):
若二叉树为空,则为空操作,否则依次执行以下三个操作:
- 按中序遍历左子树
- 访问根结点
- 按中序遍历右子树
后序遍历(LRD):
若二叉树为空,则为空操作,否则依次执行以下三个操作:
- 按后序遍历左子树
- 按后序遍历右子树
- 访问根结点
哈夫曼树
路径是指从根结点到该结点的分支序列,
路径长度是指根结点到该结点所经过的分支数目。
给树的每个结点赋予一个具有某种意义的实数,称该实数为这个结点的权
在树结构中,把从树根到某一结点的路径长度与该结点的权的乘积,称为该结点的带权路径长度。
哈夫曼树是由n个带权叶子结点构成的所有二叉树中带权路径长度最短的二叉树。又称最优二叉树。
广度优先搜索
树的按层次遍历的推广。
prim 的最小生成树算法
Dijkstra的单源最短路径算法
深度优先搜索
树的先根遍历的推广
强连通分量
最小生成树
最小生成树问题
图G=(V(G),E(G))树T=(V(T),E’(T))
在一个连通无向图G=(V, E)中,对于其中的每条边(u,v)∈E,赋予其权重w(u, v),则最小生成树问题就是要在G中找到一个连通图G中所有顶点的无环子集T⊆E,使得这个子集中所有边的权重之和最小。
即生成树为一条连接所有点的路径,最小生成树为权重和最小那个生成树(非环)
Kruskal算法——短边优先法(避圈法)
Prim算法——加点法
单源最短路径
- 单目的地最短路径问题
- 单结点对最短路径问题
- 所有结点对最短路径问题
Dijkstra算法解决的是带权重的有向图上单源最短路径问题,该算法要求所有边的权重都为非负值。
按最短路径长度递增的顺序产生一点到其余各项点的所有最短路径。
所有结点对的最短路径
参考资料:
《算法导论》
《数据结构》