贪心算法与数据结构结合1——单源最短路径问题:Dijkstra算法

万众一心,共同抗击灾难。这是我们国家自古以来的美德
国家遭受重大灾情,每个中国人都是有义务去解决这样的重大问题。
国家紧紧和老百姓拴在一起,凝聚着中国魂,中国力量,中国精神!

数据结构中的有一个图论问题——单源最短路径问题,而解决这一问题的算法是Dijkstra算法。而本篇主要介绍单源最短路径问题和Dijkstra算法

1、单源最短路径问题

什么是单源最短路径问题

给定带权有向网络G=(V,E,W),每条边e=<i,j>的权w(e)为非负实数,表示从i到j的距离。源点s属于V
求:从s出发到达其他结点的最短路径
例如这个图

我们以源点1出发寻找到达其他结点的最短路径
我们从1出发1->6->2是结点1到结点2的最短路径,这个路径的长度是3+2=5
我们从1出发1->6->2->3是结点1到结点3的最短路径,这个路径的长度是3+2+7=12
我们从1出发1->6->4是结点1到结点4的最短路径,这个路径的长度是3+6=9
我们从1出发1->6->5是结点1到结点5的最短路径,这个路径的长度是3+1=4
我们从1出发1->6是结点1到结点6的最短路径,这个路径的长度是3

那么如何找到这个最短路径呢
我们可以用贪心法来找最短路径
这个贪心法就是著名的Dijkstra算法
下面我们来看一下Dijkstra算法

2、Dijkstra算法

什么是Dijkstra算法

Dijkstra算法是解决图论中单源最短路径问题的算法
把顶点集合V分成两组:
(1)S:已求出的顶点的集合(初始时只含有源点V0)
(2)V-S:尚未确定的顶点集合

在G={V,E}中
1、初始时令 S={V0},V-S={其余顶点},T中顶点对应的距离值;若存在<V0,Vi>,d(V0,Vi)为<V0,Vi>弧上的权值;若不存在<V0,Vi>,d(V0,Vi)为∞

2、从V-S中选取一个与S中顶点有关联边且权值最小的顶点W,加入到S中

3、对其余V-S中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值

4、重复上述步骤2、3,直到S中包含所有顶点,即W=Vi为止

下面我们对算法举个例子:
我们还是对上面那个问题进行分析

我们把源点1选到S集合里面,而V-S={2,3,4,5,6}
选完之后就要实时更新边
这里dist[i]:从w到i相对S最短路径长度

我们看到啊
源点1到源点1的长度为0
源点1到源点2的长度为10
源点1到源点6的长度为3
而源点1到3,4,5是不可到达的,也就是没有有向线段是从源点1出发指向3,4,5的。故它们的长度是正无穷

接下来我们就在所更新的边(dist数组)里面找到一个长度最小的值,我们看到dist[6]长度最小
然后把源点6选到S集合里
那么S={1,6},V-S={2,3,4,5}
我们看到啊,在源点6进到集合里后,其他点的最短路,可以经过源点6。那么路线可能会被更新

这里,源点1可以经过源点6到源点2,即1->6->2,最短路径长度被更新为3+2=5
源点1可以经过源点6到源点2,即1->6->2,最短路径长度被更新为3+2=5
源点1可以经过源点6到源点4,即1->6->4,最短路径长度被更新为3+6=9
源点1可以经过源点6到源点2,即1->6->5,最短路径长度被更新为3+1=4
而还是没有一条有向线段经过源点3,故dist[3]=∞

然后再在所更新的边里面找到剩下的一个长度最小的值,我们看到dist[5]长度最小
于是把源点5选到S集合里
然后再计算并更新边

再将源点2选进去
然后再更新边

我们此时看到源点2有一条有向线段经过源点3,更新路径dist[3]=12
dist[4]长度最小
于是把源点4选到集合S里
然后更新边

最后将源点3选到S集合里去,那个这个图和旁边的最短路径就是原问题的解

如何说明这个算法是正确的呢,下面我们用数学归纳法进行证明

数学归纳法证明Dijkstra算法

我们有一个命题:当算法进行到第k步时,对于S中每个结点i,有dist[i]=short[i]
首先对于k=1时,S={s},只有一个点的时候,没有有向边。因此只有一个点的时候,其最短路是0,dist[s]=short[s]=0,显然归纳基础成立。

假设命题对k为真,下面证明对k+1也为真
也就是证明在k+1步算法中,选择顶点v,需要证明dist[v]=short[v]
如若不然,存在另一条s-v路径的一个最短路径L

这个路径L是图中绿色的路线,最后一次出S集合的顶点last_point,经过V-S的第一个顶点y,再有y经过一段在V-S中的路径到达v。

在k+1步中选择顶点v时,上面是假设路线,下面是算法选择的路线
我们看到last_point是不能和顶点v相连,因为在k+1步时,算法选择了一条最短路线,在S集合里的u指向了S集合外的v;所以如果last_point直接和v相连,此时L必然>=dist[v],不是最短路径。所以last_point要经过多点与v相连,从而找到一条最短路径。

