一、試探回溯法(N皇后問題)
一、試探回溯法概念
在介紹試探回溯法的概念之前,先簡要介紹一個簡短的古希臘神話故事,邪惡的半人半牛藏身於一個複雜的迷宮,很難找到它並殺死它,就是能成功殺死它怎麼回來也是個難事。不過,在公主阿里阿德涅的幫助下,英雄忒修斯還是想到辦法並消滅了怪物,並輕輕鬆鬆地走出了迷宮,他是怎麼做到的呢?
其實,忒修斯使用的法寶很簡單,就是一團普通的繩,他將繩子的一段系在迷宮的入口上,然後用手拿著另一端進入迷宮,在此後不斷檢查各個角落的過程中,線團或收或放,跟隨著他輾轉於曲折的迷宮中,確保它不會迷路。此外,為保證搜尋過的角落不會重複,忒修斯在他已經檢查過的而且確保不會有怪物的地方用粉筆做好記號,下次不會再去。
故事講完了,古希臘神話故事中往往蘊含著很多哲理,這個故事也不例外,其實忒修斯的高招,與現代計算機中求解一些問題的試探回溯演算法異曲同工,其中忒修斯探索未知角落就是試探,而發現該角落是錯誤的的並留下記號返回,這個過程是回溯,也可以叫做剪枝。
試探:從長度上逐步向目標解靠近的嘗試。
回溯(剪枝):做為解的區域性特徵,特徵字首在試探的過程中一旦被發現與目標解不合,則收縮到此前一步的長度,然後繼續試探下一可能的組合。
應用:試探回溯法可解決若干元素為達到某種結果的最優排序方式問題,如N皇后問題和迷宮問題。
二、N皇后問題
世界歷史上曾經出現一個偉大的羅馬共和時期,處於權力平衡的目的,當時的政治理論家波利比奧斯指出:“事涉每個人的權利,絕不應該讓任何權利大的壓過其它力量,使他人無法立足於平等條件與之抗辯的地步。”這類似著名的N皇后問題,即在NXN格的國際象棋上擺放N個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,請問有多少中擺法,並將每種擺法打印出來。圖1所示即是擺法的一種。
對於N皇后問題,若採用普通窮舉演算法,則所有可能為n!=O(n^n)的複雜度,讓人難以接受,故採用試探回溯法,可以儘早地發現不可能的情況進行回溯(剪枝),從而大大提高求解效率。
思路:N個皇后,N*N的方陣,已知queen不能同行,那麼每行肯定都有一個皇后,那麼問題轉變成求N個y在滿足皇后問題這一條件下的所有排序可能。
(1) 首先構造描述queen的類,其中重要的是判斷queen之前是否衝突的==運算子。
#pragma once class queen { public: //皇后的座標(x,y) int x, y; //建構函式 queen(int xx = 0, int yy = 0) :x(xx), y(yy) {} //解構函式 ~queen(){} //成員函式 bool operator==(const queen& q) const //過載判等運算子,判斷皇后間是否衝突(同行、同列、正對角、反對角) { if (x == q.x || y == q.y) return true; //同行或者同列 if ((x - q.x) == (y - q.y)) return true; //正對角 if((x-q.x)==(q.y-y)) return true; //反對角 return false; } bool operator!=(const queen& q) const { return !(this->operator==(q)); } };
(2) 求解演算法
int placeQueen(int N) //試探回溯法解決N皇后問題
{
stack<queen> solu; //快取當前解的路徑 y
queen q(0, 0); //第一個queen放在(0,0)
int nSolu = 0;
while (q.x >= 0) //如果沒嘗試完所有情況則繼續(迭代的最後會一直回溯到空)
{
while ((solu.size() < N)) //未形成全解,則繼續
{
while ((q.y < N) && (solu.find(q))) //橫向遍歷y的所有可能(當y沒出界且當前queen的試探位置和已放置的queen衝突)
{
q.y++;
}//橫向遍歷(y)結束
if (q.y < N) //未出界則試探成功
{
solu.push(q); //生長當前解
q.x++; //繼續下一行(一行一個queen)
q.y = 0;
}
else //出界,則上一個queen的位置導致無解,回溯(剪枝)
{
if (solu.empty()) //已經無試探點可彈,則確認問題無解
{
q.x = -1;
cout << "======所有情況遍歷完畢======" << endl;
break;
}
q = solu.pop(); //嘗試上個無解試探點的下一列
q.y++;
}
}
//出解
if (solu.size() < N)
cout << "無其他解" << endl;
else
{
nSolu++;
cout << "第" << nSolu << "個解: " << endl;
q = solu.pop(); //返回上層繼續尋找
q.y++;
}
}
return nSolu;
}