dijkstra算法_典型算法思想与应用7|贪心算法与最短路径问题

贪心法是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。通常可以首先证明问题的一个整体最优解,是从贪心选择开始的,而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解。

在对问题求解时,贪心法总是做出在当前看来是最好的选择。也就是说,暂时不从整体最优上加以考虑,当时所做出的仅是在某种意义上的局部最优解,然后把子问题的局部最优解合成原来问题的一个解,称为贪心法。

贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关

1 总体思路

(1) 建立数学模型来描述问题。

(2) 把求解的问题分成若干个子问题。

(3) 对每一子问题求解,得到子问题的局部最优解。

(4) 把子问题的局部最优解合成原来问题的一个解。

2 贪心算法适用的问题

贪心策略适用的前提是:局部最优策略能导致产生全局最优解。实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。

贪婪算法可解决的问题通常大部分都有如下的特性:

① 随着算法的进行,将积累起其它两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。

② 有一个函数来检查一个候选对象的集合是否提供了问题的解答。该函数不考虑此时的解决方法是否最优。

③ 还有一个函数检查是否一个候选对象的集合是可行的,也即是否可能往该集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。

④ 选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。

⑤ 最后,目标函数给出解的值。

为了解决问题,需要寻找一个构成解的候选对象集合,它可以优化目标函数,贪婪算法一步一步的进行。起初,算法选出的候选对象的集合为空。接下来的每一步中,根据选择函数,算法从剩余候选对象中选出最有希望构成解的对象。如果集合中加上该对象后不可行,那么该对象就被丢弃并不再考虑;否则就加到集合里。每一次都扩充集合,并检查该集合是否构成解。如果贪婪算法正确工作,那么找到的第一个解通常是最优的。

3 贪心算法的实现框架

从问题的某一初始解出发;while(能朝给定总目标前进一步){    利用可行的决策,求出可行解的一个解元素;}由所有解元素组合成问题的一个可行解;

4 贪心策略的选择

因为用贪心算法只能通过解局部最优解的策略来达到全局最优解,因此,一定要注意判断问题是否适合采用贪心算法策略,找到的解是否一定是问题的最优解。

贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。动态规划主要运用于二维或三维问题,而贪心一般是一维问题 。

5 典型应用:最短路径问题

贪心算法有诸多典型应用,如马踏棋盘,求最小生成树的Prim算法和Kruskal算法都是漂亮的贪心算法。

Dijkstra 算法是解决单源最短路径问题的贪心算法,它先求出长度最短的一条路径,再参照该最短路径扩充和更新到其它各点的路径,直到求出从源点到其他各个顶点的最短路径。

Dijkstra算法的基本思想是首先假定源点为u,顶点集合V被划分为两部分:集合S和 V-S(用一个布尔数组表示)。初始时 S 中仅含有源点 u,其中 S 中的顶点到源点的最短路径已经确定。集合V-S中所包含的顶点到源点的最短路径的长度待定,称从源点出发只经过S中的点到达V-S中的点的路径为特殊路径,并用数组dist[]记录当前源点到每个顶点的最短路径长度。然后选择特殊路径长度最短的路径,将其连接的V-S中的顶点加入到集合S中,同时更新数组dist[]。一旦S包含了所有顶点,dist[]就是从源到所有其他顶点之间的最短路径长度。

6047d32b0d03e24674716f96c232c990.png

① 数据结构

设置地图的带权邻接矩阵为map[][],即如果从源点 u 到顶点 i 有边,就令 map[u][i] 等于 (u,i) 的权值,否则 map[u][i]=∞(无穷大);采用一维数组 dist[i]来记录从源点 u 到 i 顶点的最短路径长度;采用一维数组p[i]来记录最短路径上 i 顶点的前驱。用布尔数组flag[]来表示集合V和V-S,如果flag[i]等于true,说明顶点i已经加入到集合S,否则顶点i属于集合V-S。

② 初始化

令集合S={u},对于集合V-S中的所有顶点x,初始化dist[i]=map[u][i],如果源点u到顶点i有边相连,初始化p[i]=u,否则p[i]= -1。

③ 找最小

在集合V-S中依照贪心策略来寻找使得dist[j]具有最小值的顶点 t,即dist[t]=min(dist[j] | j属于V-S集合),则顶点 t 就是集合V-S中距离源点 u 最近的顶点。

④ 加入S战队

将顶点 t 加入集合S中,同时更新V-S。

⑤ 判断是否计算结束

如果集合V-S为空,算法结束,否则转⑥。

⑥ 借东风

