目录
简要介绍
·复杂度
·迭代
插入排序
二分查找
快排划分
选择排序
计数排序
基数排序
桶排序
·递归
递归式的计算-四种方法
欧几里得算法
汉诺塔问题
快速排序
归并排序
堆排序
·分治
二维极大点问题
一维最邻近点对
二维最邻近点对
逆序对的数目
凸包
最大字段和问题
循环赛日程表
维诺图程序设计
补充
·减治
DFS
BFS
拓扑排序
生成排列(O(n!))
生成子集(O(2^n))
淘汰赛冠军问题
快速幂
俄罗斯农民乘法
编辑 假币问题
约瑟夫斯问题
·变治
实例简化
表示改变
AVL树
2-3 Tree
霍纳法则计算多项式
补充
问题规约
·贪心
找零钱问题
埃及分数
活动选择问题
背包问题和最优装载
哈夫曼编码
普里姆算法
克鲁斯卡尔算法
迪杰斯特拉算法
多级调度问题
旅行商问题
删s个数,剩下数最小
·动态规划
0-1背包问题
斐波那契数列
矩阵连乘问题
最长公共子序列
编辑距离
弗洛伊德算法
最优二叉查找树
最大子段和
最长上升子序列
·图
强连通分量-kosaraju算法
强连通分量-Tarjan算法
Tarjan算法求关节点、桥
贝尔曼-福特算法
SPFA
·分支限界法
FIFO分支限界检索4-皇后问题状态空间树
FIFO分支限界检索15-迷问题状态空间树
优先队列分支限界法解01-背包
优先队列分支限界解多段图单源最短路
优先队列分支限界解一般图单源最短路
优先队列式分支限界解TSP
·NP理论
简要介绍
根据重要程度,此文章选取了笔者本学期算法课程中的少部分内容,但是由于时间紧迫,不能对每一个地方都展开叙述,而侧重于对知识点的引导,很多地方都会是一笔带过,因此,本文章不适用于算法的0基础学习而非常适用于对算法知识的巩固和复习,大家可以根据自己学校的要求或者是自己的兴趣寻找相关内容的详解。
写这篇文章主要的目的是记录自己第一次系统地学习算法的收获,希望对以后的回顾有所帮助,同时也对即将来临的期末考试添砖加瓦。当然也希望能够稍微帮助到读者复习算法这门课程。
此文章的内容来源大多为老师ppt,所以内容梳理的顺序也将按照ppt的次序,最后在此感谢老师!
·复杂度
时间复杂度取决于待处理数据的状态和问题规模
空间复杂度取决于程序量、数据量、数据结构等
T(n):算法所需要的操作次数
五种算法复杂度的表示:
常见复杂度类型:常量型(constant)、多项式型(polynomial)、对数型(logarithmic)、指数型(exponential)、阶乘型(factorial)
·迭代
迭代的核心是利用变量原值推出新值
循环不变量:在循环的过程中保持不变的性质
插入排序
循环不变量:一些元素已经排好序,剩下的元素没有排序
是稳定排序,是就地排序,时间复杂度:O()
void InsertSort(List a)
{for(int i=1;i<a.length;i++){int j=i;int B=a[i];while(j>0&&a[j-1]>B){a[j]=a[j-1];j--;}a[j]=B;}
}
二分查找
循环不变量:维护一子序列,如果目标在原序列中,那么目标也一定在子序列中
时间复杂度:O(logn)
为了解决边界问题,二分查找形成以下两种模板,假设序列存在重复元素,第一个模板可以返回重复元素的第一个值,第二个可以返回重复元素的第二个值。
//model 1
int find(int x)
{while(l<r){int mid=l+r>>1;if(q[mid]>=x)r=mid;else l=mid+1;}return r;
}
//model2
int find(int x)
{while(l<r){int mid=l+r+1>>1;if(q[mid]<=x)l=mid;r=mid-1;}return r;
}
快排划分
快排有一个很重要的步骤叫做划分,如果用迭代法来设计这个算法,可以先考虑其循环不变量:维护两个指针:i,j,使得A[p...i]<=pivot,A[i+1...j-1]>pivot,A[r]=pivot,pivot为基准,pr是原序列左右边界
时间复杂度为O(n)
partition(A,p,r)
{x=A[r];i=p-1;for(j from p to r-1){if(A[j]<=x){i++;exchange(A[i],A[j]);}}exchange(A[i+1],A[r]);return i+1;
}
选择排序
循环不变量:A [1...j-1]是已排序的元素,且该已排序的元素由数组最小j-1个元素组成
时间复杂度O(),不是稳定排序,是就地排序
for j=1 to A.length-1k=j;for i=j to A.lengthif A[i]<A[j]k=i;temp=A[k];A[k]=A[j];A[j]=temp;
计数排序
循环不变量:已经有i个元素被放在输出数组中正确位置,辅助数据结构表示每一个键下次放置的位置
时间复杂度:O(n),是稳定排序,不是就地排序
基数排序
循环不变量:键已经按照最低i-1位有效数字进行排序
时间复杂度O(n),是稳定排序,不是就地排序
桶排序
循环不变量:前i-1个键已经被正确地放在桶中,且桶中的元素已经排好序
时间复杂度O(n),是稳定排序,不是就地排序
·递归
递归:重复调用函数自身实现循环
迭代递归的比较:前者是自己执行很多次,每次排除一个,直到找到结果,后者是每次循环包含自己的请求,子问题携带原问题,一次次缩小范围,最后找到结果
尾递归:1.在尾部调用自身2.可以通过优化,使得计算仅占用常量栈空间
备忘录方法与递归:
相同点:都是自顶向下
不同点:前者用表格保存已经求解的子问题答案,下次需要时直接查看
递归式的计算-四种方法
方法一:先猜、后证明
可以看到最后一般要给出的是常数c所要满足的要求
方法二:递归树
根据递推式子画出递归树,计算每一层的T(n),求和即为最终结果,方法核心在于计算每一层的T(n)和树的层数,下面这个例子,底部高度不平整时,简单放缩即可:
方法三:迭代法
这种方法是最容易理解也是适用范围最差的,放个例子
方法四:主理论法
先举几个例子:
满足上述第三种情况,所以T(n)=O()
这个实际上也时case3,因为case3中f(n)看的是下界
使用方法四的注意事项:f(n)不能不单调、不能不是多项式、b必须是常数
方法四的特殊情况:
例如:
欧几里得算法
时间复杂度O(logn)
int gcd(a,b)
{return !b?a:gcd(b,a%b);
}
汉诺塔问题
快速排序
时间复杂度O(nlogn),是不稳定排序,是就地排序
void quicksort(int q[],int l,int r)
{if(l>=r)return;int i=l-1,j=r+1,x=q[l+r>>1];while(i<j){do i++;while(q[i]<x);do j--;while(q[j]>x);if(i<j)swap(q[i],q[j]);}quicksort(q,l,j);quicksort(q,j+1,r);
}
归并排序
时间复杂度:O(nlogn),是稳定的排序,是就地排序
void mergesort(int q[],int l,int r)
{if(l>=r)return;int mid=l+r>>1;mergesort(q,l,mid);mergesort(q,mid+1,r);int k=0,i=l,j=mid+1;while(i<=mid&&j<=r){if(q[i]<=q[j])temp[k++]=q[i++];else temp[k++]=q[j++];}while(i<=mid)temp[k++]=q[i++];while(j<=r)temp[k++]=q[j++];for(int i=l,k=0;i<r;i++,k++)q[i]=temp[j];
}
堆排序
down调整
void down(int u)
{int t=u;if(u*2<=cnt&&h[u*2]<h[t])t=u*2;if(u*2+1<=cnt&&h[u*2+1]<h[t])t=u*2+1;if(u!=t){swap(h[u],h[t]);down(t);}
}
up调整:
void up(int u)
{while(u/2&&h[u/2]>h[u]){swap(h[u/2],h[u]);u/=2;}
}
建堆+排序
for(int i=n/2;i;i--)down(i);
while(m--)
{cout<<h[1];h[1]=h[cnt--];down(1);
}
时间复杂度:O(nlogn)
不是稳定的排序但是就地排序
·分治
思想:将原问题分成更小的子问题,递归地解决这些子问题,然后把解决之后的子问题结果合成原问题的答案
二维极大点问题
解决策略:如果S(指的是原点集)只有一个点,以极大点返回,否则,找到一条垂直X轴的线评分点集为Sl,Sr,递归地从Sl,Sr中找最大点,最后,从Sr中找到y坐标最大的点,舍弃Sl中y坐标小于此值的所有点(这一步本质就是子问题答案的合并)
T(n)=O(nlogn)
一维最邻近点对
第一种策略是直接排序,然后做一次遍历
第二种是分治,先求左边和右边,再合并,怎么合并,就是比较左边部分的最右边和右边部分的最左边的距离和左边部分最近距离和右边部分最近距离的大小关系,取最小,复杂度O(nlogn)
二维最邻近点对
分治步骤:1.按y坐标排序2.若只有一个点,返回距离无穷3.(分)方法与二维极大值点分法相同 4.(治)求d=min(dl,dr) 5.(合)对于L-d~L的板上的点p,每一个p找到在L~L+d的半板中y落在yp+d~yp-d中所有点,如果p与这个区域的点距离d’<d,d=d',d最终值即所求
O(nlogn)
逆序对的数目
数逆序对,只需要在归并排序的代码中做一点“手脚”
凸包
方法:若点集S中不多于五个点,穷举得到凸包,找一条垂直于x轴的中线平分S为Sl,Sr,递归地在Sl和Sr中构建凸包,使用合并程序合并Sl,Sr
合并程序
选择一个内部点p,得到几个相对于p具有递增极角的点序列,然后将这3个序列合并为1个序列,最后使用Graham扫描逐个检查这些点,并消除引起自反角的点
Graham扫描举例:
总体时间复杂度O(nlogn)
最大字段和问题
举个栗子:
1 3 -2 -2 1 -2 2 6 -1 4
左边: 最大为4(1+3),右边:最大为8(2+6),合并的时候看中间,先从中间(1和-2之间)往左边直到看见负数停止,只有1,先从中间(1和-2之间)往有边直到看见负数停止,什么也没有,所以合并的时候max3=1
综上max=8
循环赛日程表
n个选手的表,可通过为n/2个选手设计表来决定,n/2......可通过...n/4,....,直到位2个选手设表就简单了,比如a和b比赛设表:
a b
b a
T(n)=T(n/2) + (n/2)*(n/2)------》 T(n)= O()
维诺图程序设计
分治法构造Delaunay三角剖分法
首先对点集按x坐标排序,若点击内点个数<=3,那么可以直接构造,否则拆分,最后合并这两个子点集的角最优三角剖分,求对偶图,即S的·Voronoi图
O(nlogn)
补充
常见的如归并排序、快速排序也在分治范畴之中,但在前面提过,此处没有重复写
·减治
使问题变成与原问题相同的但是规模更小的子问题,解决这个子问题,延伸子问题的解决方法,从而得到原问题的解决方案
种类:
减少一个常量(如插排、拓排、全排列、求子集、dfs、bfs、堆排序)
减少一个常量因子(如二分查找、快速幂、俄罗斯乘法、约瑟夫斯、假币问题)
可变尺寸减治(如欧几里得算法、nim游戏、快排划分)
DFS
DFS(G)
{count=0每个顶点标记为0for each vertex v in V do:if v 标记为0dfs(v)
}
dfs(v)
{count+=1;v标记为countfor each vertex w adjacent to v:if w is marked with 0dfs(w)
}
时间复杂度:邻接矩阵O(v^2)v为顶点数
邻接表 O(v+e) e为边数
BFS
BFS(G)
{count=0每个顶点标记为0for each vertex v in V do:if v 标记为0bfs(v)
}
bfs(v)
{count+=1;v标记为countqueue qq.push(v)while q!=empty:a=front of qfor each vertex w adjacent to a:if w is marked with 0:count+=1marked w with countadd w to the qremove a from q
}
复杂度同DFS
拓扑排序
1.基于dfs:
2.减一法
时间复杂度O(v+e)
bool toposort()
{int hh=0,tt=-1;for(int i=1;i<=n;i++)if(!d[i])q[++tt]=;while(hh<=tt){int t=q[hh++];for each w adjacent to t:if(--d[w]==0)q[++tt]=j;}return tt==n-1;(如果不相同则有环)
}
生成排列(O(n!))
插入法
Johnson-Trotter法
字典生成顺序排列
生成子集(O(2^n))
1.可以直接使用减治法:在n-1规模的集合的所有子集中添加第n个元素
2.也可以使用位运算法,略
淘汰赛冠军问题
n个元素两两分组分别比较,得到n/2个优胜者,对其再分组,得到n/4个优胜者,以此类推,得到冠军
T(n)=n(1-1/2^k)=O(n)
快速幂
俄罗斯农民乘法
假币问题
simplier version:
减半法 减n/3法
complex version:
不知道真假币谁轻
约瑟夫斯问题
例如L(6,2)=2*2+1
·变治
实例简化
预排序可以解决很多问题,比如查看某个元素是否唯一,一个序列中是否有重复元素,模式计算(找到列表中出现次数最多的值)包括topk问题,排序之后这种类型的问题都可以通过一次遍历解决
高斯消元也是一个典型的变治问题,但是这个算法通常复杂度较高(达到O()),在商业中经常使用LU分解
表示改变
AVL树
下面简单说明avl树的构造,主要针对如何进行左旋右旋等问题(这种构造方法主要用于手算的方便)
时间复杂度:O(logn)
第一步:插入5,正常插入
第二步:插入6,正常插入
第三步:插入8,插入之后发现,存在平衡因子的绝对值大于1的结点,此时需要进行调整,下面给出四种调整策略:(可以简单看一眼,看不懂没关系,会有例子的)
1.如果平衡因子绝对值大于1的结点往“导致这个平衡因子绝对值大于1的结点”的路径上是右右,则需要进行左旋(L)调整,调整方法为,从这个“平衡因子绝对值大于1的结点”开始,向“导致这个平衡因子绝对值大于1的结点”的路径数三个点,把这三个点单独拿下来,组成一个小的avl树,然后合理融入原来的avl树中(如何合理融入,下面有例子)
2.如果平衡因子绝对值大于1的结点往“导致这个平衡因子绝对值大于1的结点”的路径上是左左,则需要进行右旋(R)调整,调整方法为,从这个“平衡因子绝对值大于1的结点”开始,向“导致这个平衡因子绝对值大于1的结点”的路径数三个点,把这三个点单独拿下来,组成一个小的avl树,然后合理融入原来的avl树中(如何合理融入,下面有例子)
3.如果平衡因子绝对值大于1的结点往“导致这个平衡因子绝对值大于1的结点”的路径上是左右,则需要进行左右旋(LR)调整,调整方法为,从这个“平衡因子绝对值大于1的结点”开始,向“导致这个平衡因子绝对值大于1的结点”的路径数三个点,把这三个点单独拿下来,组成一个小的avl树,然后合理融入原来的avl树中(如何合理融入,下面有例子)
4.如果平衡因子绝对值大于1的结点往“导致这个平衡因子绝对值大于1的结点”的路径上是右左,则需要进行右左旋(RL)调整,调整方法为,从这个“平衡因子绝对值大于1的结点”开始,向“导致这个平衡因子绝对值大于1的结点”的路径数三个点,把这三个点单独拿下来,组成一个小的avl树,然后合理融入原来的avl树中(如何合理融入,下面有例子)
继续回到第三步的调整中,我们可以发现,刚插入8这个结点时,结点5的平衡因子绝对值是2>1,所以从这个点开始往“导致这个点平衡因子绝对值大于1”的路径上走,哪条路呢?就是从5往右到6,再从6往右到8,因为是“右右”,使用策略一,即左旋(L(5)),然后从5开始,沿着这条路径取3个结点,分别是5、6、8,组成小的avl树,融入原avl树即可,但此时去掉3个结点之后,原avl树为空,所以直接放上去即可。
第四步:插入3,正常插入
第五步:插入2,发现结点6的平衡因子为2,但是结点5的平衡因子也是2,此时选哪个呢?答案是选取最下面的平衡因子绝对值大于1的结点,因为往下走是“左左”,所以右旋(R(5)),取出结点5、3、2,变成小avl树,插入下面即可。
第六步:插入4,此时结点6的平衡因子为2,往“导致结点6的平衡因子绝对值大于1”的路径上看,发现是“左右”,所以进行LR(6),取出6、3、5,变成小avl树之后,合理融入,怎么融入其实很简单,就是把5、3、6取出来之后剩下的结点,重新按avl的规则插入这个小avl树即可
第七步:插入7,做一个RL(6),最后的融入,更简单,因为取出来的结点不包含根节点,所以就直接插入原来的位置即可。
2-3 Tree
简单了解一下插入方式即可
时间复杂度是:O(logn)
跟avl树的插入方式一样,只不过每一个结点可以容纳1~2个结点,当结点数量到达三个时,就会“分裂”成一个小avl树
霍纳法则计算多项式
时间复杂度为O(n)
补充
堆排序、快速幂 也是经典的变治中的“表示改变”类别,但前面已经提过,不再赘述
问题规约
例子一:ab最小公倍数可以转换为求ab的最大公约数,然后用a*b除以这个公约数
例子二:通过将图的相邻矩阵提升到n次方来计算图中长度为n的路径的数量
例子三:将最大化问题转化为最小化问题,反之亦然
例子四:线性规划
例子五:农夫过河问题,转换为在图中找路径的问题,用四维0-1向量表示(人,狼,羊,菜)在河西岸的状态(在河西岸则分量取1,否则取0),共有24 =16 种状态.在河东岸的状态类似
·贪心
贪心算法两个基本的要素:1.最优子结构属性2.贪心选择策略
贪心思想:对问题求解时,总是做出当前看来是最好的选择,不从整体最优加以考虑,得到的是某种意义上的局部最优解
贪心算法优势:容易编写
贪心算法劣势:难以证明准确性、不能总是保证全局最优
找零钱问题
贪心策略:先抓25美分硬币,直到超过金额,然后是1美分硬币,然后是5美分硬币,再是1美分,即尽量从大的开始选择
失败例子:
find minimum quantity of 4,3,1 to make up 6 cents:
4+1+1 错误
3+3 正确
贪心选择何时可以得到整体最优解?当可换硬币单位是C的幂(C为正整数)
埃及分数
举个例子:7/8
第一轮循环A=7 B=8
E=2 则第一次减去的是1/2
分数变成3/8
第二次E=3 则第二次减去的是1/3
分数变成1/24
因为A==1,所以结束循环
综上:7/8=1/2+1/3+1/24
活动选择问题
贪心策略:选择最早结束的:
背包问题和最优装载
对于分数背包问题,直接按性价比优先策略,即可满足整体最优O(nlogn)
但是对于0-1背包,按上述策略,不能保证全局最优
最优装载:
此处仅涉及一个属性,即重量,所以贪心策略:优先装填重量小的,可以实现全局最优
哈夫曼编码
时间复杂度O(nlogn)
信息熵、编码效率、编码冗余度计算举例
哈夫曼树的构造已经在数据结构和离散数学中很熟悉了,此处就不说了
普里姆算法
O()
int prim()
{memset(dist,0x3f,sizeof dist);int res=0;for(int i=0;i<n;i++){int t=-1;for(int j=1;j<=n;j++){if(!st[j]&&(t==-1||dist[t]>dist[j]))t=j;}if(i&&dist[t]==INF)return INF;if(i)res+=dist[t];st[t]=true;for(int j=1;j<=n;j++)dist[j]=min(dist[j],g[t][j]);}return res;
}
克鲁斯卡尔算法
时间复杂度:O(ElogE)
使用了并查集的知识
int kruskal()
{从小到大按边的权值排序for(int i=1;i<=n;i++)p[i]=i;//并查集 int res=0;int cnt=0;for(int i=0;i<m;i++){a=第i条边的左顶点b=第i条边的右顶点w=第i条边的权值 a=find(a);b=find(b);if(a!=b){p[a]=b;res+=w;cnt++;}} if(cnt<n-1)return INF;return res;
}
迪杰斯特拉算法
时间复杂度O()
int dijkstra()
{memset(dist,0x3f,sizeof dist);dist[1]=0;for(int i=1;i<n;i++){int t=-1;for(int j=1;j<=n;j++){if(!st[j]&&(t==-1||dist[t]>dist[j]))t=j;}for(int j=1;j<=n;j=+dist[j]=min(dist[j],dist[t]+g[t][j]);st[t]=true;}if(dist[n]==0x3f3f3f3f)return -1;else return dist[n];
}
多级调度问题
这是一个np完全问题
策略是:最长时间优先,但是它不能保证整体最优
设能同时运行的进程数为m,总共有n个进程
当n<=m时,O(1)
当n>m时,O(nlogn)
旅行商问题
有两种策略
第一钟是最近邻点策略,时间复杂度O()
第二种是最短链接策略,时间复杂度O(nlogn)
删s个数,剩下数最小
题意为:有一个高精度的正整数,去掉其中的s个数字之后,剩下的数字组成的新数最小:
贪心策略:每一次删除首个上升子序列的末字符,这种策略的正确性可以用反证法轻松证得
·动态规划
思想:将待求问题分解成n个阶段,按顺序求解子阶段,前一子问题的解为后一子问题的求解提供了有用的信息
动态规划和分治:
相同点:原问题分解成子问题,先求解子问题,从中得到原问题的解
不同点:1.动态规划中的子问题不是相互独立的,分治法中的子问题相互独立
2.动态规划碰到相同的子问题只需要查询答案,而分治法会重新求解,效率更低
动态规划和贪心:
相同点:都是求解最优化问题,都有最优子结构性质(原问题的最优解包含子问题最优解)
不同点:1.动态规划是自底向上,贪心是自顶向下
2.动态规划依赖于各子问题的解,只有在解出相关子问题之后,才能做出选择,而贪心法不依赖于子问题的解,仅在当前状态下做出最好选择
何时备忘录?何时动态规划?
所有子问题都需要至少求一次:动规 else 备忘录
0-1背包问题
0-1背包问题是一个np-hard问题,但是不是一个npc问题
蛮力法:O()
贪心:无法求得最优解
动规:O(nw),n为物品个数,w为背包容量
递归式:其中B[K,W]是从前K个物品中选择,W是在容量为W的情况下总价值,bk是第k个物品的价值
给出代码:
for(int i=1;i<=n;i++)
{for(int j=1;j<=m;j++){f[i][j]=f[i-1][j];if(j>=v[i])f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);}
}
空间复杂度优化为一维:
for(int i=1;i<=n;i++)
{for(int j=v[i];j>=0;j--){f[j]=max(f[j],f[j-v[i]+w[i]);}
}
斐波那契数列
for i=2 to n doA[i]=A[i-1]+A[i-2]
return A[n]
矩阵连乘问题
m[i,j]表示从矩阵i开始一直连乘到矩阵j所需要的乘法次数,比如a1:2*5矩阵
a2:5*3矩阵,a3:3*8矩阵则m[1,3]=2*5*3*8=240
矩阵连乘问题就是在矩阵之间添加括号确定乘法的次序(因为矩阵乘法满足结合律,不满足交换律)从而使得m值最小,下面是递推关系,易知i==j时,m[i,j]==0
填表的次序与0-1背包不同,如下所示
答案是位置是在第一行的最后一列处
由S数组确定加括号的位置
最长公共子序列
蛮力法:O(n)
递推式:
递推式很好理解,在填表的时候,首先初始化数组行列下标为0的值都为0,如果对应位置的字符相同,那么就把左上角的值加一移到右下角,否则就填入这个位置的上边和左边最大的一个值:
得到这个值之后,如何找到“the actual longest common sequence”?
可以从表的最右下角向左上角反推,当反推到行列有一个下标为0时,把顺序倒置即可得到所求序列,但有时候可以存在多个lcs,这时候再向上推的时候,优先向上面和左边走,就能产生分叉,如下图所示。
编辑距离
时间复杂度O()
先初始化,再根据递推式填表即可,很好理解
弗洛伊德算法
求多元最短路,时间复杂度为O()
void floyd()
{for(int k=1;k<=n;k++){for(int i=1;i<=n;i++){for(int j=1;j<=n;j++)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);}}
}
最优二叉查找树
可以发现,这个的递推式与矩阵连乘很像,不同之处在于矩阵的初始化,和k的取值,在这里k可以取到j,而且第一个是C【i,k-1】而不是C【i,k】,但是填表顺序与之相同
最大子段和
如图递归式
最长上升子序列
f[i]:所有以第i个数结尾的上升子序列长度
f[i]=max(f[j]+1) j=0,1,2...i-1
for(int i=1;i<=n;i++)
{f[i]=1;for(int j=1;j<i;j++){if(a[j]<a[i])f[i]=max(f[j]+1,f[i]);}
}
·图
图的知识非常繁多,常见的有图的遍历、最短路、最小生成树、拓扑排序、连通分量、传递闭包、网络流、二分图、图着色问题
但是一方面像生成树、最短路、遍历前面已经说过,另一方面很多对于期末考试不是那么重要,在这一节仅补充部分图论知识
强连通分量-kosaraju算法
时间复杂度O(V+E)
强连通分量-Tarjan算法
时间复杂度O(V+E)
涉及两个数组:
dfn[i]:编号为i的结点在DFS的过程中的访问序号
low[i]:dfs过程中i的下方结点所能到达的开始时间最早的结点的开始结点
dfs遍历中:当u能达到v
if v不在栈中:low[u]=min{low[u],low[v]}
否则:low[u]=min{low[u],dfn[v]}
Tarjan算法求关节点、桥
贝尔曼-福特算法
时间复杂度:O(V*E)
拓展:
1.如果把循环次数变为k,那么最终得到的结果是从1号点到n号点最多经过k条边的距离
2.当循环了n-1轮之后,再检查一次边发现此时变还可以更新,说明一个问题:此图有负权环!!
SPFA
·分支限界法
根据选择下一个扩展节点不同分为FIFO搜索、LIFO搜索、优先队列搜索
两种解空间树:子集树、排列树
分支限界法和回溯法的区别
FIFO分支限界检索4-皇后问题状态空间树
FIFO分支限界检索15-迷问题状态空间树
优先队列分支限界法解01-背包
优先队列分支限界解多段图单源最短路
优先队列分支限界解一般图单源最短路
看这张图,注意什么时候不能入堆,其余步骤省略,只放最后的结果
dijkstra算法和分支限界法在解决该问题的异同:
dijkstra算法:每一步的选择为当前步的最优,复杂度为O(n^2)
分支限界:每一步的扩散为当前耗散度的最优
优先队列式分支限界解TSP
·NP理论
本章介绍一些概念
P:是指能在多项式时间内解决的问题
NP:是指非确定性多项式问题。不能在多项式时间内解决或不确定能不能在多项式时间内解决,但能在多项式时间复杂度内被验证的问题;TSP,背包,图着色
NP-Complete(NPC):(1)它是一个NP问题,(2)所有NP问题都可以约化到它。
既然所有的NP问题都能约化成NPC问题,那么只要任意一个NPC问题找到了一个多项式算法,那么所有的NP问题都能用这个算法解决了,NP也就等于P了。(证明:假设NPC问题A在多项式时间内是可解的,并给定一个NP问题B。由于 A 是 NPC,因此 B 的任何实例都可以在多项式时间内转换为 A 的“等效”实例。然后可以通过假设在多项式时间内求解 A 的实例。组合这两种算法会产生多项式时间B的算法。),TSP
NP-Hard(NPH):NP难问题。所有NP问题在多项式时间内都能约化(Reducibility)到它的问题(不一定是NP问题),可以理解为,NP-Hard是比所有NP问题都难的问题。非对称TSP