单源最短路——Dijkstra算法与链式前向星存图

链式前向星是一种静态链表存储,用边集数组和邻接表相结合,可以快速访问一个顶点的所有邻接点,在算法竞赛中广泛使用。

                                                                                                                          ——摘自知乎

对于下面这组数据,第一行4个顶点,6条边,接下来的6行是边的起点、终点、权值,比如 1 -> 2 的权值为2

4 6
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4

链式前向星存的就是每一个顶点为起点的边的集合

//以1为起点的边的集合有
1 2 2
1 3 5
1 4 4
//以2为起点的边的集合有
2 3 2
2 4 1
//以3为起点的边的集合有
3 4 3

我们给上面的6条边进行编号【0,5】。

接下来我们给出链式前向星的存储结构,其中e[i]存第i条边,to代表终点,next代表与这条边相同的上一条边的编号,weight代表权值,head[i]代表以第i号点为起点的最后一条边的编号,通常head[i]初始化为-1,因为没有编号为-1的边。

#define m 10

struct edge {
	int to;
	int next;
	int	weight;
}e[m];

int head[m];

现在我们就可以给出以下函数,u为起点,v为终点,w为权值,cnt记录第几条边,m为边数

void create()
{
	for (int i = 0; i < m; i++)head[i] = -1;
	int cnt = 0;
	for (int i = 1; i <= m; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		e[cnt].to = v;
		e[cnt].next = head[u];
		e[cnt].w = w;
		head[u] = cnt++;
	}
}

比如上面输入的:

第0条边:1 2 2,就是    e[0].to=2;  e[0].next=head[1]=-1;  e[0].w=2;  head[1]=0;  cnt=1;

第1条边:2 3 2,就是    e[1].to=3;  e[1].next=head[2]=-1;  e[1].w=2;  head[2]=1;  cnt=2;

第2条边:2 4 1,就是    e[2].to=4;  e[2].next=head[2]=1;  e[2].w=1;  head[2]=2;  cnt=3;

第3条边:1 3 5,就是    e[3].to=3;  e[3].next=head[1]=0;  e[3].w=5;  head[1]=3;  cnt=4;

第4条边:3 4 3,就是    e[4].to=4;  e[4].next=head[3]=-1;  e[4].w=3;  head[3]=4;  cnt=5;

第5条边:1 4 4,就是    e[5].to=4;  e[5].next=head[1]=3;  e[5].w=4;  head[1]=5;  cnt=6;

这样就把所有的6条边存完了,那么接下来就是如何遍历所有的边了

for (int i = 1; i <= n; i++)//n个点
{
	for (int j = head[i]; j != -1; j = e[j].next)//遍历以i为起点的边
	{
		cout << i << " -> " << e[j].to << " = " << e[j].weight << endl;
	}
}

比如当i=1时过程如下:

从上面的数据可以知道以1为起点的边有3条,所以有三次循环

第一次循环:j=head[1]=5; e[j].to=4; e[j].weight=4; e[j].next=3;

第二次循环:j=e[j].next=3; e[j].to=3; e[j].weight=5; e[j].next=0;

第三次循环:j=e[j].next=0; e[j].to=2; e[j].weight=2; e[j].next=-1;  此时退出循环,开始找以2为起点的边

到这链式前向星如何存图以及遍历已经解决了,接下来把它结合到Dijkstra算法中


Dijkstra算法

还是以上面的数据作为案例,我们把它复制下来

4 6
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4

 我们知道这是一个计算单源最短路的算法,单源最短路径就是给出一个源点,到目标点的最短距离,比如我们这里给出源点1,到所有其他点的最短路径

 

除了上面的链式前向星存储结构,我们还需要三个数组,D[]——记录最小权值,P[]——记录最短路径中经过了哪些点,F[]——记录该点是否完成计算

先看代码

#define INFINITY 2147483647

void findminpath(int x)//x为源点
{
    int min,temp,cnt=1;
    f[x]=1;
    d[x]=0;
    p[x]=x;
    while(cnt!=n){
        min=INFINITY;
        for(int j=head[x];j!=-1;j=e[j].next){
            if(f[e[j].to]==0&&e[j].w+d[x]<d[e[j].to]){
                d[e[j].to]=e[j].w+d[x];
                p[e[j].to]=x;
            }
        }
        for(int i=1;i<=n;i++){
            if(f[i]==0&&d[i]<min){
                min=d[i];
                temp=i;
            }
        }
        x=temp;
        f[temp]=1;
        cnt++;
    }
}

