1. 程式人生 > >關於TSP問題的幾種解決辦法

關於TSP問題的幾種解決辦法

一、實驗內容:

分別用動態規劃、貪心及分支限界法實現對TSP問題(無向圖)的求解,並至少用兩個測試用例對所完成的程式碼進行正確性及效率關係上的驗證。

二、程式設計的基本思想、原理和演算法描述:

(包括程式的資料結構、函式組成、輸入/輸出設計、符號名說明等)

1、  動態規劃法

(1)資料結構:

利用二進位制來表示集合,則集合S可由一個十進位制數x相對應,此x所對應的二進位制數為y,如果y的第k位為1,則表示k存在集合S中。

例如:

集合S={0,1}(其子集合為{}{0}{1}{01}),我們用二進位制數11(所對應十進位制數為3)表示S,11中右手邊第1個數為1表示0在集合S中,右手邊第二個數為1表示1在集合S中,其他位為0表示其它數字不在集合S中;

同理,集合S={0,2}(其子集合為{}{0}{2}{02}可用二進位制數101(所對應十進位制數為5)表示(右手邊第1個數為1表示0在集合S中,右手邊第二個數為0表示1不在集合S中,右手邊第3個數為1表示2在集合S中,則說明0,2在集合中,1不在集合中。

利用鄰接矩陣表示任意兩點之間的距離

例如:

mp[i][j]表示點i,j兩點之間的距離。

(2)函式組成

輸入函式in()

利用動態規劃法演算法實現的求解函式solve()

主函式main()

(3)輸入/輸出設計

本程式可以通過鍵盤進行輸入、螢幕進行輸出。(根據實際程式情況,還可以選擇隨機產生輸入資料、將輸出資料輸出到檔案等其它方式)

這裡採用隨機產生輸入資料,將資料輸出在螢幕上的方式。

(4)符號名說明

表示頂點個數。

mp[i][j]表示頂點i和頂點j之間的距離。

dp[i][j]表示頂點i經過集合S(用二進位制表示的數為j)後回到起始點的最短路徑和。

(5)演算法描述

某一個點i不經過任意點回到起始點的最短路徑和為mp[i][0](預設初始點為0)

dp[i][0]= mp[i][0];   (1<=i<n)

點i經過集合S(二進位制表示的數為j)的最短路徑和為從點i經過集合S中的某一點k後再從該點出發,經過集合S-{k}的最小值。

dp[i][j]=min{mp[i][k]+dp[k][j-(1<<(k-1))};

2、  貪心法

(1)資料結構

利用鄰接矩陣表示任意兩點之間的距離

例如:

mp[i][j]表示點i,j兩點之間的距離。

(2)函式組成

輸入函式in()

利用貪心法每次取最近距離的函式dfs(u,k,l),u表示當前在頂點u,k表示已經走過了k個點,l表示所經過的路徑和。

主函式main()

(3)輸入/輸出設計

本程式可以通過鍵盤進行輸入、螢幕進行輸出。(根據實際程式情況,還可以選擇隨機產生輸入資料、將輸出資料輸出到檔案等其它方式)

這裡採用隨機產生輸入資料,將資料輸出在螢幕上的方式。

(4)符號名說明

表示頂點個數。

mp[i][j]表示頂點i和頂點j之間的距離。

inq[i] 表示頂點i已經走過了。

(5)演算法描述

如果當前在頂點u,則取與頂點u距離最近的點p。

dfs(u,k,l)=dfs(p,k+1,l+mp[u][p])

3、  分支限界法

(1)資料結構

利用鄰接矩陣表示任意兩點之間的距離

例如:

mp[i][j]表示點i,j兩點之間的距離。

結構體

struct node

{

    int visp[22];//標記哪些點走了

    int st;//起點

    int ed;//終點

    int k;//走過的點數

    int sumv;//經過路徑的距離

    int lb;//目標函式的值

    bool operator <(constnode &p )const

    {

        return lb>p.lb;

    }

};

③優先順序佇列

priority_queue<node> q;

(2)函式組成

in()

輸入函式。

dfs(int u,int k,int l)

利用貪心法每次取最近距離的函式,u表示當前在頂點u,k表示已經走過了k個點,l表示所經過的路徑和。將貪心法的解作為上界的初始值。

③get_lb( node p)

求對應節點p的目標函式。

main()

主函式。

get_up()

求分支限界法的上界。

get_low()

求分支界限法的下界。

solve()

利用分支限界法求解函式

(3)輸入/輸出設計

本程式可以通過鍵盤進行輸入、螢幕進行輸出。(根據實際程式情況,還可以選擇隨機產生輸入資料、將輸出資料輸出到檔案等其它方式)

這裡採用隨機產生輸入資料,將資料輸出在螢幕上的方式。

(4)符號名說明

① n 表示頂點個數。

② mp[i][j] 表示頂點i和頂點j之間的距離。

③ inq[i] 表示頂點i已經走過了。

(5)演算法描述

首先通過貪心法求解的值作為上界,把每個點最近的兩條邊之和的1/2作為下界。

分支限界法通過設定目標函式,每次從優先順序佇列中取目標函式的值最小的節點。先判斷是否已經經過了n-1個點,如果已經經過了n-1個點,那麼可直接求出最短路徑和,並與在佇列裡的其他節點的目標函式值比較,如果該路徑和比其他所有在佇列裡的節點的目標函式值都小,那麼改路徑和就是問題的解。否則,繼續計算優先順序佇列裡面的其他節點。

三、源程式及註釋:

這裡預設頂點的個數小於22。

   1、動態規劃法

#include<iostream>
#include<cstdio>
#define INF 9999
using namespace std;
int mp[22][22];
int n;
void in()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            if(i==j)
            {
                mp[i][j]=INF;
                continue;
            }
            scanf("%d",&mp[i][j]);
        }
    }
}
int dp[22][1<<22];
int solve()
{
    int s=(1<<(n-1));
    dp[0][0]=0;
    for(int i=1;i<n;i++)
    {
        dp[i][0]=mp[i][0];
    }
    dp[0][(s-1)]=INF;
    for(int j=1;j<(s-1);j++)//總共有n-1個點,但不能全部取
    {


        for(int i=1;i<n;i++) //把1~(n-1)這n-1個點,對映為集合對應的二進位制數中的							  0~(n-2)位
        {

            if((j&(1<<(i-1)))==0)//i不在集合中
            {
                int m=INF;
                for(int k=1;k<n;k++)
                {
                    if((j&(1<<(k-1)))>0)//k在集合中
                    {
                        int tmp=dp[k][(j-(1<<(k-1)))]+mp[i][k];
                        if(m>tmp)
                        m=tmp;
                    }
                }
                dp[i][j]=m;
            }
        }
    }
    dp[0][s-1]=INF;
    for(int i=1;i<n;i++)
    {
        dp[0][s-1]=min(dp[0][s-1],mp[0][i]+dp[i][(s-1)-(1<<(i-1))]);
    }
    return dp[0][s-1];
}
int main()
{
    in();
    printf("%d\n",solve());
    return 0;
}

