分支定界法——旅行商(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