【图论】最短路径算法大全

Dijkstra算法

特点:单源最短路径,无负权边。

算法思路:设置一个dis[i]数组记录i到源点的最小距离。一开始只有源点的dis为0,其他全为无限大。从源点出发,更新周围与它相连的点。进行顶点次数的循环,每次寻找与源点距离最近的点,标记为已访问,并从该点出发更新相连的点的状态。根据三角不等式dis[i]+map[i][j]跟dis[j]比较,如果小于,说明经过i再到j比原来源点到j的最短距离还要短,则更新之。最后能得到源点到任意点的最短路径。

代码如下:

 

//djikstra算法 
void djikstra()
{
	int i,j,k;
	for(i=1;i<=n;i++)
	{
		dis[i]=inf;//一开始所有点到源点的距离为无限大 
		vis[i]=0;
	}
	dis[1]=0;//将源点到自身距离设置为0
	for(i=1;i<=n;i++)
	{
		int min=inf;
		for(j=1;j<=n;j++)
		{
			if(!vis[j]&&dis[j]<min)// 遍历所有点,如果点没有被访问过,并且dis[j]被更新过,则保留dis[j]最小的点的下标 
			{
				min=dis[j];
				k=j;
			}
		}
		if(min==inf)break;
		vis[k]=1;//将找出的dis[j]最小的点标记为已访问,这样首先一开始源点会被标记。从该点开始,进行下面的操作。
		for(j=1;j<=n;j++)
		{
			if(map[k][j]!=inf&&!vis[j])//如果k点可以到达j点,即存在(k,j)边,则更新这条边 
			{
				if(dis[k]+map[k][j]<dis[j])//如果经过k点可以到达j点,则判断源点到j的距离是否大于经过该点再到j的距离,如果是,则更新之。通过这样不断地寻找最短路径。 
				{
					dis[j]=dis[k]+map[k][j];//到达j点的最短路径=到达k点的最短路径+(k,j)边的距离 
					pre[j]=k;//将j的前置设置为k 
				} 
			}	
		} 
	} 
} 


Head优化的djikstra算法

由于普通的dijkstra算法每次寻找与源点最近的点都要遍历整个dis数组,效率有点慢,可以使用堆进行优化。将dis数组看成是最小堆,用o(logn)的时间复杂度去调整一个堆,只需要O(1)的时间就可以取出最小值。效率得到明显提高。

 

const int maxn=50000+5;
const long long INF=0x3f3f3f3f3f;
int weight[maxn];
int cnt;
int head[maxn];
long long dis[maxn];
bool vis[maxn];
struct Edge
{
    int to;//指示边的终点 
	int w;//边的权重 
	int next;//下一条边的起始点 
} edge[maxn<<1];
struct Node
{
	int u,dis;
	bool operator <(const 	Node &a) const
	{
		return dis>a.dis;
		// 当返回true时会更新堆,因此当新元素a的dis小于堆顶元素dis
        // 的时候会返回true,同时会更新堆,故此堆为小顶堆
	}
} 
void Init()
{
    cnt=0;
    memset(head,-1,sizeof(head));
    memset(vis,false,sizeof(vis));
    for(int i=0; i<=n; i++)
        dis[i]=INF;
}
void addEdge(int u,int to,int w)
{
    edge[cnt].to=to;
    edge[cnt].w=w;
    edge[cnt].next=head[u];//将这条边插入以u开头的链表 
    head[u]=cnt++;//将u这个点的head指针指向最后插入的节点,可以看成是一个头插法的链表 
}
void Dijkstra(int s)
{
    Node now,next;
    priority_queue<Node>q;//定义一个优先队列 
    now.dis=0;//将当前点的距离设置为0 
    now.u=s;
    dis[s]=0;
    q.push(now);//将源点加入到优先队列中 
    while(!q.empty())//如果优先队列非空 
    {
        now=q.top();//取出队列中dis最小的元素.这里便是整个程序优化的地方,之前我们是通过遍历n个点来得到最小值,现在我们通过维护一个堆来取得。 
        q.pop();//将最小元素出队 
        if(vis[now.u])continue;//如果该点已经被访问,则跳过 
        int u=now.u;//否则从该点出发并将该点标记为已访问 
        vis[u]=true;
        for(int i=head[u]; i!=-1; i=edge[i].next)//遍历以u开头的整个链表 ,更新与u相连的点 
        {
            int to=edge[i].to;
            if(!vis[to]&&dis[u]+edge[i].w<dis[to])
            {
                dis[to]=dis[u]+edge[i].w;
                next.dis=dis[to];
                next.u=to;
                q.push(next);
            }
        }
    }
}


