1. 程式人生 > >分支定界法——旅行商(TSP)問題

分支定界法——旅行商(TSP)問題

問題描述

給定一個n頂點網路(有向或無向),找出一個包含n個頂點且具有最小耗費的換路。任何一個包含網路所有頂點的換路稱為一個旅行。旅行商問題(Traveling Salesman Problem,TSP)是要尋找一條耗費最少的旅行。

              四頂點網路 
圖1 四頂點網路

如圖1是一個四頂點無向網路。這個網路的一些旅行:1,2,4,3,1;1,3,2,4,1和1,4,3,2,1。旅行1,2,4,3,1的耗費為66,;旅行1,3,2,4,1的耗費為25;旅行1,4,3,2,1的耗費為55.故1,3,2,4,1是網路中耗費最少的旅行。

演算法設計

和回溯法解決TSP問題一樣,首先確定問題的解空間是一個排列樹,從頂點1出發最後回到頂點1,所以排列可以表示為[1,x2,…,xn,1]。和子集樹一樣,使用一個優先順序佇列求解。要求旅行的耗費最小,故使用小根堆儲存活動節點,堆中每個活動節點的子樹耗費所能取得的下界lowerCost 是優先順序佇列的優先順序,即當前節點確定的途徑下繼續完成排列能取得最低耗費(並不一定保證能最終達到這個值)。

每個活動節點是小根堆的一個元素,元素結構包括:

lowerCost;      //當前節點往後排列,整個迴路所能取得的總耗費的下限 
currentCost; //從根到當前節點的當前排列的耗費
restMinCost; //從當前節點到最後一個節點,每個節點的最小出邊的耗費之和
s; //從根節點到當前節點的路徑為[0:s]
currentTour[]; //從根節點到當前節點的路徑(排列)

如何獲得lowerCost
對於每個活動節點,可知當前耗費currentCost,對於剩餘的頂點,計算每個頂點的最小出邊之和restMinCost即為剩餘路徑的最小耗費的下界,lowerCost=currentCost + restMinCost

演算法步驟

1、準備工作:建立小根堆,用於儲存活動節點。計算每個頂點的最小出邊,若存在某個頂點沒有出邊,則演算法終止。初始化樹根(頂點1)為第一個活動節點。 
2、判斷節點是否是葉結點的父節點:是的話,則檢查是否一定有最低耗費,若是加入小根堆;
3、不是葉結點的父節點,則生成子節點,並判斷子節點是否有可能取得最低耗費,若可能則加入小根堆;
4、取出下一個節點作為活動節點,若該節點已經是葉結點,返回當前最低耗費值,即為最優旅行。若不是葉結點則迴圈2、3步。
排列樹 
圖2 四頂點網路的解空間樹
小根堆中的元素變化:{}——>{C,D,E}——>{C,D,K,J}——>{C,K,J,H,I}——>{C,K,J,I,N}——>{C,K,I,N}——>{C,K,I}

最後取出活動節點N,但是N已經是葉結點,故返回最優值25,和最佳途徑1->3->2->4->1,並終止演算法。

事實上,檢查節點J的子節點P時執行的是步驟2,也能得到最優值25,但是由於N已經加入小根堆,並且當前最優值已經是25,所以P沒有加入小根堆,途徑1->4->2->3->1也就被排除了。

需要注意的是,對於以下的C++程式,需要手動釋放掉每個活動節點的資料成員currentTour陣列的內寸,包括最後小根堆中剩餘的節點。

C++實現

小根堆minHeap元素結構

#pragma once
struct heapNode
{
    int lowerCost;      //當前節點往後排列,整個迴路所能取得的總耗費的下限
    int currentCost;    //從根到當前節點的當前排列的耗費
    int restMinCost;    //從當前節點到最後一個節點,每個節點的最小出邊的耗費之和
    int s;              //從根節點到當前節點的路徑為[0:s]
    int *currentTour;   //從根節點到當前節點的路徑(排列)

    //算術運算時的型別轉換
    operator int() { return lowerCost; }

