一、問題描述
某售貨員要到若干城市去推銷商品,已知各城市之間的路程(或旅費)。他要選定一條從駐地出發,經過每個城市一次,最後回到駐地的路線,使總的路程(或總旅費)最小。
如下圖:1,2,3,4 四個城市及其路線費用圖,任意兩個城市之間不一定都有路可達。
二、問題理解
1.分支限界法利用的是廣度優先搜索和最優值策略。
2.利用二維數組保存圖信息City_Graph[MAX_SIZE][MAX_SIZE]
其中City_Graph[i][j]的值代表的是城市i與城市j之間的路徑費用
一旦一個城市沒有通向另外城市的路,則不可能有迴路,不用再找下去了
3. 我們任意選擇一個城市,作爲出發點。(因爲最後都是一個迴路,無所謂從哪出發)
下面是關鍵思路:
想象一下,我們就是旅行員,假定從城市1出發,根據廣度優先搜索的思路,我們要把從城市1能到達的下一個城市,都要作爲一種路徑走一下試試。
可是程序裏面怎麼實現這種“試試”呢?
利用一種數據結構,保存我們每走一步後,當前的一些狀態參數,如,我們已經走過的城市數目(這樣就知道,我們有沒有走完,比如上圖,當我們走了四個城市之後,無論從第四個城市是否能回到起點城市,都就意味着我們走完了,只是這條路徑合不合約束以及能不能得到最優解的問題)。這裏把,這種數據結構成爲結點。這就需要另一個數據結構,保存我們每次試出來的路徑,這就是堆。
數據結構定義如下:
view plaincopy to clipboardprint?
Node{
int s; //結點深度,即當前走過了幾個城市
int x[MAX_SIZE]; //保存走到這個結點時的路徑信息
}
MiniHeap{
//保存所有結點並提供一些結點的操作
}
Node{
int s; //結點深度,即當前走過了幾個城市
int x[MAX_SIZE]; //保存走到這個結點時的路徑信息
}
MiniHeap{
//保存所有結點並提供一些結點的操作
}
a.我們剛開始的時候不知道最總能得到的路徑是什麼,所以我們,就認爲按照城市編號的次序走一遍。於是有了第一個結點0,放入堆 中。 相當於來到了城市1(可以是所有城市中的任意一個,這裏姑且設爲圖中城市1)。
b.從城市1,出發,發現有三條路可走,分別走一下,這樣就產生了三個結點,都放入堆中。
結點1 數據爲:x[1 2 3 4],深度爲s=1(深度爲0表示在城市1還沒有開始走),這說明,結點1是從城市1走來,走過了1個城 市,當前停在城市2,後面的城市3 和城市4都還沒有走,但是具體是不是就按照3.4的順序走,這個不一定。
結點2 數據爲:x[1 3 2 4],深度爲s=1,表示,從城市1走來,走過了1個城市,當前停在了城市3,後面2.4城市還沒走
結點3 數據爲:x[1 4 3 2],深度爲s=1,表示,從城市1走來,走過了1個城市,當前停在了城市4,後面3.2城市還沒有走
c. 從堆中取一個結點,看看這個結點是不是走完了的,也就是要看看它的深度是不是3,是的話,說明走完了,看看是不是能回到起 點,如果可以而且費用少於先前得到最優的費用,就把當前的解作爲最優解。
如果沒有走完,就繼續把這條路走下去。
以上就是簡單的想法,而實際的程序中,堆還需要提供對結點的優先權排序的支持。而當前結點在往下一個城市走時,也是需要約束和限界函數,這些書上講的很清楚,不懂,就翻翻書。
有點要提出來說說的就是結點優先權和限界函數時都用到了一個最小出邊和,就相當於把所有城市最便宜的一條路(邊)費用加起來的值。
下面是代碼
view plaincopy to clipboardprint?
#include <stdio.h>
#include <istream>
using namespace std;
//---------------------宏定義------------------------------------------
#define MAX_CITY_NUMBER 10 //城市最大數目
#define MAX_COST 10000000 //兩個城市之間費用的最大值
//---------------------全局變量----------------------------------------
int City_Graph[MAX_CITY_NUMBER][MAX_CITY_NUMBER];
//表示城市間邊權重的數組
int City_Size; //表示實際輸入的城市數目
int Best_Cost; //最小費用
int Best_Cost_Path[MAX_CITY_NUMBER];
//最小費用時的路徑
//------------------------定義結點---------------------------------------
typedef struct Node{
int lcost; //優先級
int cc; //當前費用
int rcost; //剩餘所有結點的最小出邊費用的和
int s; //當前結點的深度,也就是它在解數組中的索引位置
int x[MAX_CITY_NUMBER]; //當前結點對應的路徑
struct Node* pNext; //指向下一個結點
}Node;
//---------------------定義堆和相關對操作--------------------------------
typedef struct MiniHeap{
Node* pHead; //堆的頭
}MiniHeap;
//初始化
void InitMiniHeap(MiniHeap* pMiniHeap){
pMiniHeap->pHead = new Node;
pMiniHeap->pHead->pNext = NULL;
}
//入堆
void put(MiniHeap* pMiniHeap,Node node){
Node* next;
Node* pre;
Node* pinnode = new Node; //將傳進來的結點信息copy一份保存
//這樣在函數外部對node的修改就不會影響到堆了
pinnode->cc = node.cc;
pinnode->lcost = node.lcost;
pinnode->pNext = node.pNext;
pinnode->rcost = node.rcost;
pinnode->s = node.s;
pinnode->pNext = NULL;
for(int k=0;k<City_Size;k++){
pinnode->x[k] = node.x[k];
}
pre = pMiniHeap->pHead;
next = pMiniHeap->pHead->pNext;
if(next == NULL){
pMiniHeap->pHead->pNext = pinnode;
}
else{
while(next != NULL){
if((next->lcost) > (pinnode->lcost)){ //發現一個優先級大的,則置於其前面
pinnode->pNext = pre->pNext;
pre->pNext = pinnode;
break; //跳出
}
pre = next;
next = next->pNext;
}
pre->pNext = pinnode; //放在末尾
}
}
//出堆
Node* RemoveMiniHeap(MiniHeap* pMiniHeap){
Node* pnode = NULL;
if(pMiniHeap->pHead->pNext != NULL){
pnode = pMiniHeap->pHead->pNext;
pMiniHeap->pHead->pNext = pMiniHeap->pHead->pNext->pNext;
}
return pnode;
}
//---------------------分支限界法找最優解--------------------------------
void Traveler(){
int i,j;
int temp_x[MAX_CITY_NUMBER];
Node* pNode = NULL;
int miniSum; //所有結點最小出邊的費用和
int miniOut[MAX_CITY_NUMBER];
//保存每個結點的最小出邊的索引
MiniHeap* heap = new MiniHeap; //分配堆
InitMiniHeap(heap); //初始化堆
miniSum = 0;
for (i=0;i<City_Size;i++){
miniOut[i] = MAX_COST; //初始化時每一個結點都不可達
for(j=0;j<City_Size;j++){
if (City_Graph[i][j]>0 && City_Graph[i][j]<miniOut[i]){
//從i到j可達,且更小
miniOut[i] = City_Graph[i][j];
}
}
if (miniOut[i] == MAX_COST){// i 城市沒有出邊
Best_Cost = -1;
return ;
}
miniSum += miniOut[i];
}
for(i=0;i<City_Size;i++){ //初始化的最優路徑就是把所有結點依次走一遍
Best_Cost_Path[i] = i;
}
Best_Cost = MAX_COST; //初始化的最優費用是一個很大的數
pNode = new Node; //初始化第一個結點併入堆
pNode->lcost = 0; //當前結點的優先權爲0 也就是最優
pNode->cc = 0; //當前費用爲0(還沒有開始旅行)
pNode->rcost = miniSum; //剩餘所有結點的最小出邊費用和就是初始化的miniSum
pNode->s = 0; //層次爲0
pNode->pNext = NULL;
for(int k=0;k<City_Size;k++){
pNode->x[k] = Best_Cost_Path[k]; //第一個結點所保存的路徑也就是初始化的路徑
}
put(heap,*pNode); //入堆
while(pNode != NULL && (pNode->s) < City_Size-1){
//堆不空 不是葉子
for(int k=0;k<City_Size;k++){
Best_Cost_Path[k] = pNode->x[k] ; //將最優路徑置換爲當前結點本身所保存的
}
/*
* * pNode 結點保存的路徑中的含有這條路徑上所有結點的索引
* * x路徑中保存的這一層結點的編號就是x[City_Size-2]
* * 下一層結點的編號就是x[City_Size-1]
*/
if ((pNode->s) == City_Size-2){ //是葉子的父親
int edge1 = City_Graph[(pNode->x)[City_Size-2]][(pNode->x)[City_Size-1]];
int edge2 = City_Graph[(pNode->x)[City_Size-1]][(pNode->x)[0]];
if(edge1 >= 0 && edge2 >= 0 && (pNode->cc+edge1+edge2) < Best_Cost){
//edge1 -1 表示不可達
//葉子可達起點費用更低
Best_Cost = pNode->cc + edge1+edge2;
pNode->cc = Best_Cost;
pNode->lcost = Best_Cost; //優先權爲 Best_Cost
pNode->s++; //到達葉子層
}
}
else{ //內部結點
for (i=pNode->s;i<City_Size;i++){ //從當前層到葉子層
if(City_Graph[pNode->x[pNode->s]][pNode->x[i]] >= 0){ //可達
//pNode的層數就是它在最優路徑中的位置
int temp_cc = pNode->cc+City_Graph[pNode->x[pNode->s]][pNode->x[i]];
int temp_rcost = pNode->rcost-miniOut[pNode->x[pNode->s]];
//下一個結點的剩餘最小出邊費用和
//等於當前結點的rcost減去當前這個結點的最小出邊費用
if (temp_cc+temp_rcost<Best_Cost){ //下一個結點的最小出邊費用和小於當前的最優解,說明可能存在更優解
for (j=0;j<City_Size;j++){ //完全copy路徑,以便下面修改
temp_x[j]=Best_Cost_Path[j];
}
temp_x[pNode->x[pNode->s+1]] = Best_Cost_Path[i];
//將當前結點的編號放入路徑的深度爲s+1的地方
temp_x[i] = Best_Cost_Path[pNode->s+1]; //??????????????
//將原路//徑中的深度爲s+1的結點編號放入當前路徑的
//相當於將原路徑中的的深度爲i的結點與深度W爲s+1的結點交換
Node* pNextNode = new Node;
pNextNode->cc = temp_cc;
pNextNode->lcost = temp_cc+temp_rcost;
pNextNode->rcost = temp_rcost;
pNextNode->s = pNode->s+1;
pNextNode->pNext = NULL;
for(int k=0;k<City_Size;k++){
pNextNode->x[k] = temp_x[k];
}
put(heap,*pNextNode);
delete pNextNode;
}
}
}
}
pNode = RemoveMiniHeap(heap);
}
}
int main(){
int i,j;
scanf("%d",&City_Size);
for(i=0;i<City_Size;i++){
for(j=0;j<City_Size;j++){
scanf("%d",&City_Graph[i][j]);
}
}
Traveler();
printf("%d/n",Best_Cost);
return 1;
}
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/JarvisChu/archive/2010/10/29/5974895.aspx