Bellman-Ford算法

特点:单源最短路径,含负权边,可以找出负环,然而效率低下,一般使用队列优化过的SPFA方法.

思路:持续地进行松弛(即上文介绍的“三角不等式”),在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。

//Bellman-Ford算法 
 struct Edge
{
    int from;
	int to;
 	int cost;
 }edge[N*2];
  int ednum;
bool Bellman(int start,int n)
{
	int i,j,flag;
	for(i=1;i<=n;i++)
	{
		dis[i]=inf;//一开始所有点到源点的距离为无限大 
		vis[i]=0;
	}
	dis[start]=0;//将源点到自身距离设置为0
	for(i=1;i<n;i++)//进行|V|-1次循环
	{
		flag=0;
		for(j=0;j<ednum;j++)
		{
			if(dis[edge[j].to]>dis[edge[j].from]+edge[j].cost)
			{
				dis[edge[j].to]=dis[edge[j].from]+edge[j].cost
				flag=1;
			}
		}
		if(!flag)break;//若没有松弛,则跳出 
	} 
	for(i=1;i<n;i++)//进行第|V|次循环
	{
		if(dis[edge[i].u]>dis[edge[i].v]+edge[i].w)//如果第V次还能进行松弛,说明存在负环 
				return true;
	}
	return false;//第|V|次没有松弛,说明不存在负环。 
} 


SPFA(Short PathFaster Algorithm)

特点:bellman-ford的一种队列实现,可以处理负权边,但无法处理负环。只要最短路径存在,上述SPFA算法必定能求出最小值。效率很高。BFS形式相似,当BFS中一个点出队后就不可能再次进入,而SPFA中一个点可以被反复迭代。如果某个点进入队列的次数超过N次则存在负环

思路:初始时将源加入队列,每次从队列中取出一个元素,并对所有与它相邻的点松弛,若松弛成功,则将其入队,直到队列为空时算法结束。

int SPFA(int n,int s)
{
        int i,head=0,tail=0,p,t;
        memset(vis,0,sizeof(vis));
        for(i=1;i<=n;i++)dis[i]=inf;//初始化所有点的距离到源点为无限大
        dis[s]=0;//将起点到起点距离设置为0
        vis[s]=1;//将起点入队并标记为已访问
        que[tail++]=s;
        while(head<tail)
        {
                p=que[head];
                for(i=1;i<=pnt[p][0];i++)
                {
                        t=pnt[p][i];
                        if(dis[p]+map[p][t]<dis[t])
                        {
                                dis[t]=dis[p]+map[p][t];
                                if(!vis[t])
                                {
                                        que[tail++]=t;
                                        vis[t]=1;
                                        flag[t]++;//记录每个点进入队列的次数
                                        if(flag[t]>n)return 0;//如果某个点进入队列次数超过n次则存在负环
                                }
                        }
                }
                vis[p]=0;
                head++;
        }
        return 1;
}


SPFA(dfs版本)

特点:判断负环更快

//SPFA dfs写法,判断负环更快
int spfa_dfs(int u)
{
    vis[u]=1;
    for(int k=head[u]; k!=0; k=edge[k].next)
    {
        int v=edge[k].v,w=edge[k].w;
        if( d[u]+w < d[v] )
        {
            d[v]=d[u]+w;
            if(!vis[v])
            {
                if(spfa_dfs(v))
                    return 1;
            }
            else
                return 1;
        }
    }
    vis[u]=0;
    return 0;
} 


floyd算法

特点:全源最短路径 时间复杂度o(n^3) 空间 o(n^2),边权可正可负,不适合

思路:利用动态规划的思路来寻找任意两点之间的最短路径,其状态转移方程为map[i,j]:=min{map[i,k]+map[k,j],map[i,j]}

void floyd()
{
        for(int k=1;k<=n;k++)
                for(int i=1;i<=n;i++)
                        for(int j=1;j<=n;j++)
                                if(dis[i][j]>dis[i][k]+dis[k][j])
                                        dis[i][j]=dis[i][k]+dis[k][j];
}


 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值