在③中已经找到了源点到 t 的最短路径,那么对集合V-S中所有与顶点 t 相邻的顶点 j,都可以借助 t 走捷径。如果dist[j]>dist[t]+map[t][j],则dist[j]=dist[t]+map[t][j],记录顶点 j 的前驱为 t,有p[j]= t,转③。

由此,可求得从源点 u 到图G的其余各个顶点的最短路径及长度,也可通过数组 p[] 逆向找到最短路径上经过的城市。

3 完美图解

现在我们有一个景点地图,如图10所示↓,假设从1号结点出发,求到其他各个结点的最短路径。

4cef9e5f82838251056c8665a32cfbf2.png

算法步骤如下。

3.1 数据结构

设置地图的带权邻接矩阵为map[][],即如果从顶点 i 到顶点 j 有边,则map[i][j]等于(i,j)的权值,否则map[i][j]=∞(无穷大),如图11所示↑。

3.2 初始化

令集合S={1},V-S={2,3,4,5},对于集合V-S中的所有顶点 x,初始化最短距离数组dist[i]=map[1][i],dist[u]=0,如图12所示↓。如果源点1到顶点i有边相连,初始化前驱数组p[i]=1,否则p[i]= -1,如图13所示↓。

5fa37bb97567c7b0909ad76742c1ca41.png

3.3 找最小

在集合V-S={2,3,4,5}中,依照贪心策略来寻找V-S集合中 dist[] 最小的顶点 t,如图14所示。

0f1e8d93313b09cc71f7cedacb925025.png

找到最小值为2,对应的结点 t=2。

3.4 加入S战队

将顶点t=2加入集合S中S={1,2},同时更新V-S={3,4,5},如图15所示。

3d9ffa8a91c2685facf54e8112c1341f.png

3.5 借东风

刚刚找到了源点到 t=2 的最短路径,那么对集合V-S中所有 t 的邻接点 j,都可以借助 t 走捷径。我们从图或邻接矩阵都可以看出,2号结点的邻接点是 3 和 4 号结点,如图16所示↑。

先看3号结点能否借助2号走捷径:dist[2]+map[2][3]=2+2=4,而当前dist[3]=5>4,因此可以走捷径即2→3,更新dist[3]=4,记录顶点3的前驱为2,即p[3]= 2。

再看4号结点能否借助2号走捷径:如果dist[2]+map[2][4]=2+6=8,而当前dist[4]=∞>8,因此可以走捷径即2→4,更新dist[4]=8,记录顶点4的前驱为2,即p[4]= 2。

更新后如图17和图18所示。

c40df6ef5e1caf4173d7ddcd4169ecad.png

3.6 找最小

在集合V-S={3,4,5}中,依照贪心策略来寻找dist[]具有最小值的顶点t,依照贪心策略来寻找V-S集合中dist[]最小的顶点t,如图19所示。

c7c09e46f6c95b3c1564c43ab3fd4416.png

找到最小值为4,对应的结点t=3。

3.7 加入S战队

将顶点t=3加入集合S中S={1,2,3},同时更新V-S={4,5},如图20所示↑。

3.8 借东风

刚刚找到了源点到t =3的最短路径,那么对集合V-S中所有 t 的邻接点 j,都可以借助 t 走捷径。我们从图或邻接矩阵可以看出,3号结点的邻接点是4和5号结点。

先看4号结点能否借助3号走捷径:dist[3]+map[3][4]=4+7=11,而当前dist[4]=8<11,比当前路径还长,因此不更新。

再看5号结点能否借助3号走捷径:dist[3]+map[3][5]=4+1=5,而当前dist[5]=∞>5,因此可以走捷径即3—5,更新dist[5]=5,记录顶点5的前驱为3,即p[5]=3。

更新后如图21和图22所示。

c2055ef220fd842b77822008b2e9a4b7.png

3.9 找最小

在集合V-S={4,5}中,依照贪心策略来寻找V-S集合中dist[]最小的顶点t,如图23所示。

a055e164c4d0d999e1f7b29cb2692fa5.png

找到最小值为5,对应的结点t=5。

3.10 加入S战队

将顶点t=5加入集合S中S={1,2,3,5},同时更新V-S={4},如图24所示↑。

3.11 借东风

刚刚找到了源点到t =5的最短路径,那么对集合V-S中所有t的邻接点j,都可以借助t走捷径。我们从图或邻接矩阵可以看出,5号结点没有邻接点,因此不更新,如图25和图26所示。

47d9941dbd38796a927a39e363c2f645.png

3.12 找最小