    //過載大於號運算子,用於小根堆比較
    bool operator>(const heapNode &right)
    {
        return lowerCost > right.lowerCost;
    }
};

小根堆minHeap 部分原始碼

#pragma once
#include<iostream>
#include <algorithm>

using namespace std;

template<class T>
void changeLength1D(T*& a, int oldLength, int newLength)
{
    if (newLength < 0)
    {
        cout << "new length must be >= 0" << endl;
        exit(1);
    }

    T* temp = new T[newLength];              // new array
    int number = min(oldLength, newLength);  // number to copy
    copy(a, a + number, temp);
    delete[] a;                             // deallocate old memory
    a = temp;
}

template<class T>
class minHeap 
{
public:
    minHeap(int initialCapacity = 10);
    ~minHeap() { delete[] heap; }
    bool empty() const { return heapSize == 0; }
    int size() const
    {
        return heapSize;
    }
    const T& top()
    {// return min element
        if (heapSize == 0)
            exit(1);
        return heap[1];
    }
    void pop();
    void push(const T&);
    void initialize(T *, int);
    void deactivateArray()
    {
        heap = NULL; arrayLength = heapSize = 0;
    }
    void output(ostream& out) const;
private:
    int heapSize;       // number of elements in queue
    int arrayLength;    // queue capacity + 1
    T *heap;            // element array
};

template<class T>
minHeap<T>::minHeap(int initialCapacity)
{// Constructor.
    if (initialCapacity < 1)
    {
        cout << "Initial capacity = " << initialCapacity << " Must be > 0";
        exit(1);
    }
    arrayLength = initialCapacity + 1;
    heap = new T[arrayLength];
    heapSize = 0;
}

template<class T>
void minHeap<T>::push(const T& theElement)
{// Add theElement to heap.

 // increase array length if necessary
    if (heapSize == arrayLength - 1)
    {// double array length
        changeLength1D(heap, arrayLength, 2 * arrayLength);
        arrayLength *= 2;
    }

    // find place for theElement
    // currentNode starts at new leaf and moves up tree
    int currentNode = ++heapSize;
    while (currentNode != 1 && heap[currentNode / 2] > theElement)
    {
        // cannot put theElement in heap[currentNode]
        heap[currentNode] = heap[currentNode / 2]; // move element down
        currentNode /= 2;                          // move to parent
    }

    heap[currentNode] = theElement;
}

template<class T>
void minHeap<T>::pop()
{// Remove max element.
 // if heap is empty return null
    if (heapSize == 0)   // heap empty
    {
        cout << "heap is empty!" << endl;
        exit(1);
    }

    // Delete min element
    heap[1].~T();

    // Remove last element and reheapify
    T lastElement = heap[heapSize--];

    // find place for lastElement starting at root
    int currentNode = 1,
        child = 2;     // child of currentNode
    while (child <= heapSize)
    {
        // heap[child] should be smaller child of currentNode
        if (child < heapSize && heap[child] > heap[child + 1])
            child++;

        // can we put lastElement in heap[currentNode]?
        if (lastElement <= heap[child])
            break;   // yes

                     // no
        heap[currentNode] = heap[child]; // move child up
        currentNode = child;             // move down a level
        child *= 2;
    }
    heap[currentNode] = lastElement;
}

TSP演算法程式

#include<iterator>
#include"heapNode.h"
#include"minHeap.h"
#define N 5

//全域性變數
int n = N - 1;
int cost[N][N];  //cost[1:n][1:n] 表示城市之間的旅行耗費,不存在路徑則耗費等於-1
int bestTour[N];

