目录
今天也是为了cc,努力奋斗的一天ヾ(≧▽≦*)o
貌似保研面试的时候会问一些这样的问题。。。那我就整理一下吧。反正刷《算法笔记》的时候看见时间复杂度就记到这里呗。
图
Bellman-Ford
时间复杂度
O(VE)
,其中V是顶点个数,E是边数。
关键代码
for(i = 0;i < n - 1;i++){ //执行n-1轮操作,其中n为顶点数
for(each edge u->v){ //每轮操作都遍历所有边
if(d[u] + length[u -> v] < d[v]){ //以u为中介点可以使d[v]更小
d[v] = d[u] + length[u -> v]; //松弛操作
}
}
}
SPFA
时间复杂度
O(kE)
,其中E是图的边数,k是一个常数,在很多情况下k不超过2,可见这个算法在大部分数据时异常高效,并且经常性地优于堆优化的Dijkstra算法。但是如果图中有从源点可达的负环时,传统SPFA的时间复杂度会退化成O(VE)
。
关键代码
//主体部分
while(!Q.empty()){
int u = Q.front(); //队首顶点编号为u
Q.pop(); //出队
inq[u] = false; //设置u为不在队列中
//遍历u的所有邻接边v
for(int j=0;j<Adj[u].size();j++){
int v = Adj[u][j].v;
int dis = Adj[u][j].dis;
//松弛操作
if(d[u] + dis < d[v]){
d[v] = d[u] + dis;
if(!inq[v]){ //如果v不在队列中
Q.push(v); //v入队
inq[v] = true; //设置v为在队列中
num[v++]; //v的入队次数加一
if(num[v] >= n){
return false; //有可达负环
}
}
}
}
}
Floyd
时间复杂度
O(n*n*n)
,其中n是顶点的个数。
关键代码
void Floyd(){
for(int k=0;k<n;k++){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(dis[i][k] != INF && dis[k][j] != INF && dis[i][k] + dis[k][j] < dis[i][j]){
dis[i][j] = dis[i][k] + dis[k][j]; //找到更短的路径
}
}
}
}
}
prim
时间复杂度
O(V*V)
,其中邻接表实现prim算法可以通过堆优化使时间复杂度降为
。
此外,
的时间复杂度也说明,尽量在图的顶点数目较少而边数较多的情况下(即稠密图)上使用prim算法。至于为什么prim算法得到的生成树一定是最小生成树,可以参考《算法导论》的相关证明。
关键代码
int prim(){ //默认0号为初始点,函数返回最小生成树的边权之和
fill(d,d+MAXV,INF); //fill函数将整个d数组赋值为INF(慎用memset)
d[0] = 0; //只有0号顶点到集合S的距离为0,其余为INF
int ans = 0; //存放最小生成树的边权之和
for(int i=0;i<n;i++){ //循环n次
int u = -1,MIN = INF; //u使d[u]最小,MIN存放该最小的d[u]
for(int j=0;j<n;j++){ //找到未访问的顶点中d[]最小的
if(vis[j] == false && d[j] < MIN){
u = j;
MIN = d[j];
}
}
//找不到小于INF的d[u],则剩下的顶点和集合S不连通
if(u == -1){
return -1;
}
vis[u] = true; //标记u为已访问
ans += d[u]; //将与集合S距离最小的边加入到最小生成树
for(int v=0;v<n;v++){
//v未访问 && u能到达v && 以u为中介点可以使v离集合S更加近
if(vis[v] == false && G[u][v] != INF && G[u][v] < d[v]){
d[v] = G[u][v]; //将G[u][v]赋值给d[v]
}
}
}
}
kruskal
时间复杂度
,
为图的边数。
显然kruskal适合顶点数较多、边数较少的情况,这和prim算法恰恰相反。于是可以根据题目所给的数据范围来选择合适的算法,即如果是稠密图(边多),则用prim算法;如果是稀疏图(边少),则用kruskal算法。
关键代码
int father[N]; //并查集数组
int findFather(int x){ //并查集查询函数
...
}
//kruskal函数返回最小生成树的边权之和,参数n为顶点个数,m为图的边数
int kruskal(int n,int m){
//ans为所求边权之和,Num_Edge为当前生成树的边数
int ans = 0;
int Num_Edge = 0;
for(int i=1;i <= n;i++){ //假设题目中顶点范围是[1,n]
father[i] = i; //并查集初始化
}
sort(E,E+m,cmp); //所有边按边权从小到大排序
for(int i=0;i<m;i++){ //枚举所有边
int faU = findFather(E[i].u); //查询测试边两个端点所在集合的根结点
int faV = findFather(E[i].v);
if(faU != faV){ //如果不在一个集合中
father[faU] = faV; //合并集合(即将测试边加入到最小生成树中)
ans += E[i].cost; //边权之和增加测试边的边权
Num_Edge++; //当前生成树的边数加1
if(Num_Edge == n - 1){
break; //边数等于顶点数减1时结束算法
}
}
}
if(Num_Edge != n-1){
return -1; //无法连通时返回-1
}else{
return ans; //返回最小生成树的边权之和
}
}