在集合V-S={4}中,依照贪心策略来寻找dist[]最小的顶点 t,只有一个顶点,所以很容易找到,如图27所示。

776abaa953ec2481c8304f96c29277d3.png

找到最小值为8,对应的结点t=4。

3.13 加入S战队

将顶点t加入集合S中S={1,2,3,5,4},同时更新V-S={ },如图28所示↑。

3.14 算法结束

V-S={ }为空时,算法停止。

(上述的重复过程可以借助循环迭代完成)

由此,可求得从源点u到图G的其余各个顶点的最短路径及长度,也可通过前驱数组p[]逆向找到最短路径上经过的城市,如图29所示。

ca6b8f142223eb675d5c6824b279b592.png

例如,p[5]=3,即5的前驱是3;p[3]=2,即3的前驱是2;p[2]=1,即2的前驱是1;p[1]= -1,1没有前驱,那么从源点1到5的最短路径为1→2→3→5。

#include   // Dijkstra 迪杰斯特拉#include      // 输出最短路径using namespace std;//———————————————1 数据结构————————————————————const int N = 100;   // 城市的个数,用于初始化固定数组const int INF = 1e7; // 初始化无穷大为10000000,表示从一点到另一点不连通int map[N][N];       // 带权邻接矩阵,表示点的连通和距离int n,m;             // n表示城市的个数,m为城市间路线的条数,用于循环输入int dist[N];         // 表示从源点u到城市j的最短路径距离为dist[j],中间值和输出int p[N];            // 表示从源点u到城市j的最短路径的前驱节点为p[j],中间值和输出bool flag[N]; // 如果flag[i]等于true,说明顶点i已经加入到集合S;否则顶点i属于集合V-S              // 集合V表示全部顶点,集合S是按贪心策略已搜索的最短路径顶点void Dijkstra(int u){int i,j;  // 循环变量//———————————————2 初始化—————————————————————    for(i=1; i<=n; i++)     // ①    {        dist[i] =map[u][i]; // 初始化源点u到其他各个顶点的最短路径长度        flag[i]=false;        if(dist[i]==INF)            p[i]=-1;        // 源点u到该顶点的路径长度为无穷大,说明顶点i与源点u不相邻        else            p[i]=u;         // 说明顶点i与源点u相邻,设置顶点i的前驱p[i]=u    }    dist[u] = 0;    flag[u]=true;           // 初始时,集合S中只有一个元素:源点u    for(i=1; i<=n; i++)     // ②    {//———————————————3 在集合V-S(!flag[j])中找距离源点u最近的顶点t——            int temp = INF, t = u;        for(j=1; j<=n; j++)     // ③ 在集合V-S中寻找距离源点u最近的顶点t            if(!flag[j]&&dist[j](dist[t]+map[t][j])) //源点到j vs 源点经t到j的路径                {                    dist[j]=dist[t]+map[t][j];  // 更新源点到j的最短距离                    p[j]=t;                     // 更新j的前驱                }    }}void findpath(int u)                // 源点到其它各顶点的最短路径{    int x;                          // 前驱    stacks;                    // 利用库创建一个栈s    for(int i=1;i<=n;i++)    {        x=p[i];        while(x!=-1)        {            s.push(x);                // 将前驱依次压入栈中            x=p[x];        }        cout<> u >> v >> w;        map[u][v] = map[u][v]> n;    cout << "请输入城市之间的路线的个数:"<>m;    cout << "请输入城市之间的路线以及距离:"<>st;    printmatrix(n);    Dijkstra(st);    findpath(st);                        // st为源点    system("pause");    return 0;}/*演示时先复制下面内容581 2 21 3 52 3 22 4 63 4 74 3 23 5 14 5 41*//*output:请输入城市的个数:5请输入城市之间的路线的个数:8请输入城市之间的路线以及距离:1 2 21 3 52 3 22 4 63 4 74 3 23 5 14 5 4请输入小明所在的位置:1输出矩阵map:                1       2       3       4       5        1       ∞      2       5       ∞      ∞        2       ∞      ∞      2       6       ∞        3       ∞      ∞      ∞      7       1        4       ∞      ∞      2       ∞      4        5       ∞      ∞      ∞      ∞      ∞源点1到顶点1的最短距离为:0,路径为:1源点1到顶点2的最短距离为:2,路径为:1→2源点1到顶点3的最短距离为:4,路径为:1→2→3源点1到顶点4的最短距离为:8,路径为:1→2→4源点1到顶点5的最短距离为:5,路径为:1→2→3→5请按任意键继续. . .*/

-End-

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值