旅行售货员问题-回溯法

问题描述

 某售货员要到若干城市去推销商品,已知各城市之间的路程,他要选定一条从驻地出发,经过每个城市一遍,最后回到住地的路线,使总的路程最短。
   旅行售货员问题-回溯法_第1张图片

  结果为: 1 3 2 4 1



算法描述

 回溯法,序列树, 假设起点为 1。

 算法开始时 x = [1, 2, 3, …, n]

 x[1 : n]有两重含义 x[1 : i]代表前 i 步按顺序走过的城市, x[i + 1 : n]代表还未经过的城市。利用Swap函数进行交换位置。

 若当前搜索的层次i = n 时,处在排列树的叶节点的父节点上,此时算法检查图G是否存在一条从顶点x[n-1] 到顶点x[n] 有一条边,和从顶点x[n] 到顶点x[1] 也有一条边。若这两条边都存在,则发现了一个旅行售货员的回路(即:新旅行路线),算法判断这条回路的费用是否优于已经找到的当前最优回路的费用bestcost,若是,则更新当前最优值bestcost和当前最优解bestx。

 若i < n 时,检查x[i - 1]至x[i]之间是否存在一条边, 若存在,则x [1 : i ] 构成了图G的一条路径,若路径x[1: i] 的耗费小于当前最优解的耗费,则算法进入排列树下一层,否则剪掉相应的子树。


递归回溯

  • 回溯法对解空间作深度优先搜索
  • 通常用递归方法实现回溯法

旅行售货员问题-回溯法_第2张图片

void backtrack (int t)
{
       if (t>n) output(x);// t>n时已搜索到一个叶结点, output(x)对得到的可行解x进行记录或输出处理.
       else{                                     
         for (int i=f(n,t);i<=g(n,t);i++) { // 函数f和g分别表示在当前扩展结点处未搜索子树的起止编号.                            
           x[t]=h(i); //h(i)表示在当前扩展结点处x[t]的第i个可选值
           if (constraint(t)&&bound(t)) backtrack(t+1);
         } //for循环结束后, 已搜索遍当前扩展结点的所有未搜索子树.
       }
} //Backtrack(t)执行完毕, 返回t-1层继续执行, 对未测试过的x[t-1]的值继续搜索.
  • if (Constraint(t)&&Bound(t) ) Backtrack(t + 1);if语句含义:Constraint(t)和Bound(t)表示当前扩展节点处的约束函数和限界函数。
  • Constraint(t): 返回值为true时,在当前扩展节点处x[1:t]的取值问题的约束条件,否则不满足问题的约束条件,可剪去相应的子树
  • Bound(t): 返回的值为true时,在当前扩展节点处x[1:t]的取值为时目标函数越界,还需由Backtrack(t+1)对其相应的子树做进一步搜索。否则,当前扩展节点处x[1:t]的取值是目标函数越界,可剪去相应的子树
  • for循环作用:搜索遍当前扩展的所有未搜索过的子树。
  • 递归出口:Backtrack(t)执行完毕,返回t-1层继续执行,对还没有测试过的x[t-1]的值继续搜索。当t=1时,若以测试完x[1]的所有可选值,外层调用就全部结束。

迭代回溯

  采用树的非递归深度优先遍历算法,可将回溯法表示为一个非递归迭代过程。

void iterativeBacktrack ( )
{
  int t=1;
  while (t>0) {
    if (f(n,t)<=g(n,t)) 
      for (int i=f(n,t);i<=g(n,t);i++) {// 函数f和g分别表示在当前扩展结点处未搜索子树的起止编号.
        x[t]=h(i);
        if (constraint(t)&&bound(t)) {
          if (solution(t)) output(x); //solution(t)判断当前扩展结点处是否已得到问题的一个可行解                                       
          else t++;} //solution(t)为假,则仅得到一个部分解,需继续纵深搜索
        }
    else t--;
  } //while循环结束后,完成整个回溯搜索过程
}

子集树与排列树

  • 子集树: 所给的问题是从n个元素的集合中找出满足某种性质的子集时, 相应的解空间称为子集树.

  子集树通常有2n个叶结点, 遍历子集树的任何算法均需Ω(2n)的计算时间.

  例如: 0-1背包问题的解空间为一棵子集树.

  • 排列树: 当所给的问题是确定n个元素满足某种性质的排列时, 相应的解空间称为排列树.

  排列树通常有(n-1)!个叶结点, 遍历排列树需要Ω(n!)的计算时间.

  例如: 旅行售货员问题的解空间为一棵排列树.

旅行售货员问题-回溯法_第3张图片


代码实现

#include 
using namespace std;
const int max_ = 0x3f3f3f;   //定义一个最大值
const int NoEdge = -1;      //两个点之间没有边
int citynum;                //城市数
int edgenum;                //边数
int currentcost;            //记录当前的路程
int bestcost;               //记录最小的路程(最优)
int Graph[100][100];        //图的边距记录
int x[100];                 //记录行走顺序
int bestx[100];             //记录最优行走顺序

void InPut()
{
    int pos1, pos2, len;     //点1 点2 距离

    cout<<"请输入城市数和边数(c e):";
    cin>>citynum>>edgenum;

    memset(Graph, NoEdge, sizeof(Graph));

    cout<<"请输入两座城市之间的距离(p1 p2 l):"<>pos1>>pos2>>len;
        Graph[pos1][pos2] = Graph[pos2][pos1] = len;
    }
}

//初始化
void Initilize()
{
    currentcost = 0;
    bestcost = max_;
    for(int i = 1; i <= citynum; ++i)
    {
        x[i] = i;
    }
}


void Swap(int &a, int &b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}


void BackTrack(int i) //这里的i代表第i步去的城市而不是代号为i的城市
{
    if(i == citynum)
    {
        //进行一系列判断,注意的是进入此步骤的层数应是叶子节点的父节点,而不是叶子节点
        if(Graph[x[i - 1]][x[i]] != NoEdge && Graph[x[i]][x[1]] != NoEdge && (currentcost + Graph[x[i - 1]][x[i]] + Graph[x[i]][x[1]] < bestcost || bestcost == max_))
        {
            //最小(优)距离=当前的距离+当前城市到叶子城市的距离+叶子城市到初始城市的距离
            bestcost = currentcost + Graph[x[i - 1]][x[i]] + Graph[x[i]][x[1]];
            for(int j = 1; j <= citynum; ++j)
                bestx[j] = x[j];
        }
    }
    else
    {
        for(int j =  i; j <= citynum; ++j)
        {
            if(Graph[x[i - 1]][x[j]] != NoEdge && (currentcost + Graph[x[i - 1]][x[j]] < bestcost || bestcost == max_))
            {
                Swap(x[i], x[j]);  //这里i 和 j的位置交换了, 所以下面的是currentcost += Graph[x[i - 1]][x[i]];
                currentcost += Graph[x[i - 1]][x[i]];
                BackTrack(i + 1);   //递归进入下一个城市
                currentcost -= Graph[x[i - 1]][x[i]];
                Swap(x[i], x[j]);
            }
        }
    }
}

void OutPut()
{
    cout<<"最短路程为:"<



样例测试

以前面的样例示范

输入

请输入城市数和边数(c e):4 6
请输入两座城市之间的距离(p1 p2 l):
1 2 30
1 3 6
1 4 4
2 4 10
2 3 5
3 4 20


输出
最短路程为:25
路线为:
1 3 2 4 1
旅行售货员问题-回溯法_第4张图片

你可能感兴趣的:(算法,数据结构,算法,回溯法,c++)