这几天一直在看图论的东西,什么Dijkstra,Floyd,Prim,Kruskal等等的东西吧,之前总感觉这些东西好难,也就没怎么看,这段时间接触多了,每天都看那么一丢丢,感觉真的也就那么回事,什么东西都是要一点一点去学,慢慢的去掌握的不是么,所以看了好几天的东西,今天想花点时间去总结一次,就当做省赛前的小放松了,哈哈,其实平常也挺放松的.......好了,进入正题吧!
Dijkstra算法
Dijkstra算法是典型的 单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是 以起点为中心向外层扩展,直到扩展到终点为止。特别注意的是 Dijkstra算法要求图中不存在负权边。
Dijkstra单源最短路 邻接矩阵形式
/*
* 单源最短路径,Dijkstra算法,邻接矩阵形式,复杂度为O(n^2)
* 求出源beg到所有点的最短路径,传入图的顶点数和邻接矩阵cost[][]
* 返回各点的最短路径lowcost[],路径pre[],pre[i]记录beg到i路径上的父节点,pre[beg] = -1
* 可更改路径权类型,但是权值必须为非负,下标0~n-1
*/
const int MAXN = 1010;
const int INF = 0x3f3f3f3f; // 表示无穷
bool vis[MAXN];
int pre[MAXN];
void Dijkstra(int cost[][MAXN], int lowcost[], int n, int beg)
{
for (int i = 0; i < n; i++)
{
lowcost[i] = INF;
vis[i] = false;
pre[i] = -1;
}
lowcost[beg] = 0;
for (int j = 0; j < n; j++)
{
int k = -1;
int min = INF;
for (int i = 0; i < n; i++)
{
if (!vis[i] && lowcost[i] < min)
{
min = lowcost[i];
k = i;
}
}
if (k == -1)
{
break;
}
vis[k] = true;
for (int i = 0; i < n; i++)
{
if (!vis[i] && lowcost[k] + cost[k][i] < lowcost[i])
{
lowcost[i] = lowcost[k] + cost[k][i];
pre[i] = k;
}
}
}
}
Dijkstra 单源最短路 邻接矩阵形式 双路径信息
/*
*单源最短路径,dijkstra算法,邻接矩阵形式,复杂度为o(n^2)
*两点间距离存入map[][],两点间花费存入cost[][]
*求出源st到所有点的最短路径及其对应最小花费
*返回各点的最短路径lowdis[]以及对应的最小花费lowval[]
*可更改路径权类型,但是权值必须为非负,下标1~n
*/
const int MAXN=1010;
const int INF=0x3f3f3f3f;
int n,m;
int lowdis[MAXN];
int lowval[MAXN];
int visit[MAXN];
int map[MAXN][MAXN];
int cost[MAXN][MAXN];
void dijkstra(int st)
{
int temp=0;
for(int i=1;i<=n;i++)
{
lowdis[i]=map[st][i];
lowval[i]=cost[st][i];
}
memset(visit,0,sizeof(visit));
visit[st]=1;
for(int i=1;i<n;i++)
{
int MIN=INF;
for(int j=1;j<=n;j++)
{
if(!visit[j]&&lowdis[j]<MIN)
{
temp=j;
MIN=lowdis[j];
}
}
visit[temp]=1;
for(int j=1;j<=n;j++)
{
if(!visit[j]&&map[temp][j]<INF)
{
if(lowdis[j]>lowdis[temp]+map[temp][j])
{
lowdis[j]=lowdis[temp]+map[temp][j];
lowval[j]=lowval[temp]+cost[temp][j];
}
else if(lowdis[j]==lowdis[temp]+map[temp[j])
{
if(lowval[j]>lowval[temp]+cost[temp][j])
{
lowval[j]=lowval[temp]+cost[temp][j];
}
}
}
}
}
return;
}
Dijkstra起点Strat结点有权值
#define M 505
const int inf=0x3f3f3f3f;
int num[M]; //结点权值
int map[M][M]; //图的临近矩阵
int vis[M]; //结点是否处理过
int ans[M]; //最短路径结点权值和
int dis[M]; //各点最短路径花费
int n,m,Start,End; //n结点数,m边数,Start起点,End 终点
void Dij(int v)
{
ans[v]=num[v];
memset(vis,0,sizeof(vis));
for(int i=0;i<n;i++)
{
if(map[v][i]<inf)
{
ans[i]=ans[v]+num[i];
}
dis[i]=map[v][i];
}
dis[v]=0;
vis[v]=1;
for(int i=1;i<n;i++)
{
int u=0,min=inf;
for(int j=0;j<n;j++)
{
if(!vis[j]&&dis[j]<min)
{
min=dis[j];
u=j;
}
}
vis[u]=1;
for(int k=0;k<n;k++)
{
if(!vis[k]&&dis[k]>map[u][k]+dis[u])
{
dis[k]=map[u][k]+dis[u];
ans[k]=ans[u]+num[k];
}
}
for(int k=0;k<n;k++)
{
if(dis[k]==map[u][k]+dis[u])
{
ans[k]=max(ans[k],ans[u]+num[k]);
}
}
}
printf("%d %d\n",dis[End],ans[End]);//输出终点最短路径花费、最短路径结点权值和
}
int main()
{
scanf("%d%d%d%d",&n,&m,&Strat,&End);
for(int i=0;i<n;i++)
scanf("%d",&num[i]);
memset(vis,0,sizeof(vis));
memset(map,0x3f,sizeof(map));
for(int i=0;i<m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(map[x][y]>z)
{
map[x][y]=z;
map[y][x]=z;
}
}
Dij(Start);
return 0;
}
Floyd算法
Floyd-Warshallsu算法是解决任意两点间的最短路径的一种算法,可以处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。
算法描述:
a)从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有相连,则权为无穷大。
b)对于每一对定点u和v,看看是否存在一个顶点w使得从u到w再到v比已知的路径更短.如果是,那么就更新它.
Floyd算法 邻接矩阵形式
/*
*Floyd算法,求从任意节点i到任意节点j的最短路径
*cost[][]:初始化为INF(cost[i][i]:初始化为0)
*lowcost[][]:最短路径,path[][]:最短路径(无限制)
*/
const int MAXN=100;
int cost[MAXN][MAXN];
int lowcost[MAXN][MAXN];
itn path[MAXN][MAXN];
void Floyd(int n)
{
memcpy(lowcost,cost,sizeof(cost));
memset(path,-1,sizeof(path));
for(int k=0;k<n;k++)
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
if(lowcost[i][j]>(lowcost[i][k]+lowcost[k][j]))
{
lowcost[i][j]=lowcost[i][k]+lowcost[k][j];
path[i][j]=k;
}
}
}
Floyd算法 点权 + 路径限制
/*Floyd算法,求任意节点i到任意节点j的最短路径
*cost[][]:初始化为INF(cost[i][i]:初始化为0)
*val[]:点权,lowcost[][]:除起点,终点外的点权之和+最短路径
*path[][]:路径限制,要求字典序最小的路径,下表1~N
*/
const int MAXN=110;
const int INF=0x3f3f3f3f;
int val[MAXN]; //点权
int cost[MAXN][MAXN];
int lowcost[MAXN][MAXN];
int path[MAXN][MAXN]; //i~j路径中第一个结点
void Floyd(int n)
{
memccpy(lowcost,cost,sizeof(cost));
for(itn i=0;i<=n;i++)
{
for(int j=0;j<=n;j++)
{
path[i][j]=j;
}
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int temp=lowcost[i][k]+lowcost[k][j]+val[k];
if(lowcost[i][j]>temp)
{
lowcost[i][j]=temp;
path[i][j]=path[i][k];
}
else if(lowcost[i][j]==temp&&path[i][j]>path[i][k])
path[i][j]=path[i][k];
}
return;
}
Prim算法
prim算法:每次选取一条边,该边满足:1.一端已选,一端未选;2.该边权值最小。
算法描述:Prim只与定点有关,求最小生成树的时候,和边数无关,只和定点的数量相关,所以适合求稠密网的最小生成树
/*
* Prim求MST
* 耗费矩阵cost[][],初始化为INF,标号从0开始,0 ~ n-1
* 返回最小生成树的权值,返回-1表示原图不连通
*/
const int INF = 0x3f3f3f3f;
const int MAXN = 110;
bool vis[MAXN];
int lowc[MAXN];
int cost[MAXN][MAXN];
// 修正cost(添加边)
void updata(int x, int y, int v)
{
cost[x - 1][y - 1] = v;
cost[y - 1][x - 1] = v;
return ;
}
int Prim(int cost[][MAXN], int n) // 0 ~ n - 1
{
int ans = 0;
memset(vis, false, sizeof(vis));
vis[0] = true;
for (int i = 1; i < n; i++)
{
lowc[i] = cost[0][i];
}
for (int i = 1; i < n; i++)
{
int minc = INF;
int p = -1;
for (int j = 0; j < n; j++)
{
if (!vis[j] && minc > lowc[j])
{
minc = lowc[j];
p = j;
}
}
if (minc == INF)
{
return -1; // 原图不连通
}
ans += minc;
vis[p] = true;
for (int j = 0; j < n; j++)
{
if (!vis[j] && lowc[j] > cost[p][j])
{
lowc[j] = cost[p][j];
}
}
}
return ans;
}
Kruskal算法
在带权连通图中,不断地在边集合中找到最小的边,如果该边满足得到最小生成树的条件,就将其构造,直到最后得到一颗最小生成树。
克鲁斯卡尔算法的执行步骤:
第一步:在带权连通图中,将边的权值排序;
第二步:判断是否需要选择这条边(此时图中的边已按权值从小到大排好序)。判断的依据是边的两个顶点是否已连通,如果连通则继续下一条;如果不连通,那么就选择使其连通。
第三步:循环第二步,直到图中所有的顶点都在同一个连通分量中,即得到最小生成树。
/*
* Kruskal算法求MST
* 对边操作,并排序
* 切记:初始化赋值问题(tol)
*/
const int MAXN = 110; // 最大点数
const int MAXM = 10000; // 最大边数
int F[MAXN]; // 并查集使用
struct Edge
{
int u; // 起点
int v; // 终点
int w; // 权值
} edge[MAXM]; // 存储边的信息
int tol; // 边数,加边前赋值为0
void addEdge(int u, int v, int w)
{
edge[tol].u = u;
edge[tol].v = v;
edge[tol++].w = w;
return ;
}
bool cmp(Edge a, Edge b)
{
// 排序函数,将边按照权值从小到大排序
return a.w < b.w;
}
int find(int x)
{
if (F[x] == x)
{
return x;
}
else
{
return F[x] = find(F[x]);
}
}
int Kruskal(int n) // 传入点数,返回最小生成树的权值,如果不连通则返回-1
{
for (int i = 0; i <= n; i++)
{
F[i] = i;
}
sort(edge, edge + tol, cmp);
int cnt = 0; // 计算加入的边数
int ans = 0;
for (int i = 0; i < tol; i++)
{
int u = edge[i].u;
int v = edge[i].v;
int w = edge[i].w;
int tOne = find(u);
int tTwo = find(v);
if (tOne != tTwo)
{
ans += w;
F[tOne] = tTwo;
cnt++;
}
if (cnt == n - 1)
{
break;
}
}
if (cnt < n - 1)
{
return -1; // 不连通
}
else
{
return ans;
}
}