练习06:最短路劲问题

单源最短路劲算法

Dijkstra算法

贪心思想:每次找到一个最优点,然后遍历最优点的边,更新维护一个dist数组(dist代表点到各个点的目前最优距离)。

用数组简单是简单,但是遇到大量数据,被安排的明明白白,但还是把两中都写上把。

数组版(简单未优化,新手看)

/*未优化,入门简单版*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<queue>
#include<algorithm>
#define Max 1005
#define max(a,b) a>b?a:b;
#define inf 0x3f3f3f3f
using namespace std;
int a[Max][Max]; //各个定点的距离
bool vis[Max];  //记录是否走过
int dist[Max];  //目前最优距离
int n;	//点的数量
/*数组版的效率可能低了点...*/
void dijkstra(int num)
{
	dist[num]=0;//自己是0
	for(int i=1;i<n;i++) //一共需要找多少个点
	{
		int min=inf,u=-1;
		for(int j=1;j<=n;j++)
		{
			if(!vis[j]&&dist[j]<min) //找到最小的点
			{
				min=dist[j];
				u=j;
			}
		}
		vis[u]=true;//走过
		for(int j=1;j<=n;j++)
		{
			if(min+a[u][j]<dist[j])
			{
				dist[j]=a[u][j]+min;
			}
		}
	}
}
int main()
{
	int beg,end,m,x,y,w;
	memset(a,inf,sizeof(a)); //记得初始化这几个数组
	memset(vis,false,sizeof(vis));
	memset(dist,inf,sizeof(dist));
	scanf("%d%d%d%d",&n,&m,&beg,&end); //依次为点,边,开始的点,结束的点
	while(m--)
	{
		scanf("%d%d%d",&x,&y,&w);
		a[x][y]=w;
		a[y][x]=w;
	}
	dijkstra(beg);
	printf("%d\n",dist[end]);
	return 0;
}

优化策略:每次查找最小点的时候可以用堆优化(我使用STL的优先队列,不懂的可以先百度),当数据比较大,有事稀疏图的时候数组会炸,所有使用结构体保存边,利用一个head数组作为索引(好像有人叫向前星??,我觉得就是和链表一样头插法)

稀疏图时间复杂度:O(ElgV)

个人目前使用的Dijkstra模板,别说模板麻烦,遇到有些题卡时间就知道了,也可以自己找模板:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<queue> 
#define Max 100010
#define max(a,b) a>b?a:b;
#define min(a,b) a>b?b:a;
#define inf 0x3f3f3f3f
/*数据存储优化,用数组储存尽量,但是不能太大...AC!  标准版868ms  弱化版429ms*/ 
using namespace std;
bool vis[Max];
int dist[Max];
int max_num;
struct Edge{
	int next;  //指向下一个元素 
	int to;  //点 
	int dis;//距离 
}edge[200010];
int num_edge;
int head[Max];//初始化为0 
void add_edge(int x,int y,int data)  //头插法 
{
	edge[++num_edge].next=head[x];
	edge[num_edge].to=y;
	edge[num_edge].dis=data;
	head[x]=num_edge;
}
struct heap{ //构建优先队列
	int data;
	int weight;
};
struct cmp{  //优先队列 比较函数
 bool operator()(struct heap a,struct heap b)
	{
		return a.weight>b.weight;
	}
};
priority_queue <struct heap,vector<heap>,cmp> q;
struct node{
	int data;
	int weight;
	struct node *next;
};
int n;
void dijkstra(int num)
{
	dist[num]=0;
	max_num=n;
	struct heap h;
	h.data=num;
	h.weight=0;
	num_edge=0;
	q.push(h);
	while(q.size())
	{
		int min=inf,u=-1;
//		for(int j=1;j<=max_num;j++)  //每次查找都需要n时间, 
//		{
//			if(!vis[j]&&dist[j]<=min)
//			{
//				min=dist[j];
//				u=j;
//			}
//		}
		h=q.top();
		q.pop();
		min=h.weight;
		u=h.data;
		if(vis[u])
			continue;
		vis[u]=true;
		for(int i=head[u];i;i=edge[i].next)
		{
			if(min+edge[i].dis<dist[edge[i].to])
			{
				dist[edge[i].to]=min+edge[i].dis;
				if(vis[edge[i].to]) //已经确定了最优点了 
				continue;
				h.data=edge[i].to; //点 
				h.weight=dist[edge[i].to];
				q.push(h);
			}
		}
	}
}
void show(int a[],int n)
{
	for(int i=1;i<n;i++)
	{
//		if(a[i]==1061109567)  //我设置的和它不一样... 
//			printf("2147483647");
//		else
		printf("%d",a[i]);
		if(i!=n-1)
			printf(" ");
	}
	printf("\n");
}
int main()
{
	int m,x,y,weight,data;
	memset(vis,false,sizeof(vis));
	memset(dist,inf,sizeof(dist));
	memset(head,0,sizeof(head));
	scanf("%d%d%d",&n,&m,&data);
	while(m--)
	{
		scanf("%d%d%d",&x,&y,&weight);
		if(x==y)
			continue;
		else
		{
			add_edge(x,y,weight);
		}
	}
	dijkstra(data);
	show(dist,n+1);
	return 0;
}

