IP动态选路和最短路算法

时间:2021-05-02 11:12:39

RIP使用Bellman-Ford算法

在开始之前呢,我们先了解一下Bellman-Ford算法吧!

Bellman-Ford算法(Dijstra算法也是)是来自于动态规划。动态规划的两点特征:最优子结构和重叠子问题。

首先是最优子结构问题:

最短路径的子路径也是最短路径:从vi经过vj到vk的最短路必须要经过的子路径vj到vk的最短路和子路径vi到vj的最短路,就是说两个子路径必须都是最短路,他们之和才有可能是最短路径。

其次是重叠子问题:

如果我们要求从vi到vz的最短路径,那么我们也要求出从vi到vj,直至 vy的最短路径,每一次求解的过程中都会出现重叠的子问题。比如,求从vi经过vj到vk的最短路,就需要先求得vi到vj的最短路,这就是vi到vk的子问题,而且这个子问题不只出现在当前vi

到vk点最短路的问题中,也会出现在vi到vj最短路的问题中,他们的子问题就是重叠的。

由此我们得到动态规划的动态转移方程:

dist[eage[j].v] = min(dist[eage[j].v] ,dist[eage[j].u] + eage[j].w)

eage[j].v表示的是v点,eage[j].u表示的是u点,eage[j].w表示从v到u的距离;

而dist[]数组表示从源点s到当前点的最短路径长度。

这就是松弛操作,而证明如下:

假设从源点s到定点v的最短路径是dist[v],加入存在从源点一条经过点u到达v的路径dist[j]+w,并且dist[j]+w比dist[v]更短,这和最短路径相矛盾,那么要对最短路径进行更新。

以下是bellman-ford的表述:

数据结构:

struct {        
int u, v;
int w;
}eage[MAX_SIZEE] = {0};
int dist[MAX_SIZEN];//访问记录

算法:

