1、引入
当我们通过网络浏览网页、 发送电子邮件、 QQ消息传输的时候, 数据会在联网设备之间流动, 如图, 当PC上的浏览器向服务器请求一个网页时, 请求信息需要:先通过本地局域网,由路由器A发送到Internet,请求信息沿着Internet中的众多路由器传播,最后到达服务器本地局域网所属的路由器B,从而传给服务器。
我们可以将互联网路由器体系表示为一个带权边的图,路由器作为顶点,路由器之间网络连接作为边,权重可以包括网络连接的速度、网络负载程度、分时段优先级等影响因素,作为一个抽象,我们把所有影响因素合成为单一的权重,如下图所示
2、功能分析
(1)算法简介
解决信息在路由器网络中选择传播速度最快路径的问题, 就转变为在带权图上最短路径的问题。这个问题与广度优先搜索BFS算法解决的词梯问题相似, 只是在边上增加了权重,如果所有权重相等,还是还原到词梯问题,解决该问题的算法为Dijkstra算法。
(2)算法实现
❖顶点的访问次序由一个优先队列来控制,队列中作为优先级的是顶点的dist属性。
❖最初, 只有开始顶点dist设为0, 而其他所有顶点dist设为sys.maxsize(最大整数) , 全部加入优先队列。
❖随着队列中每个最低dist顶点率先出队
❖并计算它与邻接顶点的权重, 会引起其它顶点dist的减小和修改, 引起堆重排
❖并据更新后的dist优先级再依次出队
3、代码实现
class Graph:
# 图结构初始化
def __init__(self):
self.vertices = {}
self.numVertices = 0
# 增加节点
def addVertex(self, key):
self.numVertices = self.numVertices + 1
newVertex = Vertex(key)
self.vertices[key] = newVertex
return newVertex
# 获取节点
def getVertex(self, n):
if n in self.vertices:
return self.vertices[n]
else:
return None
# 特殊方法 in
def __contains__(self, n):
return n in self.vertices
# 添加边
def addEdge(self, f, t, cost=0):
if f not in self.vertices:
nv = self.addVertex(f)
if t not in self.vertices:
nv = self.addVertex(t)
self.vertices[f].addNeighbor(self.vertices[t], cost)
# 获取所有顶点的key值
def getVertices(self):
return list(self.vertices.keys())
# 特殊方法 迭代器
def __iter__(self):
return iter(self.vertices.values())
class Vertex:
# 顶点初始化
def __init__(self, num):
self.id = num
self.connectedTo = {}
self.color = 'white' # 颜色
self.dist = sys.maxsize # 距离
self.pred = None # 前驱节点
self.disc = 0 # 开始时间
self.fin = 0 # 结束时间
# 比较大小
# def __lt__(self,o):
# return self.id < o.id
# 添加邻边节点
def addNeighbor(self, nbr, weight=0):
self.connectedTo[nbr] = weight
# 设置颜色
def setColor(self, color):
self.color = color
# 设置距离
def setDistance(self, d):
self.dist = d
# 设置前驱
def setPred(self, p):
self.pred = p
# 设置发现时间
def setDiscovery(self, dtime):
self.disc = dtime
# 设置结束时间
def setFinish(self, ftime):
self.fin = ftime
# 获取结束时间
def getFinish(self):
return self.fin
# 获取发现时间
def getDiscovery(self):
return self.disc
# 获取前驱
def getPred(self):
return self.pred
# 获取距离
def getDistance(self):
return self.dist
# 获取颜色
def getColor(self):
return self.color
# 获取连接边
def getConnections(self):
return self.connectedTo.keys()
# 获取与某个节点间的权重值
def getWeight(self, nbr):
return self.connectedTo[nbr]
# 特殊方法 print
def __str__(self):
return str(self.id) + ":color " + self.color + ":disc " + str(self.disc) + ":fin " + str(
self.fin) + ":dist " + str(self.dist) + ":pred \n\t[" + str(self.pred) + "]\n"
# 获取Id
def getId(self):
return self.id
class PriorityQueue:
def __init__(self):
self.heapArray = [(0, 0)]
self.currentSize = 0
def buildHeap(self, alist):
self.currentSize = len(alist)
self.heapArray = [(0, 0)]
for i in alist:
self.heapArray.append(i)
i = len(alist) // 2
while (i > 0):
self.percDown(i)
i = i - 1
def percDown(self, i):
while (i * 2) <= self.currentSize:
mc = self.minChild(i)
if self.heapArray[i][0] > self.heapArray[mc][0]:
tmp = self.heapArray[i]
self.heapArray[i] = self.heapArray[mc]
self.heapArray[mc] = tmp
i = mc
def minChild(self, i):
if i * 2 > self.currentSize:
return -1
else:
if i * 2 + 1 > self.currentSize:
return i * 2
else:
if self.heapArray[i * 2][0] < self.heapArray[i * 2 + 1][0]:
return i * 2
else:
return i * 2 + 1
def percUp(self, i):
while i // 2 > 0:
if self.heapArray[i][0] < self.heapArray[i // 2][0]:
tmp = self.heapArray[i // 2]
self.heapArray[i // 2] = self.heapArray[i]
self.heapArray[i] = tmp
i = i // 2
def add(self, k):
self.heapArray.append(k)
self.currentSize = self.currentSize + 1
self.percUp(self.currentSize)
def delMin(self):
retval = self.heapArray[1][1]
self.heapArray[1] = self.heapArray[self.currentSize]
self.currentSize = self.currentSize - 1
self.heapArray.pop()
self.percDown(1)
return retval
def isEmpty(self):
if self.currentSize == 0:
return True
else:
return False
def decreaseKey(self, val, amt):
# this is a little wierd, but we need to find the heap thing to decrease by
# looking at its value
done = False
i = 1
myKey = 0
while not done and i <= self.currentSize:
if self.heapArray[i][1] == val:
done = True
myKey = i
else:
i = i + 1
if myKey > 0:
self.heapArray[myKey] = (amt, self.heapArray[myKey][1])
self.percUp(myKey)
def __contains__(self, vtx):
for pair in self.heapArray:
if pair[1] == vtx:
return True
return False
def dijkstra(aGraph,start):
pq = PriorityQueue()
start.setDistance(0)
pq.buildHeap([(v.getDistance(),v) for v in aGraph])
while not pq.isEmpty():
currentVert = pq.delMin()
for nextVert in currentVert.getConnections():
newDist = currentVert.getDistance()+currentVert.getWeight(nextVert)
if newDist<nextVert.getDistance():
nextVert.setDistance(newDist)
nextVert.setPred(currentVert)
pq.decreaseKey(nextVert,newDist)
4、算法分析
- Dijkstra算法只能处理大于0的权重,如果图中出现负数权重,则算法会陷入无限循环
- 首先, 将所有顶点加入优先队列并建堆,时间复杂度为O(|V|)
- 其次, 每个顶点仅出队1次, 每次delMin花费O(log|V|), 一共就是O(|V|log|V|)
- 另外, 每个边关联到的顶点会做一次decreaseKey操作(O(log|V|)), 一共是O(|E|log|V|)
- 上 面 三 个 加 在 一 起 , 数 量 级 就 是O((|V|+|E|)log|V|)