最短路算法

dijkstra算法

除了负值都能用,存图可用邻接矩阵、vector、链式前向星

朴素循环

#include 
using namespace std;
const int N = 1e5 + 5;
int head[N];
int n, m, s;
struct node {
	int to, nex, val;
} arr[N];
int tot = 0;
int dis[N];
bool vis[N];

void add(int u, int v, int c) {
	arr[++tot].to = v;
	arr[tot].val = c;
	arr[tot].nex = head[u];
	head[u] = tot;
}

void dij(int a) {
	memset(dis, 0x3f, sizeof dis);
	memset(vis, 0, sizeof vis);
	dis[a] = 0;
	for (int i = 0; i < n; ++i) {    //大循环循环的是次数,与i值无关
		int id = -1;
		int minn = 0x3f;
		for (int j = 1; j <= n; ++j) {        //找点
			if (vis[j] == 0 && dis[j] < minn) {
				id = j;
				minn = dis[j];
				vis[j] = 1;
			}
		}
		for (int j = head[id]; j; j = arr[j].nex) {    //更新dis数组
			int v = arr[j].to;
			if (dis[v] > dis[id] + arr[j].val) {
				dis[v] = dis[id] + arr[j].val;
			}
		}
	}
	for (int i = 1; i <= n; ++i) {
		cout << dis[i] << " \n"[i == n];
	}
}

int main() {
	cin >> n >> m >> s;
	int u, v, c;
	for (int i = 0; i < m; ++i) {
		cin >> u >> v >> c;
		add(u, v, c);
	}
	dij(s);
	return 0;
}

优先队列优化

洛谷3371进阶板子题

#include 
using namespace std;
#define ll long long
//不开ll见祖宗!!
const int N = 5e5 + 5;
const int T = 1e4 + 5;
ll tot = 0;
ll head[T];
struct node {
	ll to, nex, val;
} arr[N];
ll dis[T];
bool vis[T];
bool rec[T];        //记录是否可到达
pairpii;

void add(ll u, ll v, ll c) {
	arr[++tot].to = v;
	arr[tot].val = c;
	arr[tot].nex = head[u];
	head[u] = tot;
}

void dijkstra(ll a) {
	memset(vis, 0, sizeof vis);
	memset(rec, 0, sizeof rec);
	memset(dis, 0x3f, sizeof dis);
	dis[a] = 0;
	rec[a] = 1;
	priority_queue, vector >, greater > > q;
	q.push(make_pair(0, a));
	while (!q.empty()) {
//我们的队列里存的是有可能成为中转站的点
//在对中转站遍历的过程中更新以中转站为边的另一端点的dis值
//什么点可能成为中转站呢?
//就是在遍历过程中被更新过的点,他有了更小的值,有了减小别的点的dis值的可能
		int u = q.top().second;
		q.pop();
		if (vis[u])
			continue;
//这里需要注意,是个优化点。(*)
//那么此时我们的vis数组记录的并不是在不在队列中,而是是否出过队
		vis[u] = 1;
		for (ll i = head[u]; i; i = arr[i].nex) {
			ll v = arr[i].to;
			if (dis[v] > dis[u] + arr[i].val) {
				dis[v] = dis[u] + arr[i].val;
				rec[v] = 1;
				q.push(make_pair(dis[v], v));
			}
		}
	}
}

int main() {
	ll n, m, s;
	cin >> n >> m >> s;
	ll u, v, c;
	for (ll i = 1; i <= m; ++i) {
		cin >> u >> v >> c;
		add(u, v, c);
	}
	dijkstra(s);
	for (ll i = 1; i <= n; ++i) {
		if (rec[i])
			cout << dis[i] << " \n"[i == n];
		else
			cout << "2147483647" << " \n"[i == n];
	}
	return 0;
}

(*):一开始不理解这个为什么不像SPFA那样在入队时判断,后来想通了:因为我们的优先队列是基于dis值的升序判断的,一个点可能经过好几个中转站被多次更新,那么并不一定他第一次被更新的值就是最小的值,所以我们应该让他重复入队,利用优先队列让相同点的最小dis值优先出队。

还有一个问题,是否有可能使得某点出队后再次被更新得到更小的dis?

不可能!因为按照优先队列的出队规则,在他之后出队的dis值是一定比他大。我们看更新的操作,由于加法原则,他被更新时(即他入队时),中转站的dis和val之和小于他的dis,那中转站一定比他先出队,他获得再次被更新的可能。他出队时被更新者的dis值必大于等于他的dis+val(小于该值则不会被更新),即被他更新而入队的元素dis值必不小于他,拓展一下,在他出队之后入队的所有dis必不小于他,不可能再次更新他。

这样就可以保证正值范围内优先队列的正确性。

负值不能用的原因是,val可能出现负值从而改变dis值,负值可以成功修改他的dis值,但是我们不能保证他被修改一定是在他出队前,若他在出队后dis值被更新为更小,那么按理讲他可以成为新的中转站去更新别的点,但我们已经标记过他了,他再次出队时会被continue掉,就会发生错误,这就引出下面的SPFA。

SPFA

暴力算法

#include
using namespace std;
const int N=1005;
bool vis[N];
int tot=0;
int head[N];

struct node{
	int to,nex,val;
}arr[N];

void add(int u,int v,int c){
	arr[++tot].to=v;
	arr[tot].val=c;
	arr[tot].nex=head[u];//建图建图建图是head[u]
//是谁写了u卡了一下午啊是我啊
	head[u]=tot;
}

void spfa(int a){
	memset(dis,0x3f,sizeof dis);
	queueq;
	dis[a]=0;
	vis[a]=1;
	q.push(a);
	while(!q.empty()){
		int u=q.front();q.pop();vis[u]=0;    //这里vis表示是否在队中
//我们浪费时间,使一个元素可多次入队,保证出现负值的正确性
		for(int i=head[u];i;i=arr[i].nex){
			int v=arr[i].to;
			if(dis[v]>dis[u]+arr[i].val){
				dis[v]=dis[u]+arr[i].val;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}

int main(){
	int m,n,s;
	cin>>n>>m>>s;
	int u,v,c;
	for(int i=0;i>u>>v>>c;
		add(u,v,c);
	}
	spfa(s);
	for(int i=1;i<=n;++i){
		cout<

这里再拓展一下SPFA可以用来判负环,dfs或bfs都可,我目前没遇到那样难度的题来着,但是有讲的很好的博客,感兴趣的可以去看看。 

Floyed

本质是dp

int dis[N][N];
void floyed(){
	for(int k=1;k<=n;++k){
		for(int i=1;i<=n;++i){
			for(int j=1;j<=n;++j){
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
	}
}

你可能感兴趣的:(图论,图论,算法)