距离上一次发文已经有挺长一段时间了 主要是前一段时间的题目基本上不会很难 除了那个数独还没去理解(估计得后面自己深入的理解再写出来),深搜广搜基本用同一类方法都能做,但是最近写到了dijkstra,一下子就没有了思路与想法,dijkstra有I和II面对I,我看着代码稍微理解一下,还可以学到,但是对于II,这学期还没学C++的我想要直接写出II的解决方法着实有些困难,所以我花了大半个早上去专门理解了一下II的解决方法,在这里与大家分享。
首先我们先来看一下dijkstra I 吧!
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
在第一题中数据范围是1<=n<=500,1<=m<=10^5
我们可以发现其点的总量不大,所以就可以用一个大小为 510*510的数组来表示这个边与点之间的关系,然后我们运用一个思路,设置一个距离数组,表示的是这个上面的点到第一个点的距离。
那么我们怎么把这个distance 填进去呢? 这里要满足一个原则,就是我遍历n次,每一次都去找距离1这个位置最小的点同时这个点要满足它没有被标记过,然后我可以比较当前剩余点到1的距离与剩余点到当前点的距离加上这个最小距离的距离,并把它取二者中小的,所以遍历结束之后,能得到的distance数组就是给的条件的点到1的距离,没给条件的话它应该保持的是初值。下面附上代码:
这个代码我是照着他人写的自己写了一遍,应该和很多人都相似甚至一样 以后如果可以我会再进行一些优化!
#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;
int arr[511][511];
bool path[511];
int path2[511];
int m,n;
int max2=10000000;
int dijkstra();
int main()
{
memset(arr,0x3f,sizeof arr);
cin >> n >> m;
for(int i=0;i<511;i++){
path2[i]=1000000;
}
for(int i=0;i<m;i++){
int a,b,c;
cin >> a >> b >> c;
arr[a][b]=min(arr[a][b],c); //这里可以注意一下,我们是取重边中的最小值。我们把距离arr中的值调到最大。
}
cout << dijkstra() << endl;;
return 0;
}
int dijkstra(){
path2[1]=0;
for(int i=0;i<=n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
if(!path[j]&&(t==-1||path2[j]<path2[t])) t=j; //每次去找这么多点中到1的最小距离,这样后面就能让剩下的都不断满足最小中找最小。
path[t]=true;
for(int j=1;j<=n;j++)
path2[j]=min(path2[j],path2[t]+arr[t][j]);
}
if(path2[n]==1000000){
return -1;
}else {
return path2[n];
}
}
然后了解了dijkstra I 我们来看一下II, II中题目没有变化,但是数据大小变化了,其中的1<=n<=10^5
我们可以发现,n太大了,不适合再设置数组比如设置一个 (10 ^ 5+10 )*(10 ^ 5+10)的数组空间无法分配那么大,同时很多空间都会被浪费,所有我们不考虑说用这种方法,那么从II 这道题目中我学到了两个东西,一个是存储数据(啊,我也叫不来是啥,可能是图论中的东西把!),另一个是队列。
接下来我们分别解释一下这两个东西,(emmm先写出我的理解吧,估计再过一段时间我的理解应该能够更加深入)
下面是存储方式:
大家看一下下面这一小行代码:
int hou[N],d[N],before[N],xian[N],index2;
void add(int a, int b, int c){
hou[index2]=b,d[index2]=c;
before[index2]=xian[a],xian[a]=index2++;
}
可能看上去有一些不舒服应该我用了我自己习惯的方式给数组命名,有中文有英文,那么上面这行代码有什么用呢?
假如说我告诉你 两个点,以及两个点的距离,那么这个数据你会怎么储存呢?我想你应该会选择将二维数组第一个参数用第一个点表示,第二个参数用第二个点表示,然后里面的大小表示距离,比如我们第一题中的那种,但是如果这个点有10^5多个,那么这个方法显然不好(上面有解释),那么我们现在提出一种新的存储方式,我把第二个点以及第二个点到第一个点的距离存起来,然后通过存储index的方式(我也不知道这个叫啥,但是我觉得有点像是链表的样子)——这个是有记住上一个以及存着现在这个。当然我这样讲估计很难理解,那么来举个例子吧!
比如我现在要将 1 2 2 ,2 3 1 ,1 3 4 存储四次,那么我们可以发现,通过add函数我们能够得到第一次:hou[0]=2,d[0]=2,before[0]=xian[1]=xian[1]的初值,xian[1]=0;第二次:hou[1]=3,d[1]=1,before[1]=xian[2]==xian[2]的初值,xian[2]=1;第三次:hou[2]=3,d[2]=4,before[2]=xian[1]=0,xian[1]=2;
那么我们可以发现如果我想要找1到剩余点的距离的话我可以怎么找呢?
我们是不是可以用
for(int i=xian[1];i!=xian[1]的初值;i=before[i])
这个过程就是i=2 -> i=0 ->i=xian[1]的初值,因此可以通过这种方式来遍历1的所有相邻的点同时知道那些点到1的距离。(注意一下可以这个最后的判断条件最好是不是可以让全体都取一个初值,自己思考一下)
好的第一个问题解决了然后我们来解决第二个问题。
队列:
对于一个没有学过这个的小白得先了解一下基础知识:
下面是基础知识:
首先要包含头文件#include, 他和queue不同的就在于我们可以自定义其中数据的优先级, 让优先级高的排在队列前面,优先出队。
优先队列具有队列的所有特性,包括队列的基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。
和队列基本操作相同:
top 访问队头元素
empty 队列是否为空
size 返回队列内元素个数
push 插入元素到队尾 (并排序)
emplace 原地构造一个元素并插入队列
pop 弹出队头元素
swap 交换内容
定义:priority_queue<Type, Container, Functional>
然后我们来看一下:
//升序队列,小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//降序队列,大顶堆
priority_queue <int,vector<int>,less<int> >q;
//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
这两个是升降序的定义形式。这一题我们用到的是小顶堆,因为我们要找的是最小距离。
然后了解一下pair,你可以定义一个类型 pair它包含两个数,一个是first 一个是second
pair<int,int> P
好接下来我们来分析题目
先附上代码:
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int N = 150010;
int n,m;
typedef pair<int,int> P ; //方便后面表示而已
int hou[N],d[N],before[N],xian[N],index2;
int path[N];
int dist[N];
void add(int a, int b , int c);
int dijkstra();
int main()
{
memset(xian,-1,sizeof(xian));//为了后面方便判断
cin >> n >> m;
while(m--){
int a,b,c;
cin >> a >> b >> c;
add(a,b,c);
}
cout << dijkstra() << endl;
return 0;
}
void add(int a, int b, int c){
hou[index2]=b,d[index2]=c;
before[index2]=xian[a],xian[a]=index2++;
}
//为了存储数据
int dijkstra(){
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
priority_queue<P,vector <P>,greater<P>> que;
que.push({0,1});//1.
while(que.size()){
auto e=que.top();//这里的auto去百度一下就知道了
que.pop();//思考一下如果这个删除不放在这里放在接下来的if下面一行会出现什么情况?
int y = e.second;//把现在到1的最小值的这个点记住
if(path[y])continue;
path[y]=1;
for(int i=xian[y];i!=-1;i=before[i]){
int j=hou[i];
if(dist[j]>dist[y]+d[i]){
dist[j]=dist[y]+d[i];
que.push({dist[j],j});
}
}//2.
}
if(dist[n]==0x3f3f3f3f)return -1;
else return dist[n];
}
我来简单解释一下1和2,1.中加入的{0,1}表示的是1到1这个点距离为0
2.中整体表示的是第一题的思路但是我们的第一题只能通过遍历来找到最小的值,而小顶堆的派上了用场,每一次的第一个都是最小的距离,所以我把它这个点给记住后面遍历然后使得dist和que都被不断填入。
其实我还想多写一些关于优先队列的东西,但是其实大家可以在网上搜到很多相关的所以我在这里就不写了,如果以后把这些东西更深入的弄懂我可能会回来改一改现在还没理解的东西,大概就先这样吧!