亭亭玉立,两袖清风。花拳绣腿,一事无成。|

菜鸟侦探乐

园龄:4年3个月粉丝:2关注:2

2021-08-04 01:47阅读: 103评论: 0推荐: 0

算法每周一题(一)——单源最短路

原题目


题目描述
给定一个 n 个点,m 条有向边的带非负权图,请你计算从 s 出发,到每个点的距离。

数据保证能从 s 出发到任意点。

输入格式
第一行为三个正整数 n,m,s。 第二行起 m 行,每行三个非负整数 ui,vi,wi,表示从 uivi 有一条权值为 wi 的有向边。

输出格式
输出一行 n 个空格分隔的非负整数,表示 s 到每个点的距离。


初次尝试:


由于刚刚学过数据结构,很快就有了思路(采用邻接表存储+Dijkstra算法),但是由于从没实践过😭,所以写的时候有点费劲,又调试了好久,才得到正确的结果:

  • 第一代代码:
//单源最短路传统
#include<bits/stdc++.h>
using namespace std;
int dist[100005],path[100005];
bool S[100005];
typedef struct e{
int key;
int d;
struct e *next;
}edge;
typedef struct{
edge *out=NULL;
}node;
void add(node *n,int v,int w)
{
edge *e=(edge *)malloc(sizeof(edge));
e->key=v;
e->d=w;
e->next=n->out;
n->out=e;
}
void kl(node *n,int k)
{
edge *e=n->out;
while(e!=NULL)
{
if(e->d+dist[k]<dist[e->key])
{
dist[e->key]=e->d+dist[k];
path[e->key]=k;
}
e=e->next;
}
}
int main()
{
memset(dist,100,sizeof(dist));//距离初始化为无穷,这里为一个很大的数
memset(path,-1,sizeof(path));
int n,m,s,u,v,w;
node N[100005];
scanf("%d%d%d",&n,&m,&s);
int nn=n;
dist[s]=0;path[s]=s;dist[0]=2000000005;
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&u,&v,&w);
add(N+u,v,w);
}
while(nn!=0)
{
int mini=0;
kl(N+s,s);
S[s]=S[0]=1;
for(int i=1;i<=n;i++)
if(dist[i]<dist[mini]&&S[i]==0)mini=i;//寻找距离最短的结点来考虑
s=mini;
nn--;
}
for(int i=1;i<=n;i++)printf("%d ",dist[i]);
return 0;
}

痛苦地写了很久很久,结果全部超时了,简直太悲伤了!!😭😭

又审视写下的代码,发现while(nn!=0)内存在两个循环,一个在kl()函数中,但遍历所有点是算法要求因此不作优化;另一个“寻找距离最短的结点”时间复杂度为O(n),需要考虑对其进行优化。由于总是取出距离最短的结点,想到可以使用堆来进行维护,时间复杂度为O(1),但此时笔者已经心太累只想快点完成这道题,因此直接使用STL库中定义好的优先队列——堆来进行优化。

  • 小资料

    • stl优先队列的使用:

    • //定义一个优先队列
      std::priority_queue<Type,Container,Functional> q;
      //数据操作
      q.push()//入队
      q.pop()//出队
      q.top()//返回顶部元素
      q.empty()//返回队列是否为空
      /*特别地,stl优先队列使用<进行大小比较,对于自定义的容器,需要在内部重载运算符才能正常使用。*/
      //使用范例
      #include<bits/stdc++.h>
      using namespace std;
      struct node{
      int k;
      int m;
      bool operator <(const node &x)const{
      return k<x.k;//默认为大根堆,相反则为小根堆
      }//关键部分,定义逻辑比较符,声明比较的内容
      };
      int main()
      {
      std::priority_queue<node> q;
      q.push({1,2});
      q.push({2,1});
      q.push({0,7});
      node temp=q.top();
      cout<<temp.k<<" "<<temp.m;
      return 0;
      }

解法优化


优化的关键在于想到把新旧的<距离,结点>组全部放在一个堆中是没有关系的,只要取出以后判断结点是否已经访问过即可。这样就方便多了。其次本题中path[]数组也是没有用的,直接注释掉。

  • 优化后代码
//单源最短路堆优化
#include<bits/stdc++.h>
using namespace std;
int dist[100005];//int path[100005];//本题path[]数组好像没什么用
bool vis[100005];
//定义数据结构
struct edge{
int key;
int d;
edge *next;
};
struct node{
edge *out=NULL;
}N[100005];
struct node_dist{
int dist;
int node;
bool operator < (const node_dist &x)const
{
return dist>x.dist;
}//重载运算符使兼容,并定义成小根堆
};
std::priority_queue<node_dist> q;//使用stl库定义一个优先队列
//定义函数
void add(node *n,int v,int w)
{
edge *e=(edge *)malloc(sizeof(edge));
e->key=v;
e->d=w;
e->next=n->out;
n->out=e;
}
void kaolv(node *n,int k)
{
edge *e=n->out;
while(e!=NULL)
{
if(e->d+dist[k]<dist[e->key])
{
dist[e->key]=e->d+dist[k];
// path[e->key]=k;
q.push({dist[e->key],e->key});
}
e=e->next;
}
}
int main()
{
int n,m,s,u,v,w;
memset(dist,100,sizeof(dist));//将dist[]数组所有元素初始化为无穷(很大的值)
// memset(path,-1,sizeof(path));
scanf("%d %d %d",&n,&m,&s);
int nn=n;
//输入
for(int i=0;i<m;i++)
{
scanf("%d %d %d",&u,&v,&w);
add(N+u,v,w);
}
//Dijkstra
dist[s]=0;//path[s]=s;
q.push({0,s});
while(!q.empty())
{
node_dist tmp=q.top();
q.pop();
int n=tmp.node;
if(!vis[n])
{
kaolv(N+n,n);//考虑该点
vis[n]=1;
}
}
//输出
for(int i=1;i<=n;i++)printf("%d ",dist[i]);
return 0;
}


终于过了,记录下成功的喜悦2021-8-4 凌晨1点24分!!

本文作者:菜鸟侦探乐

本文链接:https://www.cnblogs.com/zouludaxia/p/15097108.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   菜鸟侦探乐  阅读(103)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起