1. 程式人生 > >遞迴、回溯-旅行售貨員問題

遞迴、回溯-旅行售貨員問題

某售貨員到若干城市去推銷商品,已知各城市之間的路程(或旅費)。他要選定一條路線,經過每個城市一遍最後回到駐地的路線,使得總的路程(或總旅費)最小(預設從1號城市開始)。

輸入:城市的數目n,城市a,b,以及其之間的路程d。

輸出:最短的路程,最短的路徑方案。

執行結果:

旅行售貨員問題的解空間是一顆排列數,對於排列數的回溯搜尋與生成1,2,...,n的所有排列的遞迴演算法Perm類似。開始時x=[1,2,...n]。則相應的排列樹由x[1:n]的所有排列構成。

在遞迴演算法Backtrack中,當i=n時,當前擴充套件結點是排列樹的葉結點的父結點。此時演算法檢測圖G是否存在一條從頂點x[n-1]到頂點x[n]的邊和一條從頂點x[n]到頂點1的邊,如果這兩條邊都存在,則找到一條旅行售貨員迴路。此時,演算法還需判斷這條迴路的費用是否優於已找到的當前最優迴路的費用bestc。如果是,則必須更新當前最優值bestc和當前最優解bestx。

當i<n時,當前擴充套件結點位於排列數的第i-1層,圖G中存在從頂點x[i-1]到頂點x[i]的邊時,x[1:i]構成圖G的一條路徑,且當x[1:i]的費用小於當前最優值時演算法進入排列樹的第i層,否則將剪去相應的子樹。演算法用變數cc記錄當前路徑x[1:i]的費用。

解旅行售貨員問題的回溯演算法可描述如下:

template <class Type>
class Traveling
{
    template <class T>
    friend T TSP(T**, int*, int, T);
private:
    void BackTrack(int t);

    int n,                                  //城市個數
        *x,                                 //當前解,表示從解空間根結點到當前結點代表的所經過的城市
        *bestx;                             //當前最優解,已搜尋的部分解空間樹中費用最小周遊路線對應的每一個經過的城市
    Type **a,                               //城市之間的鄰接矩陣,a[i][j]表示城市i到城市j的費用,
                                            //當a[i][j]=NoEdge時,表明城市i和j無邊相連
         cc,                                //當前費用,表示從解空間樹根結點到當前結點代表的經過城市的費用和
         NoEdge,                            //無邊標記
         bestc;                             //當前最優值,已搜尋的部分解空間樹中周遊線路的最小費用
};
//對解空間樹回溯搜尋,找出周遊線路額最小費用
template <class Type>
void Traveling<Type>::BackTrack(int t)
{
    int i;
    if(t == n)                                                         //當前擴充套件結點是排列數的葉結點的父結點
    {
         /*
         演算法是否存在從城市x[n-1]到城市x[n]的邊和從城市x[n]到x[1]的邊,
         如果都存在,則找到一條旅行售貨員的迴路判斷這條迴路的費用是否優
         於已找到的當前最優迴路費用
        */
        if(a[x[n-1]][x[n]] != NoEdge && a[x[n]][x[1]] != NoEdge &&
            (bestc==NoEdge || cc+a[x[n-1]][x[n]]+a[x[n]][1] < bestc))
        {
            bestc = cc+a[x[n-1]][x[n]]+a[x[n]][1];                     //更新當前最優解bestc
            for(i = 1; i <= n; i++)                                    //更新當前最優解bestx
                bestx[i] = x[i];
        }
        return;
    }
    //可否進入x[j]子樹
    for(i = t; i <= n; i++)
        if(a[x[t-1]][x[i]] != NoEdge && (bestc==NoEdge ||   //檢測是否存在一條從城市x[i-1]到城市x[j]的邊
                            cc+a[x[t-1]][x[i]] < bestc))    //判斷從根結點到當前搜尋結點處的部分周遊路線的費用
                                                            //是否小於當前找到的最小費用周遊路線
        {
            //如果滿足約束函式和限界函式,則搜尋以當前搜尋結點為根的子樹
            swap(x[t], x[i]);
            cc += a[x[t-1]][x[t]];
            BackTrack(t+1);
            cc -= a[x[t-1]][x[t]];                          //回溯還原
            swap(x[t], x[i]);
        }
}
//負責變數初始化,呼叫遞迴函式Backtrack實現回溯搜尋,並返回周遊線路的最小費用
template <class Type>
Type TSP(Type **a, int *v, int n, Type NoEdge)
{
    Traveling<Type> Y;
    int i;

    Y.x = new int[n+1];
    //呼叫函式回溯搜尋前要將陣列x初始化為{1,2,n}
    for(i = 1; i <= n; i++)
        Y.x[i] = i;
    Y.a = a;
    Y.bestc = NoEdge;
    Y.bestx = v;
    Y.cc = 0;
    Y.n = n;
    Y.NoEdge = NoEdge;
    Y.BackTrack(2);                             //搜尋x[2:n]的全排列,呼叫遞迴函式Backtrack實現回溯搜尋
    delete []Y.x;

    return Y.bestc;                             //返回周遊線路的最小費用
}

 2、演算法效率

如果不考慮更新bestx所需的計算時間,則Backtrack需要O((n-1)!)計算時間。由於演算法Backtrack在最壞情況下可能需要更新當前最優解O((n-1)!)次,每次更新bestx需O(n)計算時間,從而整個演算法的計算時間複雜度為O(n!)。