1.題目描述:設有n件工作分配給n個人。將工作j分配給第i個人所需的
費用爲Cij。設計一個算法,使每個人都有一件不同的工作,並使總費用最少。
數據輸入:第一行有一個正整數n,接下來是N行,每行N個數,表示工作費用。
結果輸出:將計算最小費用輸出。
2.問題分析:
常規思想:使用窮舉法搜索,算法時間複雜度爲N!,但此法採用分枝限界 來求解該問題,在不對實際問題求解的情況下,如何求出一個最優成本的
下界呢?很顯然,任何解的成本都不會小於矩陣每一行的最小元素的和。不同於回溯法總是生成最近一個有希望節點的單個子女。分枝限界法總是擴展最有希望的子結點。如果能找到一個最小下界(搜索最小值的情況下)或最大上界(搜索最大值的情況下),那麼在搜索時,總是擴展小於最小下界或者大於最大上界的結點。當求得的值小於所有未擴展結點的最小下界或者大於所有未擴展結點的最大上界時,停上搜索。
在此問題中,是求費用的最小值,只要能找出擴展結點的最小下界,就可進行剪枝。但怎樣求最小下界呢?可以明任何可行解都不可能大於每一行最小元素的和。因此將每一行最小元素的各作爲此搜索算法的一個下界,然後進行剪枝。對擴展的結點求出下界,對有最小下界的節點擴充,一直搜索到N層,如果第N層有最小下界的節點值小於所有未擴展的節點,則停止搜索。否則,擴展那些最小下界值小於當前節點最小下界的節點。本算法採用了最小堆,每次將新生成的節點壓堆,然後擴展堆的根節點。如果根節點已經是第N層的節點,搜索停止。
3.代碼:
void CWorkAllocation::Run(){
for (int i=0;i<m_nCount;++i){ //新建初始節點,將第一個工作分別分配給(0-N)個人
CTask* pAllocation = new CTask;
pAllocation->AllocateTask(i);
int lb = CalculateLowBound(pAllocation->GetAllocationState()); //計算最小下界
pAllocation->SetLowBound(lb);
m_Allocation.push_back(pAllocation);
}
make_heap(m_Allocation.begin(),m_Allocation.end(),Less); //設置最小堆
CTask* pA = GetMiniNode(); //取下界最小的節點並刪除
while(pA->GetAllocationNum() < m_nCount){ //直到工作分配完成
int numAllocated = pA->GetAllocationNum();
const vector<int> AllocationState = pA->GetAllocationState();
bool* person = new bool[m_nCount];
memset(person,false,sizeof(bool)*m_nCount);
for (int i=0;i<AllocationState.size();++i) //將已經分配過的任務的人標記
person[AllocationState[i]] = true;
for (int i=numAllocated;i<m_nCount;++i){//擴展pA的葉子節點
CTask* pTask = new CTask(*pA);
for(int j=0;j<m_nCount;++j)
if (!person[j]){ //沒分配過任務
person[j] =1;
pTask->AllocateTask(j);
int lb = CalculateLowBound(pTask->GetAllocationState()); //最小下界
pTask->SetLowBound(lb);
break;
}
m_Allocation.push_back(pTask);
push_heap(m_Allocation.begin(),m_Allocation.end(),Less);
}
delete []person;
delete pA;
pA = GetMiniNode(); //取下界最小的節點並刪除
}
m_nMiniCost = pA->GetLowBound();
delete pA;
}
4.算法複雜度分析:
最好的情況下,只需要每一層搜索一次。時間複雜度爲線性 O(n),最壞的情況每個節點都被擴充。時間複雜度爲O(n!);