图论-1主要讲了一下内容:
1.单源最短路径dij,多源最短路径floyd,负权图spfa
2.最小生成树 Prime,Kruskal
3.二分图,最小点覆盖,最大独立集
4.割点,割边。
下面就来详细写一下个部分内容,以免混淆。
一. 最短路径
1.首先是单源最短路径(无负权)的Dijskatra算法,该算法是基于贪心思想的,算法思想如下:
我们定义一个点集S,距离dis[]。点集S初始只有源点,dis[]初始化成源点到其他节点的距离。算法思想就是每次找到未加入S中的dis[]中的最小值,将其加入S中同时维护dis[]数组。就相当于把加入S点集中的点缩成一个点,就有相当于源点是的情况啦。这就是他的最有子结构与贪心策略。
Step1:初始化dis[]数组,源点加入S
Step2:查询不在S中且dis[]最小的
Step3:根据新加入的点更新dis
Step4:如果还有未加入S中的点,鸡血Step2.
下面给出邻接表的实现
#define INF 0xffffffff
const int MAXN=1000; ///MAXN是节点的数目
typedef struct{
int to,cost;
}node;
vector Grap[MAXN]; ///Grap[i]中存储的是i的邻接表。
int dis[MAXN];
bool vis[MAXN];
void Dij(int s){///源点是s
memset(vis,0,sizeof(vis));
for(int i=0;idis[j]&&!vis[j]){
pos=j;
Min=dis[j];
}
}
vis[pos]=1;
for(int j=0;j
Floyd-Warshall算法的原理是动态规划。
设Di,j,k为从i到j的只以(1..k)集合中的节点为中间节点的最短路径的长度。
因此,Di,j,k = min(Di,k,k − 1 + Dk,j,k − 1,Di,j,k − 1)。
在实际算法中,为了节约空间,可以直接在原来空间上进行迭代,这样空间可降至二维。(见下面的算法描述)
Floyd-Warshall算法的描述如下:
for k ← 1 to n do for i ← 1 to n do for j ← 1 to n do if (Di,k + Dk,j < Di,j) then Di,j ← Di,k + Dk,j;
其中Di,j表示由点i到点j的代价,当Di,j为 ∞ 表示两点之间没有任何连接。
下面是代码实现:
for ( int k = 0 ; k < n ; k++ )
for ( int i = 0 ; i < n ; i++ )
for ( int j = 0 ; j < n ; j++ )
dis[i][j] = min ( dis[i][j] , dis[i][k] + dis[k][j] );
3.对负权图的单源最短路径SPFA
int spfa_bfs(int s)
{
queue q;
memset(d,0x3f,sizeof(d));
d[s]=0;
memset(c,0,sizeof(c));
memset(vis,0,sizeof(vis));
q.push(s); vis[s]=1; c[s]=1;
//顶点入队vis要做标记,另外要统计顶点的入队次数
int OK=1;
while(!q.empty())
{
int x;
x=q.front(); q.pop(); vis[x]=0;
//队头元素出队,并且消除标记
for(int k=f[x]; k!=0; k=nnext[k]) //遍历顶点x的邻接表
{
int y=v[k];
if( d[x]+w[k] < d[y])
{
d[y]=d[x]+w[k]; //松弛
if(!vis[y]) //顶点y不在队内
{
vis[y]=1; //标记
c[y]++; //统计次数
q.push(y); //入队
if(c[y]>NN) //超过入队次数上限,说明有负环
return OK=0;
}
}
}
}
return OK;
}
二.最小生成树
1.最小生成树的Prime算法
Prime算法也是基于贪心的思想,和dij很想,不过不同的是dis维护的是做过的路径长度最短,Prime算法维护的是加入的单个边的长度最短。
2最小生成树的Kruskal算法
Kruskal相当于贪心加上并查集的一种实现
对边的长度进行排序,然后依次检索每一条边,如果当前检索的边不在同一个连通分量里,就把这条边加入,将他们合并在一起,依次进行这个操作。
三.二分图