int TSP(int *bestTour)
{//最小費用分支定界程式碼,尋找最短旅行
 //bestTour[1:n]儲存最短旅行路徑

    minHeap<heapNode> liveNodeMinHeap;

    //costOfMinOutEdge[i]表示頂點i的最小出邊
    int *minCostOutEdge = new int[n + 1];
    int sumOfMinCostOutEdges = 0;
    for (int i = 1; i <= n; ++i)
    {
        int minCost = -1;
        for (int j = 1; j <= n; ++j)
            if (cost[i][j] != -1 && (minCost == -1 || minCost > cost[i][j]))
                minCost = cost[i][j];
        if (minCost == -1)
            return -1;
        minCostOutEdge[i] = minCost;
        sumOfMinCostOutEdges += minCost;
    }

    //初始E-節點的根
    heapNode eNode;
    eNode.lowerCost = sumOfMinCostOutEdges;
    eNode.currentCost = 0;
    eNode.restMinCost = sumOfMinCostOutEdges;
    eNode.s = 0;
    eNode.currentTour = new int[n];
    //初始化排列為[1,2,3,...,n,1]
    for (int i = 0; i < n; ++i)
        eNode.currentTour[i] = i + 1;

    int bestCostSoFar = -1;  //當前最佳旅行耗費
    int *currentTour = eNode.currentTour;

    //搜尋排列樹
    while (eNode.s < n - 1)
    {
        currentTour = eNode.currentTour;
        if (eNode.s == n - 2)
        {//葉結點的父節點
         //檢查是否為當前最優旅行
            if (cost[currentTour[n - 2]][currentTour[n - 1]] != -1 &&
                cost[currentTour[n - 1]][1] != -1 &&
                (bestCostSoFar == -1 || eNode.currentCost +
                    cost[currentTour[n - 2]][currentTour[n - 1]] +
                    cost[currentTour[n - 1]][1] < bestCostSoFar))
            {//發現最優旅行,加入小根堆
                bestCostSoFar = eNode.currentCost +
                    cost[currentTour[n - 2]][currentTour[n - 1]] +
                    cost[currentTour[n - 1]][1];
                eNode.currentCost = bestCostSoFar;
                eNode.lowerCost = bestCostSoFar;
                eNode.s++;
                liveNodeMinHeap.push(eNode);
            }
            else
            {
                delete[] eNode.currentTour;  //捨棄非最優的葉結點的父節點,釋放記憶體
                eNode.currentTour = nullptr;
            }
        }

        else
        {//生成子節點
            for(int i = eNode.s + 1; i < n; ++i)
                if (cost[currentTour[eNode.s]][currentTour[i]] != -1)
                {//子節點可行
                    int currentCost = eNode.currentCost +
                        cost[currentTour[eNode.s]][currentTour[i]];
                    int restMinCost = eNode.restMinCost -
                        minCostOutEdge[currentTour[eNode.s]];
                    int leastCostPossible = currentCost + restMinCost;

                    if (bestCostSoFar == -1 ||
                        leastCostPossible < bestCostSoFar)
                    {//子樹可能有更優的葉結點,把當前子樹的根放入小根堆
                        heapNode hNode;
                        hNode.lowerCost = leastCostPossible;
                        hNode.currentCost = currentCost;
                        hNode.restMinCost = restMinCost;
                        hNode.s = eNode.s + 1;
                        hNode.currentTour = new int[n];
                        copy(currentTour, currentTour + n, hNode.currentTour);

                        swap(hNode.currentTour[hNode.s], hNode.currentTour[i]);
                        liveNodeMinHeap.push(hNode);
                    }
                }

            //完成節點擴充套件,釋放記憶體
            delete[] eNode.currentTour;
            eNode.currentTour = nullptr;
        }

        if (liveNodeMinHeap.empty())
            break;

        //取下一個E-節點
        eNode = liveNodeMinHeap.top();
        liveNodeMinHeap.pop();
    }

    if (bestCostSoFar == -1)
        return -1;

    //複製到bestTour
    copy(eNode.currentTour, eNode.currentTour + n, bestTour + 1);

    //釋放小根堆中剩餘元素的currentTour陣列記憶體***雖然小根堆析構,
    //但是currentTour是new的記憶體,依然存在,故必須手動釋放
    while (true)
    {
        delete[] eNode.currentTour;
        if (liveNodeMinHeap.empty())
            break;
        //取下一個E-節點
        eNode = liveNodeMinHeap.top();
        liveNodeMinHeap.pop();
    }

    return bestCostSoFar;
}