2、貪心法

#include<iostream>
#include<cstdio>
#define INF 9999
using namespace std;
int n;
int mp[22][22];
int inq[22];
void in()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            if(i==j)
            {
                mp[i][j]=INF;
                continue;
            }
            scanf("%d",&mp[i][j]);
        }
    }
}
int dfs(int u,int k,int l)
{
    if(k==n) return l+mp[u][1];
    int minlen=INF , p;
    for(int i=1; i<=n; i++)
    {
        if(inq[i]==0&&minlen>mp[u][i])/*取與所有點的連邊中最小的邊*/
        {
            minlen=mp[u][i];
            p=i;
        }
    }
    inq[p]=1;
    return dfs(p,k+1,l+minlen);
}
int main()
{
    in();
    inq[1]=1;
    printf("%d\n",dfs(1,1,0));
    return 0;
}

3、分支限界法

//分支限界法
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#define INF 100000
using namespace std;
/*  n*n的一個矩陣  */
int n;
int mp[22][22];//最少3個點,最多15個點
/*輸入距離矩陣*/
void in()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            if(i==j)
            {
                mp[i][j]=INF;
                continue;
            }
            scanf("%d",&mp[i][j]);
        }
    }
}
struct node
{
    int visp[22];//標記哪些點走了
    int st;//起點
    int st_p;//起點的鄰接點
    int ed;//終點
    int ed_p;//終點的鄰接點
    int k;//走過的點數
    int sumv;//經過路徑的距離
    int lb;//目標函式的值
    bool operator <(const node &p )const
    {
        return lb>p.lb;
    }
};
priority_queue<node> q;
int low,up;
int inq[22];
//確定上界
int dfs(int u,int k,int l)
{
    if(k==n) return l+mp[u][1];
    int minlen=INF , p;
    for(int i=1; i<=n; i++)
    {
        if(inq[i]==0&&minlen>mp[u][i])/*取與所有點的連邊中最小的邊*/
        {
            minlen=mp[u][i];
            p=i;
        }
    }
    inq[p]=1;
    return dfs(p,k+1,l+minlen);
}
int get_lb(node p)
{
    int ret=p.sumv*2;//路徑上的點的距離
    int min1=INF,min2=INF;//起點和終點連出來的邊
    for(int i=1; i<=n; i++)
    {
        if(p.visp[i]==0&&min1>mp[i][p.st])
        {
            min1=mp[i][p.st];
        }
    }
    ret+=min1;
    for(int i=1; i<=n; i++)
    {
        if(p.visp[i]==0&&min2>mp[p.ed][i])
        {
            min2=mp[p.ed][i];
        }
    }
    ret+=min2;
    for(int i=1; i<=n; i++)
    {
        if(p.visp[i]==0)
        {
            min1=min2=INF;
            for(int j=1; j<=n; j++)
            {
                if(min1>mp[i][j])
                min1=mp[i][j];
            }
            for(int j=1; j<=n; j++)
            {
                if(min2>mp[j][i])
                min2=mp[j][i];
            }
            ret+=min1+min2;
        }
    }
    return ret%2==0?(ret/2):(ret/2+1);
}
void get_up()
{
    inq[1]=1;
    up=dfs(1,1,0);
}
void get_low()
{
    low=0;
    for(int i=1; i<=n; i++)
    {
        /*通過排序求兩個最小值*/
        int min1=INF,min2=INF;
        int tmpA[22];
        for(int j=1; j<=n; j++)
        {
            tmpA[j]=mp[i][j];
        }
        sort(tmpA+1,tmpA+1+n);//對臨時的陣列進行排序
        low+=tmpA[1];
    }
}
int solve()
{
    /*貪心法確定上界*/
    get_up();
    
    /*取每行最小的邊之和作為下界*/
    get_low();
    
    /*設定初始點,預設從1開始 */
    node star;
    star.st=1;
    star.ed=1;
    star.k=1;
    for(int i=1; i<=n; i++) star.visp[i]=0;
    star.visp[1]=1;
    star.sumv=0;
    star.lb=low;
    
    /*ret為問題的解*/
    int ret=INF;
    
    q.push(star);
    while(!q.empty())
    {
        node tmp=q.top();
        q.pop();
        if(tmp.k==n-1)
        {
            /*找最後一個沒有走的點*/
            int p;
            for(int i=1; i<=n; i++)
            {
                if(tmp.visp[i]==0)
                {
                    p=i;
                    break;
                }
            }
            int ans=tmp.sumv+mp[p][tmp.st]+mp[tmp.ed][p];
            node judge = q.top();
            
            /*如果當前的路徑和比所有的目標函式值都小則跳出*/
            if(ans <= judge.lb)
            {
                ret=min(ans,ret);
                break;
            }
            /*否則繼續求其他可能的路徑和,並更新上界*/
            else
            {
                up = min(up,ans);
                ret=min(ret,ans);
                continue;
            }
        }
        /*當前點可以向下擴充套件的點入優先順序佇列*/
        node next;
        for(int i=1; i<=n; i++)
        {
            if(tmp.visp[i]==0)
            {
                next.st=tmp.st;

                /*更新路徑和*/
                next.sumv=tmp.sumv+mp[tmp.ed][i];

                /*更新最後一個點*/
                next.ed=i;

                /*更新頂點數*/
                next.k=tmp.k+1;

                /*更新經過的頂點*/
                for(int j=1; j<=n; j++) next.visp[j]=tmp.visp[j];
                next.visp[i]=1;

                /*求目標函式*/
                next.lb=get_lb(next);
                
                /*如果大於上界就不加入佇列*/
                if(next.lb>up) continue;
                q.push(next);
            }
        }
    }
    return ret;
}
int main()
{
    in();
    printf("%d\n",solve());
    return 0;
}