下面题都能过

P1339 [USACO09OCT]热浪Heat Wave

P1346 电车

P4779 【模板】单源最短路径(标准版)

SPFA算法:

介绍:Bellman-Ford算法 的队列优化算法,等等。自己看吧。

emmm,其实我也是今天刚学,其实它的复杂度比Dijkstra大,但是用的人多,主要是它容易写,代码量小。还有个优点判断环,还有能计算负权值的情况。

PS;有时候出题人会故意卡SPFA算法的哦,所以能用Disjksta  最好用Dijkstra求。

贴一个今天刚写的模板:
 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<queue>
#include<algorithm>
#define Max 10001
#define max(a,b) a>b?a:b;
#define min(a,b) a>b?b:a;

using namespace std;

/*按照LG1339写的*/
queue<int> q;
int d[Max];
int cnt[Max];//记录走过了多少次
bool inque[Max]; //在队列中的信息
struct Node{
	int t;
	int dist;//距离
};
vector<Node> g[Max];
int n;
bool spfa(int s)  //从s开始
{
	memset(d,0x3f3f3f3f,sizeof(d));
	memset(inque,0,sizeof(inque));
	memset(cnt,0,sizeof(cnt));
	d[s]=0;
	q.push(s);
	inque[s]=true; //记录
	cnt[s]++;
	while(q.size())
	{
		int u=q.front();
		q.pop();
		inque[u]=false; //出去
		int nun=g[u].size();	//提前把记录弄出来,不然警告。。。
		for(int i=0;i<nun;i++)
		{
			int v=g[u][i].t;
			int w=g[u][i].dist;
			if(d[u]+w<d[v])
			{
				d[v]=d[u]+w;
				if(inque[v]==false)  //不在队列里面
				{
					q.push(v);		 //入队
					cnt[v]++;
					if(cnt[v]>n)
						return false;
					inque[v]=true;
				}
			}
		}
	}
	return true;
}
int main()
{
	int m,st,end;
	int x,y,w;
	scanf("%d%d%d%d",&n,&m,&st,&end); //点、边、开始、结束
	struct Node temp;
	while(m--)
	{
		scanf("%d%d%d",&x,&y,&w);
		temp.t=x;
		temp.dist=w;
		g[y].push_back(temp);
		temp.t=y;
		g[x].push_back(temp);//两边都要走
	}
	if(spfa(st))
		printf("%d\n",d[end]);
	else
		printf("有环。\n");
	return 0;
}

 

多源最短路劲问题

 

Floyd算法

  核心代码5行,简单吧:

 

for(k=1;k<=n;k++) //一共有多少个点,每次进入一个点

    for(i=1;i<=n;i++)  //遍历所有的点

        for(j=1;j<=n;j++)  

            if(e[i][j]>e[i][k]+e[k][j])  //松弛操作

                 e[i][j]=e[i][k]+e[k][j];

 

简单练习题

AC代码:
 

#include<stdio.h>
#include<string.h>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<stack>
#define Max 210
#define inf 0x3f3f3f3f
#define min(a,b) a>b?b:a;
using namespace std;

int t[Max];//村庄
int m[Max][Max];	//路径的值
int main()
{
	int n,mm,x,y,data,k=0;
	memset(m,inf,sizeof(m));
	scanf("%d%d",&n,&mm);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&t[i]);
		m[i][i]=0;
	}
	while(mm--)
	{
		scanf("%d%d%d",&x,&y,&data);
		if(x==y)
			continue;
		m[x][y]=data;  //两边都可以走哦
		m[y][x]=data;
	}
	int q;
	scanf("%d",&q);
	k=0;
	while(q--) //询问
	{
		scanf("%d%d%d",&x,&y,&data);
		while(t[k]<=data&&k<=n)  //注意,村庄恢复的时间题目给的升序,不需要排
		{
			for(int i=0;i<n;i++) //把这个点进来后更新所有的点
			{
				for(int j=0;j<n;j++)
				{
					m[i][j]=min(m[i][j],m[i][k]+m[k][j]);
				}
			}
			k++;
		}
		if(m[x][y]==inf||t[x]>data||t[y]>data) //时间不对或者不能到达
			printf("-1\n");
		else
			printf("%d\n",m[x][y]);
	}
	return 0;
}

暂时到这了,有问题请在下面留言。

时间越来越紧了,大批算法需要学习,暑假加油啊!ACM!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值