void init()
{
    cost[1][1] = -1;
    cost[1][2] = 30;
    cost[1][3] = 6;
    cost[1][4] = 4;

    cost[2][1] = 30;
    cost[2][2] = -1;
    cost[2][3] = 5;
    cost[2][4] = 10;

    cost[3][1] = 6;
    cost[3][2] = 5;
    cost[3][3] = -1;
    cost[3][4] = 20;

    cost[4][1] = 4;
    cost[4][2] = 10;
    cost[4][3] = 20;
    cost[4][4] = -1;
}
void main()
{
    init();

    int bestCost = TSP(bestTour);  //起點定為1,從第二層開始  

    cout << "最少的運費為:" << bestCost << endl;
    cout << "最佳路徑為: ";

    copy(bestTour + 1, bestTour + n + 1, ostream_iterator<int>(cout, "->"));
    cout << bestTour[1] << endl;
}

測試結果

最少的運費為:25
最佳路徑為: 1->3->2->4->1
請按任意鍵繼續. . .

相關推薦

分支——行商(TSP)問題

問題描述 給定一個n頂點網路(有向或無向),找出一個包含n個頂點且具有最小耗費的換路。任何一個包含網路所有頂點的換路稱為一個旅行。旅行商問題(Traveling Salesman Problem,TSP)是要尋找一條耗費最少的旅行。

五大常用演算法之分支

看了五大常用演算法之一這篇博文,感覺理解了很多,可是純粹都是理論,缺少一些示例,所以準備綜合一篇博文,以幫助自己記憶,原文: http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741378.html 一、基本描述

回溯——行商(TSP)問題

問題描述 給定一個n頂點網路(有向或無向),找出一個包含n個頂點且具有最小耗費的換路。任何一個包含網路所有頂點的換路稱為一個旅行。旅行商問題(Traveling Salesman Problem,TSP)是要尋找一條耗費最少的旅行。

演算法課堂實驗報告(五)——python回溯分支限界行商TSP問題)

python實現回溯法與分支限界 一、開發環境 開發工具:jupyter notebook 並使用vscode,cmd命令列工具協助程式設計測試演算法,並使用codeblocks輔助編寫C++程式 程式語言:python3.6 二、實驗目標 1. 請用回溯法求對稱的旅

分支(branch and bound)解裝箱問題

裝箱問題 [ 問題描述 ] 有一個箱子容量為 v( 正整數, 0≤v≤20000) ,同時有 n 個物品 (0≤n≤30) ,每個物品有一個體積 ( 正整數 ) 。要求從 n 個物品中,任取若干個裝入箱內,使箱子的剩餘空間為最小。 [ 樣例 ] 輸入: 10 一個整數,表示

數學建模常用Matlab/Lingo/c程式碼總結系列——行商TSP問題

Lingo程式碼: MODEL: SETS: CITY / 1.. 6/: U; ! U( I) = sequence no. of city; LINK( CITY, CITY): DIST, ! The distance matri

模擬退火算-行商問題-matlab實現

是否 -i ren isp close 交換 coo 準則 matrix 整理一下數學建模會用到的算法,供比賽時候參考食用。 —————————————————————————————————————————— 旅行商問題(TSP): 給定一系列城市和每對城市之間的距離,求

行商(TSP)及相關問題列表

1, 旅行商問題(Traveling Salesman Problem, TSP) 這個問題字面上的理解是:有一個推銷員,要到n個城市推銷商品,他要找出一個包含所有n個城市的具有最短路程的環路。 TSP的歷史很久,最早的描述是1759年尤拉研究的騎士周遊問題,即對於國際象棋棋

遺傳演算法 求解行商 TSP 問題,matlab程式碼

學習啟發式演算法時,旅行商問題是一個經典的例子。其中,遺傳演算法可以用來求解該問題。遺傳演算法是一種進化演算法,由於其啟發式演算法的屬性,並不能保證得到最優解。求解效果與初始種群選取,編碼方法,選擇方法,交叉變異規則有關。 上課時,老師不知從哪裡找了一個非常粗糙的程式,自己

基於分支限界行商問題(TSP)一

