迪杰斯特拉算法
算法概念
迪杰斯特拉算法用于查找图中某个顶点到其它所有顶点的最短路径。
但是有一个点,就是用迪杰斯特拉算法时所有边的权值为非负数
如何实现算法
算法流程图
算法详解
我们讲解一下哈这个是一个D–A的遍历
我们要记住要使用两个矩阵来存储,一个用啦存储点是否使用过,一个用来计算边是否使用过
首先我们以定点D为初始点,接下来D是不是有两个连接点也就是C,E,我们比较CE的3的带权值也就是3最小。
C又和F、D、E三个点连接,但是D点是已经用过了,接下来就不用了,我们选取E点,因为我们选最短的嘛。
可是这里有人就问了,到F点不是D–E–F更短么,这里我们接下来就会比较一次因为DE是有连接这里我们会把已经走过的C–D–E进行比较,如果是CDE小那就按照原来的路走,反过来就按DE走并且记录下到C的最小距离是3,然后接下来选取DE路径E点与F、G连接,选最短的F。
这里我们就已经遍历了4个点,并且得到了到这四个电最短的路径了,然后我们看F连接了剩下的3个点,我们选取最短的,这里图上有误应该是先选取B点。
我们直接看图上的第六部分,这里我们会发现DCB是13,DEFB也是13,我们来讲讲这里,我们还是先按照已经选定的最短路径来选,但是我们又发现BC是有一条路径的,我们就会在这里计算这条路径是否使用过,没有那么我们就用他来加上之前已经选过的最短的D到C的路径相加比较,从而得到最短的。
然后我们在看看B连接的点只有A了,所以我们就先计算这条路径,但是我们发现还有D点以及AF、AG、FG,CE还没遍历到,这里我们还是先继续选择路径也就是A-G,并且计算值,这里就剩下边AF和FG、CE没有使用了,在这我们是不是发现两个都有F点啊,所以我们就又以公共点为初始点开始重新计算得到DEFA小于DEFB,于是更新刚社数据,然后同时也计算F到G的距离并且用上边FG,得到一条更短的,更新数据,再用上最后一条,也就是EG得到最短的。这个图就遍历完了。
总结就是,先遍历点选最短的路径,然后标记边和点,最后遍历没有用上的边寻找最短的路径。
代码如下
#include <stdio.h>
#define V 20 //顶点的最大个数
#define INFINITY 65535
typedef struct {
int vexs[V]; //存储图中顶点数据
int arcs[V][V]; //二维数组,记录顶点之间的关系
int vexnum, arcnum; //记录图的顶点数和弧(边)数
}MGraph;
//根据顶点本身数据,判断出顶点在二维数组中的位置
int LocateVex(MGraph * G, int v) {
int i = 0;
//遍历一维数组,找到变量v
for (; i < G->vexnum; i++) {
if (G->vexs[i] == v) {
break;
}
}
//如果找不到,输出提示语句,返回-1
if (i > G->vexnum) {
printf("no such vertex.\n");
return -1;
}
return i;
}
//构造无向有权图
void CreateDG(MGraph *G) {
printf("输入图的顶点数和边数:");
scanf("%d %d", &(G->vexnum), &(G->arcnum));
printf("输入各个顶点:");
for (int i = 0; i < G->vexnum; i++) {
scanf("%d", &(G->vexs[i]));
}
for (int i = 0; i < G->vexnum; i++) {
for (int j = 0; j < G->vexnum; j++) {
G->arcs[i][j] = INFINITY;
}
}
printf("输入各个边的数据:\n");
for (int i = 0; i < G->arcnum; i++) {
int v1, v2, w;
scanf("%d %d %d", &v1, &v2, &w);
int n = LocateVex(G, v1);
int m = LocateVex(G, v2);
if (m == -1 || n == -1) {
return;
}
G->arcs[n][m] = w;
G->arcs[m][n] = w;
}
}
//迪杰斯特拉算法,v0表示有向网中起始点所在数组中的下标
void Dijkstra_minTree(MGraph G, int v0, int p[V], int D[V]) {
int final[V];//为各个顶点配置一个标记值,用于确认该顶点是否已经找到最短路径
//对各数组进行初始化
for (int v = 0; v < G.vexnum; v++) {
final[v] = 0;
D[v] = G.arcs[v0][v];
p[v] = 0;
}
//由于以v0位下标的顶点为起始点,所以不用再判断
D[v0] = 0;
final[v0] = 1;
int k = 0;
for (int i = 0; i < G.vexnum; i++) {
int min = INFINITY;
//选择到各顶点权值最小的顶点,即为本次能确定最短路径的顶点
for (int w = 0; w < G.vexnum; w++) {
if (!final[w]) {
if (D[w] < min) {
k = w;
min = D[w];
}
}
}
//设置该顶点的标志位为1,避免下次重复判断
final[k] = 1;
//对v0到各顶点的权值进行更新
for (int w = 0; w < G.vexnum; w++) {
if (!final[w] && (min + G.arcs[k][w] < D[w])) {
D[w] = min + G.arcs[k][w];
p[w] = k;//记录各个最短路径上存在的顶点
}
}
}
}
int main() {
MGraph G;
CreateDG(&G);
int P[V] = { 0 }; // 记录顶点 0 到各个顶点的最短的路径
int D[V] = { 0 }; // 记录顶点 0 到各个顶点的总权值
Dijkstra_minTree(G, 0, P, D);
printf("最短路径为:\n");
for (int i = 1; i < G.vexnum; i++) {
printf("%d - %d的最短路径中的顶点有:", i, 0);
printf(" %d-", i);
int j = i;
//由于每一段最短路径上都记录着经过的顶点,所以采用嵌套的方式输出即可得到各个最短路径上的所有顶点
while (P[j] != 0) {
printf("%d-", P[j]);
j = P[j];
}
printf("0\n");
}
printf("源点到各顶点的最短路径长度为:\n");
for (int i = 1; i < G.vexnum; i++) {
printf("%d - %d : %d \n", G.vexs[0], G.vexs[i], D[i]);
}
return 0;
}
弗洛伊德算法
算法概念
弗洛伊德算法定义了两个二维矩阵:
矩阵D记录顶点间的最小路径
例如D[0][3]= 10,说明顶点0 到 3 的最短路径为10;
矩阵P记录顶点间最小路径中的中转点
例如P[0][3]= 1 说明,0 到 3的最短路径轨迹为:0 -> 1 -> 3。
它通过3重循环,k为中转点,v为起点,w为终点,循环比较D[v][w] 和 D[v][k] + D[k][w] 最小值,如果D[v][k] + D[k][w] 为更小值,则把D[v][k] + D[k][w] 覆盖保存在D[v][w]中。
算法讲解
顶点名称和下标的对应
A B C D E F G
0 1 2 3 4 5 6
第2步:
以A为中间点,原D矩阵中,D[B][G]的值为INF,即不存在B->G的最小路径,但是通过A为中间点,D[B][A] + D[A][G] = 12 + 14 = 26 小于 D[B][G] = INF, 所以D[B][A] + D[A][G] 为 B -> G的最小值,因此覆盖D[B][G] 为 26。
第3步:
以B为中间点,第2步后的D矩阵中,D[A][C]的值为INF, 但是通过B,D[A][B] + D[B][C] = 12 + 10 = 22 小于 D[A][C] = INF,所以D[A][B] + D[B][C] 为 A->C的最小路径,覆盖D[A][C]的值为22, 以此类推。
代码如下
#include <stdio.h>
#include <stdlib.h>
#define MAXN 10
#define INF = 1000
typedef struct struct_graph{
char vexs[MAXN];
int vexnum;//顶点数
int edgnum;//边数
int matirx[MAXN][MAXN];//邻接矩阵
} Graph;
int pathmatirx[MAXN][MAXN];//记录对应点的最小路径的前驱点,例如p(1,3) = 2 说明顶点1到顶点3的最小路径要经过2
int shortPath[MAXN][MAXN];//记录顶点间的最小路径值
void short_path_floyd(Graph G, int P[MAXN][MAXN], int D[MAXN][MAXN]){
int v, w, k;
//初始化floyd算法的两个矩阵
for(v = 0; v < G.vexnum; v++){
for(w = 0; w < G.vexnum; w++){
D[v][w] = G.matirx[v][w];
P[v][w] = w;
}
}
//这里是弗洛伊德算法的核心部分
//k为中间点
for(k = 0; k < G.vexnum; k++){
//v为起点
for(v = 0 ; v < G.vexnum; v++){
//w为终点
for(w =0; w < G.vexnum; w++){
if(D[v][w] > (D[v][k] + D[k][w])){
D[v][w] = D[v][k] + D[k][w];//更新最小路径
P[v][w] = P[v][k];//更新最小路径中间顶点
}
}
}
}
printf("\n初始化的D矩阵\n");
for(v = 0; v < G.vexnum; v++){
for(w = 0; w < G.vexnum; w++){
printf("%d ", D[v][w]);
}
printf("\n");
}
printf("\n初始化的P矩阵\n");
for(v = 0; v < G.vexnum; v++){
for(w = 0; w < G.vexnum; w++){
printf("%d", P[v][w]);
}
printf("\n");
}
v = 0;
w = 3;
//求 0 到 3的最小路径
printf("\n%d -> %d 的最小路径为:%d\n", v, w, D[v][w]);
k = P[v][w];
printf("path: %d", v);//打印起点
while(k != w){
printf("-> %d", k);//打印中间点
k = P[k][w];
}
printf("-> %d\n", w);
}
int main(){
int v, w;
Graph G;
printf("请输入顶点数:\n");
scanf("%d", &G.vexnum);
printf("请输入初始矩阵值:\n");
for(v = 0; v < G.vexnum; v++){
for(w = 0; w < G.vexnum; w++){
scanf("%d", &G.matirx[v][w]);
}
}
printf("\n输入的矩阵值:\n");
for(v = 0; v < G.vexnum; v++){
for(w = 0; w < G.vexnum; w++){
printf("%d ", G.matirx[v][w]);
}
printf("\n");
}
short_path_floyd(G, pathmatirx, shortPath);
}
SPFA算法
算法概念
由已知向未知更新,如果一个结点发生了更新,则需要对这个点的相邻的点进行更新。入队表示这个结点需要对其相邻的点进行更新。
代码如下
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int spfa()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1]=true;
while(q.size())
{
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j = e[i];
//如果一个结点发生了更新,则需要对这个点的相邻的点进行更新
if(dist[j]>dist[t]+w[i])
{
dist[j] = dist[t] + w[i];
if(!st[j])
{
q.push(j);
st[j]=true;
}
}
}
}
return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof(h));
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int t = spfa();
if(t == 0x3f3f3f3f)puts("impossible");
else printf("%d\n",t);
return 0;
}
bellman-ford算法
算法概念
算法描述:由已知向未知更新,每次循环都只能更新已知的有最小距离的点的相邻的点的距离,因为存在负权边,所以循环k次只能求得1号点到 n号点的最多经过 k条边的最短距离。
算法讲解
代码如下
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510,M=10010;
struct Edge
{
int a,b,c;
}edges[M];
int dist[N];
int last[N];
int n,m,k;
void bellman_ford()
{
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=0;i<k;i++)
{
memcpy(last,dist,sizeof(dist));
//对m条边进行遍历
for(int j=0;j<m;j++)
{
auto e = edges[j];
//dist[e.b]要么就是>0x3f3f3f3f/2,要么就是离原点的最小距离
//每次循环都只能更新已知的有最小距离的点的相邻的点的距离
dist[e.b]=min(dist[e.b],last[e.a]+e.c);
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
edges[i] = {a,b,c};
}
bellman_ford();
if(dist[n] > 0x3f3f3f3f/2)puts("impossible");
else printf("%d\n",dist[n]);
return 0;
}