错误总结
- 数组开太小了,就你不要看有多少个点就开多少个,可能是后面需要的更多。边的需求量是很大很大滴!!反正你需要考虑你边的需求量,还是需求很多的!
- 你不要定义一样名字的遍历,dist和你那个队列顶端的距离不要都叫dist
- 是小根堆,小根堆,dijkstra堆优化的是小根堆。
- 注意dijkstra 的前四行,又分为两组,一组是关于dist的初始化的,一组的关于小根堆的初始化和第一个值放进去的。
if(dist[j]>dist[ver]+w[i])
这一行是这俩东西!!!i和j是一伙的!都是下一层,只有ver才是上一层!!- 其实你邻接表的add就比单链表的多了一个权重而已,一共就4条,很好想出来的。
- e,w,ne都是用下标idx为索引的,只有h和dist是用结点当作索引的,h里面的值才是下标
- typedef pair<int, int> PII;这个部分你忘记了格式怎么写
- 科学计数法要表达是1e6+10
- 队列的PII进队是heap.push({dist[j],j}),不是heap.push(dist[j],j)!!!
- 你的堆在取出top之后记得要pop一下
总体思路
S:当前已经确定起点到它的最短距离的点的集合
1.初始化距离,用数组,起点为第一个点,那么dist[1]=0,其他的距离设置一个非常大的数。
2 for 从1~n
这是一个迭代的过程,每一次确定一个点到起点的最短路,确定的点的当前还没有确定的点中距离最小的点(贪心的思想)
2.1
找到t这个点,并把t加入到S中
t:不在S中,能找到的到起点距离最短的点,把它加入到S中,(因为你的S已经是确定好最短路的点了,然后呢你的t又刚好是从这个点出发的直接接触到的最短的点了,所以其实你按定义来看呢,你的t刚好就是符合S的定义了,所以加入到S中,很顺滑)
2.2
用t更新其他点到起点的距离,更新完之后呢,这次迭代就结束了。
时间复杂度:外层是n重循环,然后层一个找最小值还有一个更新,这两部都是n次,所以就是 n 2 n^2 n2的时间复杂度。
朴素版的dijkstra算法,由于边数很多,所以是一个稠密图。
然后稠密图呢用邻接矩阵去做。(稀疏图用邻接表去做)
代码
朴素算法
const int N = 510;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;//按照计划的那样初始化距离
for (int i = 0; i < n - 1; i ++ )//也是按照计划的那样迭代n次
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;//其实也是同样的按计划从不在s中的点一个一个找,直到找到距离最小的t。
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
我想的是这种东西只需要把算法的细节一点一点全部都弄明白之后就好了,然后自己写的时候还是出了问题,不够熟练会导致想法很乱思路不连贯。
这种的还是需要自己完整地弄出来一遍才能直到每一处的细节,虽然可能有点麻烦,但是只要弄会一次就以后就几乎不用再学了。
只有自己去复现一遍才会真正了解它需要考虑的所有的细节,才能清楚什么东西是不可以缺少的,清楚什么顺序是可以改变的。
这次出现的问题:
1.脑子卡了,输入的时候看不清楚它是怎么输入的,它是好几组,一组的abc代表的分别是a到b的距离。
2.存储边的数组和存储每个点到起点距离的数组混了。。。
3.它的重边处理呢,可以直接不处理,也可以的。
4.输出的时候不要用&!!!
5.起点到要求哪个点之间没有路径!!!!
虽然会出现问题但是也有优化得比它原本好的地方,比如
for(int j=1;j<=n;j++){
if(!st[j]&&dist[j]>dist[t]+g[t][j]){
dist[j]=dist[t]+g[t][j];
}
}
和它原本
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
的对比,虽然没有它的简洁,但是有些地方考虑得会比它更加周到。
堆优化版
大概的意思就是说呢,你不是原本有三个for循环吗,一个套着两个,然后你直接给他把第一个for循环优化掉了,你换成了用堆去实现,少了被套在里面的第一个for循环。然后你外面的换成了一个类似广搜一样的while去搞,就是这么个意思。
就是比如说是一个稀疏图,然后n是100000,那么两重循环就会爆掉。
通过一个优先队列去优化一下。
学东西呢,一定要循序渐进,把基础的学清楚了再去提高难度。上面的学明白了再去学堆优化版就会感觉很清晰简单。
先看一下邻接表的数组模拟部分。
邻接表其实就是n个链表
算了,那先讲一下单个链表的情况吧,反正邻接表就是多个单链表组合起来。
// head存储链表头(下标),e[]存储节点的值,ne[]存储节点的next的下标,idx表示当前用到了哪个下标,idx从0开始
1.定义
int head, e[N], ne[N], idx;
这里面head,ne,idx全部都是下标,只有e表示的是结点
2.初始化
// 初始化
void init()
{
head = -1;
idx = 0;
}
3.添加
// 在链表头插入一个数,值是a
void insert(int a)
{
e[idx] = a, //idx这个新的下标给你a
ne[idx] = head, //idx下标的下一个下标变成头结点的下标
head = idx ++ ;//头结点变成你这个下标
}
// 将头结点删除,需要保证头结点存在
void remove()
{
head = ne[head];
}
看完了单链表的东西,就可以看一下这个邻接表存储图的操作了。
这边由于边是有权重的,所以多出了一个w数组用来存储权重。
h数组呢,存储的是这些单链表的头结点的下标。里面的所有东西一开始都赋值为-1,就是代表一开始这些链表的头结点都是-1,就跟单链表中的一个赋值是一样的。
1.定义
int h[N], w[N], e[N], ne[N], idx;
2.初始化
memset(h, -1, sizeof h);
3.添加
还有加边的模板
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
然后加边就只需要
add(a, b, c)
就可以了
嗯,到这为止是邻接表的部分。
再次重申一下邻接表的定义,就是所有相邻的点都存在一个链表里面。
现在是堆优化的部分。
1.定义小根堆,把第一个值放进去,小根堆需要加俩东西。
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1});
2.对这个小根堆进行宽搜?
这里有三个东西需要拿出来看一下。一个是下标,一个是结点,还有一个是这个结点到起始点的距离。这三个东西完全不同,要特别注意,距离dist还有头结点的下标数组h是用结点为索引的,w,e,ne是用下标为索引的。注意一下。
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue;
st[ver] = true;//至于这个点,这两行为什么要这么写呢,就是因为别人说因为堆优化不能修改任意值,所以会导致冗余,就加了这么两行,我不是很理解
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}