分支限界法通常是是广度优先或者以最小消耗(最大效益)优先的方式搜索问题的解控键树。

FIFO分支限界法

  按照先进先出的原则选择下一个活结点作为扩展结点,即从节点中取出的顺序与加入结点的顺序相同。


分支限界法算法策略

(1活节点一旦成为扩展结点,就一次性产生其所有儿子结点

(2)在这些儿子结点中,导致不可行或者非最优解的儿子结点将会被舍弃,其余儿子结点加入活节点表中。

(3)从活结点表中取下一结点当做当前扩展结点,并重复上述结点扩展操作

这个过程一直持续到所需的解或者活节点表为空为止。


提高分支限界法的算法效率:

 若我们把搜索过程看作是对一棵树的遍历,那么剪枝就是将树中一些"死结点"和不能得到最优解的结点剪掉。

实现分支限界法时,首先确定目标值上下界,边搜索边减掉搜索树的某些分支,提高搜索效率。在搜索时绝大部分需要用到剪枝。"剪枝"是搜索算法中优化程序的一种基本方法,需要通过设计出合理的判断方法,以决定某一分支的取舍。在设计判断方法的时候,需要遵循一定的原则

(1)正确性

  剪枝的前提是一定要保证不丢失正确的结果。如果随便剪枝的,把带有最优解的那一部分支也剪掉的话,剪枝也就失去了意义。

(2)准确性

  在保证正确性的基础上,采取合适的判断手段,是不包含最优解的枝叶尽可能多的被剪去,以达到优化程序的目的。

(3)高效性

  设计优化程序的目的,是要减少搜索的次数,使程序运行的时间减少。但为了搜索的次数尽可能的少,我们又必须花功夫设计出一个准确性较高的优化算法,而当算法的准确性较高,其判断的次数势必增多,从而导致耗时增多。所以在优化和效率之间找到一个平衡点是关键。


单源最短路径问题

  给定一个带权有向图G=(V,E),其中每条边的权是非负数。给定V中的一个顶点,成为源。现在要计算从源到所有其他个顶点的最短路径长度,这里路径长度指的是各边权之和。这个问题通常被称作单源最短路径问题。

如图所示,每一边都有一非负权值。求图G到原点s的到t的最短路径。



算法分析

  算法从G的源点s和空队列开始。结点s被扩展之后,他的儿子结点2,3,4倍一次插入队列当中。然后取出队头元素,进行下一步扩展。保证每一次扩展时,源到当前节点的和都是最小的。具体的解空间图如下:




算法过程

算法先从源节点s开始扩展,3个子结点2,3,4被插入到队列当中,如下图所示。


取出结点2,它有3个子树。结点2沿边f扩展到3时,路径长度为5,而结点3的当前路径为3(s->6),没有得到优化,该子树被剪掉。.结点2沿边d,e扩展值5,6时,将他们加入优先队列,如图


取出头结点3,它有两个子树。结点3沿f边扩展到结点6时,该路径长度为12,而结点6的当前路径为4,该路径没有被优化,该子树被剪枝。结点3沿g扩展到7时,将7加入到优先队列。如下如所示


重复上面操作直到队列为空。s到各个结点的最短路径。

代码如下:

#include <iostream>
#include<queue>
using namespace std;

typedef struct ArcCell{
    int adj;//保存权值
    int info;//存储最短路径长度
}ArcCell,AdjMaxtrix[100][100];

typedef struct{
    int data;
    int length;
}VerType;

typedef struct{
    VerType vexs[100];//顶点向量
    AdjMaxtrix arcs;
    int vexnum;//顶点数
    int arcnum;//弧数
}Graph;

Graph G;
queue<int> q;

void CreateGraph()
{
    int m,n,t;
    printf("输入顶点数和弧数:");
    scanf("%d%d",&G.vexnum,&G.arcnum);
    printf("输入顶点:");
    for(int i=1;i<=G.vexnum;i++)
    {
        scanf("%d",&G.vexs[i].data);
        G.vexs[i].length=10000;
    }

    for(int i=1;i<=G.vexnum;i++)
        for(int j=1;j<=G.vexnum;j++)
        {
            G.arcs[i][j].adj=0;
        }

    printf("输入弧及权重:\n");
    for(int i=1;i<=G.arcnum;i++)
        {
            scanf("%d%d%d",&m,&n,&t);
            G.arcs[m][n].adj=1;
            G.arcs[m][n].info=t;
        }

}

int NextAdj(int v,int w)
{
    for(int i=w+1;i<=G.vexnum;i++)
        if(G.arcs[v][i].adj)
            return i;
    return 0;//not found;
}

void ShortestPaths(int v)
{
    int k=0;//从首个节点开始访问
    int t;
    G.vexs[v].length=0;
    q.push(G.vexs[v].data);
    while(!q.empty())
    {
        t=q.front();
        k=NextAdj(t,k);
        while(k!=0)
        {
            if(G.vexs[t].length+G.arcs[t][k].info<=G.vexs[k].length)//减枝操作
            {
                G.vexs[k].length=G.vexs[t].length+G.arcs[t][k].info;
                q.push(G.vexs[k].data);
            }
            k=NextAdj(t,k);
        }
        q.pop();
    }
}

void Print()
{
    for(int i=1;i<=G.vexnum;i++)
        printf("%d------%d\n",G.vexs[i].data,G.vexs[i].length);
}

int main()
{
    CreateGraph();
    ShortestPaths(1);
    Print();
    return 0;
}

上面的代码只计算了到各个结点的最短路径值,并没有保存最小路径经过的节点。要求最短路径经过的节点可以采取逆推法。t的最短路径减去和它相连结点之间弧的权值,然后判断否相等,如果相等,则说明该节点在最短路径中,然后从该节点继续向前推,直到推导到t



Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