方法名 | 比较运算符 | 含义 |
---|---|---|
__eq__ |
== | equal |
__lt__ |
less than | |
__le__ |
= | less and equal |
__gt__ |
> | greater than |
__ge__ |
>= | greater and equal |
但很遗憾,python库自带的优先队列from queue import PriorityQueue
,并不满足本文的需求。当PriorityQueue的元素为对象时,需要该对象的class实现__lt__函数,在往优先队列里添加元素时,内部是用的堆排序,堆排序的特点为每个堆(以及每个子堆)的第一个元素总是那个最小的元素。关键在于,在建立了这个堆后,堆就已经记录下来了创建堆时各个元素的大小关系了,在创建优先队列后,再改变某个对象的值,这个堆的结构是肯定不会变的,所以这种堆排序造成了排序是一次性的,如果之后某个对象的属性发生变化,堆的结构也不会随之而改变。
或者说,我们想要的优先队列肯定不是系统提供的优先队列,因为我们要支持可变对象的成员修改导致堆的改变,解决方案有三种,1.内部使用的堆排序的堆,最起码要支持,删除任意节点和增加节点操作(因为这两步就可以达到修改的效果了)2.这个内部堆,在执行出队操作时,考察哪个节点有修改操作,再把堆改变到正确的形态,再出队3.维护一个list,进行排降序,然后每改变一个可变对象的值,就对这个对象进行冒泡或者二分查找找到位置(因为别的都是已经排好序的了,只有它不在正确的位置),最后再list.pop(),但第三个方案是我后来想到的,所以下面代码并不是这样实现的,读者可以进行尝试,肯定比每次遍历全部快。
应该说,可能用不上队列了。我们可能只需要一个list或者set来存储v,在出队前随便vi改变其dist,在出队时再遍历找到最小的dist的vi,再删除掉这个vi即可。因为vi的dist一直在变,需求特殊,但是没必要专门造个轮子(感觉这个轮子也不好造),虽然时间复杂度可能高了点,但代码简单了啊。
失效代码如下:三个节点对象的dist都是无穷大,在三个对象都进入队列,再把v3的dist改成0,想要的效果是出队出v3,但出队出的是v1。原因如上:
from queue import PriorityQueue class Vertex: #顶点类 def __init__(self,vid,dist): self.vid = vid self.dist = dist def __lt__(self,other): return self.dist other.dist v1=Vertex(1,float('inf')) v2=Vertex(2,float('inf')) v3=Vertex(3,float('inf')) vlist = [v1,v2,v3] q = PriorityQueue() for i in range(0,len(vlist)): q.put(vlist[i]) v3.dist = 0 print('vid:',q.get().vid)#结果为vid: 1
而如果将在入队前,就把dist改变了,就能正确的出队。
v3.dist = 0 for i in range(0,len(vlist)): q.put(vlist[i]) #结果为vid: 3
class Vertex: #顶点类 def __init__(self,vid,outList): self.vid = vid#出边 self.outList = outList#出边指向的顶点id的列表,也可以理解为邻接表 self.know = False#默认为假 self.dist = float('inf')#s到该点的距离,默认为无穷大 self.prev = 0#上一个顶点的id,默认为0 def __eq__(self, other): if isinstance(other, self.__class__): return self.vid == other.vid else: return False def __hash__(self): return hash(self.vid) #创建顶点对象 v1=Vertex(1,[2,4]) v2=Vertex(2,[4,5]) v3=Vertex(3,[1,6]) v4=Vertex(4,[3,5,6,7]) v5=Vertex(5,[7]) v6=Vertex(6,[]) v7=Vertex(7,[6]) #存储边的权值 edges = dict() def add_edge(front,back,value): edges[(front,back)]=value add_edge(1,2,2) add_edge(1,4,1) add_edge(3,1,4) add_edge(4,3,2) add_edge(2,4,3) add_edge(2,5,10) add_edge(4,5,2) add_edge(3,6,5) add_edge(4,6,8) add_edge(4,7,4) add_edge(7,6,1) add_edge(5,7,6) #创建一个长度为8的数组,来存储顶点,0索引元素不存 vlist = [False,v1,v2,v3,v4,v5,v6,v7] #使用set代替优先队列,选择set主要是因为set有方便的remove方法 vset = set([v1,v2,v3,v4,v5,v6,v7]) def get_unknown_min():#此函数则代替优先队列的出队操作 the_min = 0 the_index = 0 j = 0 for i in range(1,len(vlist)): if(vlist[i].know is True): continue else: if(j==0): the_min = vlist[i].dist the_index = i else: if(vlist[i].dist the_min): the_min = vlist[i].dist the_index = i j += 1 #此时已经找到了未知的最小的元素是谁 vset.remove(vlist[the_index])#相当于执行出队操作 return vlist[the_index] def main(): #将v1设为顶点 v1.dist = 0 while(len(vset)!=0): v = get_unknown_min() print(v.vid,v.dist,v.outList) v.know = True for w in v.outList:#w为索引 if(vlist[w].know is True): continue if(vlist[w].dist == float('inf')): vlist[w].dist = v.dist + edges[(v.vid,w)] vlist[w].prev = v.vid else: if((v.dist + edges[(v.vid,w)])vlist[w].dist): vlist[w].dist = v.dist + edges[(v.vid,w)] vlist[w].prev = v.vid else:#原路径长更小,没有必要更新 pass main() print('v1.prev:',v1.prev,'v1.dist',v1.dist) print('v2.prev:',v2.prev,'v2.dist',v2.dist) print('v3.prev:',v3.prev,'v3.dist',v3.dist) print('v4.prev:',v4.prev,'v4.dist',v4.dist) print('v5.prev:',v5.prev,'v5.dist',v5.dist) print('v6.prev:',v6.prev,'v6.dist',v6.dist) print('v7.prev:',v7.prev,'v7.dist',v7.dist)
运行结果与数据变化表的最终情况一致。
把以下代码和以上代码合起来就可以运行成功,使用递归的思想来做:
def real_get_traj(start,index): traj_list = [] def get_traj(index):#参数是顶点在vlist中的索引 if(index == start):#终点 traj_list.append(index) print(traj_list[::-1])#反转list return if(vlist[index].dist == float('inf')): print('从起点到该顶点根本没有路径') return traj_list.append(index) get_traj(vlist[index].prev) get_traj(index) print('该最短路径的长度为',vlist[index].dist) real_get_traj(1,3) real_get_traj(1,6)
如图所示,从v1到v3的最短路径为:[1, 4, 3]
从v1到v6的最短路径为:[1, 4, 7, 6]
Dijkstra算法要求边上的权值不能为负数,不然就会出错。如上,本来最短路径是012,但由于算法是贪心的,所以只会直接选择到2
注意,只有有向无圈图才有拓扑排序。
如果知道图是无圈图,那么我们可以通过改变声明顶点为known的顺序(原本这个顺序是,每次从unknown里面找出个最小dist的顶点),或者叫做顶点选取法则,来改进Dijkstra算法。新法则以拓扑排序选择顶点。由于选择和更新(每次选择和更新完成后,就会变成数据变化表中的某一种情况)可以在拓扑排序执行的时候进行,因此算法能一趟完成。
因为当一个顶点v被选取以后,按照拓扑排序的法则它肯定没有任何unknown顶点到v(指明方向)的入边,因为v的距离 d v d_v dv不可能再下降了(因为根本没有别的路到v了),所以这种选择方法是可行的。
使用这种方法不需要优先队列。
到此这篇关于python3实现Dijkstra算法最短路径的实现的文章就介绍到这了,更多相关python3 最短路径内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!