如果last_point与y点相连,在k+1步时,算法选择了v点而不是y点,所以dist[v]<=dist[y]。
令y到v的路径长度为d(y,v)
因此有dist[y]+d(y,v)=L=dist[v]=short[v]
而又因为dist[v]<=dist[y],dist[y]<=L
所以dist[v]<=L。这与L=dist[v]矛盾。所以不存在一个相对算法更小的最短路径L,故Dijkstra算法是正确的。因此,得证。

下面分析Dijkstra算法的代码
我们将选好的点集存放在数组里,定义一个图集类的变量存放边或者进行更新边的操作。首先进行初始化,将源点1加进去,并将1到各点的边的数值加到图集类的变量中去(这个变量也是个数组),然后遍历图集类数组变量,如果找到一条最短的边,则把下标记下来,便于进行下一步寻找最短边和更新边操作以及求解。然而更新边操作,我们找到了一个最短边相连的点,将这个点也加到了集合里去了,再判断这个点和其他边+上一个求得最小边小于原始的边,则进行更新。可能看起来有点绕,一会可以从代码中体会

下面上代码

public class Graph {
	int v1;  //v1顶点
	int v2;  //v2顶点
	int len;  //权值
	
	public Graph() {
		// TODO Auto-generated constructor stub
	}

	public Graph(int v1, int v2, int len) {
		super();
		this.v1 = v1;
		this.v2 = v2;
		this.len = len;
	}
	
	
}

public static void main(String[] args) {
		// TODO Auto-generated method stub
		//设置权重:自己和自己连的是0,没有边的是-1,其他的都是自己权重值
				int[][] a = {
		                {0,10,-1,-1,-1,3},
		                {-1,0,7,5,-1,-1},
		                {-1,-1,0,-1,-1,-1},
		                {3,-1,4,0,7,-1},
		                {-1,-1,-1,-1,0,-1},
		                {-1,2,-1,6,1,0}
		        };
				
				CreateGraph(a, a.length);
				
				MinPath(a);
	}
	
	//创建图
	public static void CreateGraph(int a[][],int m) {
		for(int i=0;i<m;i++)
			for(int j=0;j<m;j++)
				if(a[i][j]==-1||a[i][j]==0)
					a[i][j]=0x3f3f3f3f;
	}
	
	//贪心算法解单源最短路径问题
	public static void MinPath(int a[][]) {
		int k=0;  //表示正在访问的点
		int min=0;
		int pointWeight[]=new int[a.length];  //记录权值
		int numPoint=a.length;
		Boolean p[]=new Boolean[a.length];  //判断哪些点已经被取到了
		Graph minWeight[]=new Graph[a.length];  
		Graph tracing[]=new Graph[a.length];  //单源最短路径问题的解
		
		minWeight[0]=new Graph(0, 0, a[0][0]);
		p[0]=true;
		for(int i=1;i<a.length;i++) {
			minWeight[i]=new Graph(0,i,a[0][i]);
			p[i]=false;
		}
		
		for(int i=1;i<a.length;i++) {
			min=0x3f3f3f3f;
			for(int j=0;j<a.length;j++)
				if(!p[j]&&minWeight[j].len<min) {
					min=minWeight[j].len;
					k=j;
				}
			
			p[k]=true;  //找到一个最小权值的点
			tracing[i-1]=minWeight[k];
			pointWeight[k]=min;
			numPoint--;
			//更新当前最短路径
			for(int j=0;j<a.length;j++) {
				if(!p[j]&&(min+a[k][j])<minWeight[j].len) {
					minWeight[j]=new Graph(k,j,min+a[k][j]);
				}
			}
		}
		if(numPoint==1)
			TracingMixPath(tracing, pointWeight);
		else
			System.out.println("没有最短路径");
	}
	
	//遍寻单源最短路径
	public static void TracingMixPath(Graph tracing[],int pointWeight[]) {
		System.out.println("可连接的最短路径:");
		for(int i=0;i<tracing.length-1;i++) 
	    	System.out.println((tracing[i].v1+1)+"-->"+(tracing[i].v2+1)+"          ");
		
		System.out.println("此时最短路径的长度分别是:");
		for(int i=0;i<pointWeight.length;i++)
			System.out.println("short["+(i+1)+"]="+pointWeight[i]);
	}

下面分析Dijkstra算法

分析Dijkstra算法

代码采用了带权邻接矩阵进行分析的,所以说时间复杂度就是O(n^2)
我们看下代码,在第一个for循环是遍寻所有点,第二个for循环是找到最小边,在第二个for循环出来之后才有了第三个for循环,这个循环是更新边
所以时间复杂度也是O(n^2)

综上Dijkstra算法的时间复杂度就是O(n^2)

如果采用了基于堆实现的优先队列的数据结构,可以将算法时间复杂度降到O(nlogn)
对于这个的实现,大家可以自己研究下。这里就不再详细介绍了

######          积力之所举,则无不胜也;众智之所为,则无不成也。(刘安《淮南子·主术训》)

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值