//分支限界法 #include<iostream> #include<algorithm> #include<cstdio> #include<queue> const int INF = 100000; const int MAX_N = 22; usin

HDU 3001 Travelling:TSP行商

sta init using 多少 b- upd eof == als 題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=3001 題意:   有n個城市,m條雙向道路,每條道路走一次需要花費路費v。你可以將任意一個城市作為起點出

【算學習】雙調歐幾裏得行商問題(動態規劃)(轉)

png .com 16px 我們 pan 子結構 最小 而且 復雜度 雙調歐幾裏得旅行商問題是一個經典動態規劃問題。《算法導論(第二版)》思考題15-1和北京大學OJ2677都出現了這個題目。 旅行商問題描述:平面上n個點,確定一條連接各點的最短閉合旅程。這個解的一般形式

[數學建模(三)]遺傳算行商問題

size log 數學建模 col randperm pre 個數 blog sum clc,clear sj=load(‘data3.txt‘) %加載敵方100 個目標的數據 x=sj(:,1); y=sj(:,2); d1=[70,40]; sj0=[d1

迷茫的行商:一個無處不在的計算機算問題

jpg tao 技術 分享 com tps .com bsp 說明 掃碼時備註或說明中留下郵箱付款後如未回復請至https://shop135452397.taobao.com/聯系店主迷茫的旅行商:一個無處不在的計算機算法問題

行商問題(Traveling Salesman Problem,TSP)的+Leapms線性規劃模型及c++呼叫

知識點 旅行商問題的線性規劃模型旅行商問題的+Leapms模型及CPLEX求解C++呼叫+Leapms 旅行商問題 旅行商問題是一個重要的NP-難問題。一個旅行商人目前在城市1,他必須對其餘n-1個城市訪問且僅訪問一次而後回到城市1,請規 劃其最短的迴圈路線。 旅行商問題的建模 設城市i,j之間的距

行商問題TSP】【狀態壓縮dp】【記憶化dp】

【題意】 給定一個n個頂點組成的帶權的有向圖的距離矩陣d[i,j],要求從0開始結果所有點一次回到0,問所經過邊的總權重的最小值為多少 【思路】 旅行商問題TSP,狀態壓縮dp,記憶化dp 【分析】 假設當前已經訪問過的頂點集合為S,(起點0當作還未訪問過的點),當

行商問題(TSP,Traveling Salesman Problem)

題意:給定一個n個頂點組成的帶權有向圖的距離矩陣d(I,j)(INF表示沒有邊)。要求從頂點0出發,經過每個頂點恰好一次後再次回到頂點0.問所經過的邊的總權重的最小值是多少? 假設現在已經訪問過的頂點的集合(起點0當作還未訪問過的頂點)為S,當前所在的頂點為v,用dp[S]

TSP(Traveling Salesman Problem)-----淺談行商問題(動態規劃,回溯實現)

  1.什麼是TSP問題   一個售貨員必須訪問n個城市,這n個城市是一個完全圖,售貨員需要恰好訪問所有城市的一次,並且回到最終的城市。   城市於城市之間有一個旅行費用,售貨員希望旅行費用之和最少。   完全圖:完全圖是一個簡單的無向圖,其中每對不同的頂點之間都恰連有一條邊相連。      2.T

遺傳演算法解決TSP行商問題(附:Python實現)

前言 我先囉嗦一下:不是很喜歡寫計算智慧的演算法,因為一個演算法就要寫好久。前前後後將近有兩天的時間。 好啦,現在進入正題。 巡迴旅行商問題(TSP)是一個組合優化方面的問題,已經成為測試組合優化新演算法的標準問題。應用遺傳演算法解決 TSP 問題,首先對訪問

TSP 行商

THUOJ 資料結構(上)TSP 旅行商 點選檢視題目:TSP旅行商 實現思路 建立鄰接表 每讀入一條邊u->v,將其插入u中(後面將實現的tspNode中的邊,是以其為出發點的邊),並將v的入度+1 拓撲排序過程中計算最長道路經過的村莊數 演算法:零入度拓撲排序,p16