除了负值都能用,存图可用邻接矩阵、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。
暴力算法
#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都可,我目前没遇到那样难度的题来着,但是有讲的很好的博客,感兴趣的可以去看看。
本质是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]);
}
}
}
}