void bellman_ford(){
int i, j;

for(i = 1; i < node; ++i)
dist[i] = MAX;

dist[1] = 0;//找个初始点

for(i = 1; i < n; i++)
{
for(j = 1; j < count; j++)//count总边数
{
if(dist[eage[j].v] > dist[eage[j].u] + eage[j].w)//松弛操作
dist[eage[j].v] = eage[j].w + dist[eage }
}
}

其实,bellman-ford算法就是基于这样一个事实,在不含负环的前提下,最短路径树的高度最高只能是n(图中点的总数),就是说,从源点最多把图中的所有点走完从而得到一条最短路。

所以算法的最外层循环是对总的点数进行计数,保证考虑到这棵最短路径树高度最高的情况。每次外层循环进行一次循环,内层循环要把所有的边进行一次松弛操作,以保证得到当前高度的路径树的最短路。当外层达到n(最短路径树的最高高度)时,那么说明当前的单源最短路已经得到。

那么RIP是如何使用这个算法呢?

前提条件:我们首先假设当前的网络中已经n个路由(节点),他们已经各自算出了各自的最短路径树,就是树高最高为n的一棵最短路径树。

情况1:此时,我们在当前网络中加入一个路由A(节点),当前该网络图中有n+1个节点,那么很显然,当前节点A的最短路径树最高也就是n+1,当前路由A(节点)相连的路由B(节点)已经有了一张路由表(树高最高为n的一棵最短路径树),那么当前节点A只需要把所有相邻路由B传过来的路由表(路由表中包含路由(点)到路由(点)之间边的长度(度量))进行计算,就可以得到路由表(一棵树高最高为n+1的最短路径树)。再把自己求得的路由表(最短路径树)进行广播,然后其他的路由C(B包含在C中)再对所有路由到路由的度量(边)进行一次松弛操作,很容易的就都建立起一颗以自身为源点的路由表(最大高度为n+1的最短路径树)。

我们可以知道,像这种增加节点可以很快的收敛,RIP的好事总是传播的很快。

情况2:我们在当前网络中删除一个路由A(节点),那么当前网络图的节点有n个,只有当前节点A的相邻节点B才知道当前路由A被移除了,那么当前路由A的相邻节点B通知其他路由节点C(B不包含在C中),该路由度量值(A到B的边)被置为16(不可达),然后其他路由C通知该路由的相邻节点B,一张含C到B(度量值为2)的点,为什么度量值为2?

因为A->B的度量值是1,B->C度量值是1,所以B->C度量值是2。因为C虽然知道它需要通过B才能到达A,但是在RIP传输路由表(详见RIP路由信息)时并没有下一站出现(B并不知道C是通过自己到达A的),所以B会被C误导,误以为可以通过C到达A,所以B到A的度量值变成2+1,然后进行广播,C会收到B的当前路由表,但是C知道它要通过B到达A,又把到A的度量值设为3+1。。。。。。然后两个逗比不停的交换直到双方到A的RIP度量值变成16,最后才把节点A的路由删除了。

所以,故障出现时,RIP收敛的很慢。

OSPF和dijstra最短路算法

dijstra也是用了松弛操作,不过它并不像bellman-ford那么笨,他使用了贪心算法对bellman-ford算法进行了优化。

它把图中的所有顶点分成了已经找到了最短路径的顶点S和还没找到最短路径的定点V-S。每次在V-S中找到一个和源点最近的点,然后加入S集合中,并对未进入S集合点进行松弛操作,知道最后把所有点都加入S集合中。

这里为什么可以应用贪心呢?为什么可以每次在V-S中找到一个和源点最近的点,然后加入S集合中?如何保证这是该点和源点的最短路就是当前长度而不受其他未加入S集合的点的影响?

证明:假设下一条路径上有一个定点vj不在S集合中,即当前最短路径为(v0..vj..vi)。显然,(v0..vj)的长度要小于(v0..vj..vi)的长度,所以下一条最短路径应该是(v0…vj)。这和题设的最短路径(v0..vj..vi)相矛盾,所以下一条最短路径不存在一个点vj不在S中的顶点vj。就是说,下一条最短路径到vj,路径上经过的所有点都是在S中的。所以,我们总是可以利用贪心,把当前V-S集合的最短路加入S集合中,因为当前最短路径总是可以保证路径上所有的点都在S中的。

那么每一条最短路径最多只有两种情况,或者是直接从源点到达vi,或者是通过源点到S集合中某点vk的最短路径(最短路径的子路径也是最短路

径)再到达vi。由此,我们可以再次利用松弛dist[eage[j].v] = min(dist[eage[j].v] ,dist[eage[j].u] + eage[j].w)求得最短路径。算法如下(选择最小权值路径可以利用堆排进行优化)。

struct{        
int v;//顶点数
int e;//边数
int p[MAX_SIZE][MAX_SIZE];//邻接矩阵
}page;
void djst()
{
int min;
int i, j, k, t =1;
for(i = 1; i <=page.v; i++ )
{
dist[i] = page.p[1][i];//记录各条最小路长度
}
path[1] = 1;//表示i = 0 这个节点已经选过
for(i = 2; i<=page.v; i++)//寻找各条最短路径
{
min = MAX;
for(j = 1; j<= page.v; j++)//选择最小权值路径
if(!path[j]&&dist[j] < min)
{
k = j;
min = dist[j];
}

if(min == MAX) return;
path[k]= 1;//记录下k已经被选择过了
for(j = 1; j <= page.v; j++)
{
if(!path[j]&&page.p[k][j] < MAX && dist[k]+page.p[k][j] < dist[j])
dist[j] = dist[k] + page.p[k][j];//松弛操作
}
}
}

OSPF每次都向整个网络中的所有路由器发送信息。使用洪泛法,相邻的接收到信息路由又将此信息发往其所有的相邻路由器。最后整个区域的路由器都会得到这个信息的一个副本。使用这种方法,各个路由器之间不断的交换链路状态信息,因此所有的路由器最终都能建立一个链路状态数据库(全网的网络拓扑结构图)。

所以网络的拓扑结构一发生改变,OSPF可以较快的更新(因为它不像RIP只知道所有网络的距离,和下一跳的路由器),各个路由器都能及时更新路由表,所以收敛的快。

OSPF能通过全网的拓扑结构进行Dijstra最短路算法,而RIP却不能使用Dijstra,因为使用RIP永远不知道最短的那个相邻路由什么时候到来,却也不建立全网拓扑结构对网络结构进行保存,所以Dijstra不适合RIP。