我们来分析其过程

首先我们选定源点x=1;并将d[]全部初始化为一个很大的值

将d[x]=0,因为源点到自身的最短路径为0;将f[x]=1,表示源点已经完成计算(0代表还没有计算完成);将p[x]=x,因为源点到自身为x->x;同时以源点作为拐点

第一个for循环我们更新所有与拐点有边并且没有计算完成的点,只要当前边的权值加上d[x]比之前的d[i]小,那么就更新,同时将p[i]全部置为拐点

接着第二个for循环我们找除了已经计算完成的点之外的所有点的d[i]的最小值,将那个点作为下一个拐点,将其赋值给x

第一次循环:与拐点1有边并且没有计算完成的点有2、3、4,遍历到点2时,1->2的权值为2,2+d[1]=2<d[2]=INFINITY,所以更新 d[2]=2+d[1]=2,p[2]=x=1;同理3和4就更新为 d[3]=5,d[4]=4,p[3]=x=1,p[4]=x=1;上述我们已经将d[2]、d[3]、d[4]全部更新为2,5,4;很明显这里的最小值为d[2],所以我们将2作为下一个拐点,令x=2、f[x]=1

DPF
1011
2211
3510
4410

第二次循环:d[3]>d[2]+2,所以更新d[3]=4,p[3]=x=2;d[4]>d[2]+1,所以更新d[4]=3,p[4]=x=2;很明显这次4作为下一个拐点,将4置为计算完毕

 DPF
1011
2211
3420
4321

第三次循环:d[3]<d[4]+3,所以不更新d[3]和p[3],这里只剩下3一个了,所以将3置为计算完毕,循环结束。

DPF
1011
2211
3421
4321

所以源点1到其他点的最短路径通过最后一个表格已经出来了

那么如何遍历最短路径经过了哪些点呢,代码如下

tempindex=4;//需要查看的最短路径的目标点,我们这里选4
while (D[tempindex] != 0) //只要D[tempindex]!=0就代表还没有到源点
{
		cout << P[tempindex] << "->" << tempindex << endl;
		tempindex = P[tempindex];
} 

结果为

2->4

1->2

当然你也可以改进一下让它变得更好看

到此我们的基本的Dijkstra就结束了

离字典,将起始节点的距离设为0,其他节点的距离设为无穷大 distances = {node: sys.maxsize for node in graph} distances[start] = 0 # 初始化已访问节点的集合和未访以下是使用问节点D的集ijkstra合 visited = set() unvisited算法求解最短路径的Python = set(graph) while unvisited: # 代码示例: ```python class D选择当前ijkstra距: def __init__(self, graph离最小的节点 , start, current goal): self.graph = graph # 邻接表_node = min(unvisited, key=lambda self node: distances[node]) # 更新.start = start当前节点的 # 起邻居节点点 self.goal =的距离 goal # 终点 for neighbor in graph self.open[current_node]: _list = {} if neighbor in # open 表 self.closed_list unvisited: new_distance = distances[current_node] + = {} graph[current_node][neighbor # closed 表 self.open_list[start] if new_distance] = < distances[neighbor]: 0.0 # 将 distances[neighbor] = new_distance # 将当前起点放入 open_list 中 self.parent = {节点标记start:为已访 None} 问,并从未访问集合中移除 visited.add # 存储节点的父子关系。键为(current_node) 子节点, unvisited值为父.remove(current_node) return节点。方便做最 distances def print后_path(dist路径的ances,回 start溯 self.min, end): _dis = None # 根 # 最短路径的长度 def shortest_path据距离字典和终点节点(self): while True: ,逆向 if self打印路径.open_list is path = [end None: ] print('搜索 current_node =失败 end while current_node !=, 结束!') break distance start: , min_node = for neighbor in graph min(zip[current_node]: if(self.open_list distances[current.values(), self_node] ==.open_list.keys distances[neighbor())) #] + graph 取出距[neighbor][current_node]: 离最小的节点 self path.open_list.pop.append(min_node)(neighbor) current_node = neighbor break path.reverse() # 将其从 open_list 中去除 self print.closed("_list[minShortest_node] = path from", distance # 将节点加入 closed start, "to", end,_list ":", "->".join(path)) # 示例 中 if min_node == self.goal: # 如果节点为图的邻接矩阵终点 self.min_dis = distance 表示 graph shortest = { _path = [ 'Aself.goal]': {'B': # 5, 'C 记录从': 终1}, 点回溯的路径 'B
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皮城大学生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值