深度和廣度優先分油問題(C#實現)
最後更新:2017-02-28
來源:互聯網
上載者:User
問題 分油問題
-、問題描述
分油問題:兩個小孩去打油,一人帶了一個一斤的空瓶,另一個帶了一個七兩和一個三兩的空瓶。原計劃各打一斤油,可是由於所帶的錢不夠,只好合打了一斤油,在回家的路上,二人想平分這一斤油,可是又沒有其它工具。現只用這三個瓶子(一斤、七兩、三兩)精確地分出兩個半斤油來。
二、演算法描述
F 演算法選擇
通過分析題目並結合深度優先、廣度優先和迭代加深搜尋的演算法的特點以及有缺點,這裡選擇廣度優先演算法來求解該分油問題。如果採用深度優先演算法搜尋,由於其盲目性導致搜尋陷入局部陷阱,並不一定能求得解即使得到解也不一定是最優解,因此並不採用此演算法。迭代加深搜尋則是在固定的深度上進行深度和廣度搜尋結合的策略來進行搜尋,這樣避免了單一的深度搜尋無法得到解的缺點,但是找到的解並不一定是最優解。廣度優先以犧牲空間代價和時間代價來換取保證取得最優解。由於該問題並不複雜,即使使用廣度優先演算法也不會佔有太多的空間和時間,因此為了取得最優解這裡選擇廣度優先演算法來求解。
F 演算法描述
1. 用unVisitedBttsArr表示初始節點列表(待擴充,此為一個動態數組)
2. 如果unVisitedBttsArr為空白集,則退出並給出失敗訊號
3. n取為unVisitedBttsArr的第一個節點,並在 unVisitedBttsArr中刪除節點n,放入已訪問節點列表haveVisitedBttsArr
4. 如果n為目標節點,則退出並給出成功訊號
5. 否則,將n的子節點加到N的末尾,並返回2)步
F 問題分析
l 選擇合適的資料結構表示問題狀態
F 用向量(T,S,R)表示狀態——T表示10兩瓶中的油量,S表示7兩瓶中的油量,R表示3兩瓶中的油量。
F 問題的起始狀態:(10,0,0)。
F 問題的目標狀態:(5,2,3)或者(5,3,2)或者(5,5,0)。
l 確定智能運算元,用以表示變化狀態的規則。由於總共油量為10兩,而10兩的瓶可以裝滿所有的油,因此可以把10兩的瓶當作一個大油桶,這樣此題就化為和上一題8/6類似的問題了。因此在描述變化狀態的時候只需給出7、3瓶的狀態即可,10瓶的狀態即為10-S-R的油量。可是由於在程式處理上的一致性,在程式的實現上我還是把10、8、6的瓶子統一處理,而不是用兩個狀態表示。
號
規則
解釋
1
(S,R) and S<7 à (7,R)
7斤瓶不滿時裝滿
2
(S,R) and R <3 à (S,3)
3斤瓶不滿時裝滿
3
(S,R) and S >0 à (0,R)
7斤瓶不空時倒空
4
(S,R) and R >0 à (S,0)
3斤瓶不空時倒空
5
(S,R) and S>0 and S+R≤3à (0,S+R)
7斤瓶中油全倒入3斤瓶
6
(S,R) and R >0 and S+R≤7à (S+R,0)
3斤瓶中油全倒入7斤瓶
7
(S,R) and S<7 and S+R≥7à (7, S+R -7)
用3斤瓶油裝滿7斤瓶子
8
(S,R) and R <3 and S+R≥3à (S+R -3,3)
用7斤瓶油裝滿3斤瓶子
三、程式設計
演算法使用C#語言來實現的。程式主要根據上面提供的廣度優先演算法的描述來對演算法進行實現的。程式共有四個類:
Bottle類,用來描述瓶子的狀態以及一些行為動作和屬性。
WidthSearch類,是廣度優先搜尋演算法的實作類別
DepthSearch類,是深度優先搜尋演算法的實作類別
MainForm類,是介面設計的類。
這裡提供兩個演算法的實現主要是為了做個對比。
以下主要對幾個核心演算法的程式實現進行說明介紹。
//瓶子類
public class Bottle
{
int Capability = 0 ;//瓶子的總容量
int Current = 0 ;//當前瓶子中的油量
int Remain = 0 ;//瓶子中剩餘的量
//倒入油的行為動作
public void Add(int val)
{
Current += val;
Remain -= val;
}
//倒出油的行為動作
public void Sub(int val)
{
Current -= val;
Remain += val;
}
//深度優先演算法實作類別
public class DepthSearch
public void Searching()
{
Random r = new Random();
while(bottle_10.CurrentVal != 5) //判斷是否達到目標狀態
{
int random = r.Next(1,7);//用隨機產生的數來隨機確定下一個子狀態
switch(random)
{
case 1 :
FlowTo(bottle_03,bottle_07);
break;
case 2 :
FlowTo(bottle_10,bottle_03);
break;
case 3 :
FlowTo(bottle_07,bottle_03);
break;
case 4 :
FlowTo(bottle_10,bottle_07);
break;
case 5 :
FlowTo(bottle_03,bottle_10);
break;
case 6 :
FlowTo(bottle_07,bottle_10);
break;
default :
break;
}
if(!stateArr.Contains(BottlesState()))
{
stateArr.Add(BottlesState());
}
else
{
ReturnToPreState();
}
}
}
//倒油的動作。這裡是從bottle1倒到bottle2
private void FlowTo(Bottle bottle1,Bottle bottle2)
{
if(bottle1.CurrentVal>bottle2.RemainVal)
{
bottle1.Sub(bottle2.RemainVal);
bottle2.CurrentVal = bottle2.CapabilityVal;
}
else
{
bottle2.Add(bottle1.CurrentVal);
bottle1.CurrentVal = 0;
}
}
//廣度優先搜尋實作類別
public class WidthSearch
public void S(TreeNode node)
{
while(unVisitedBttsArr.Count>=0) //未訪問表中如果有結點繼續迴圈搜尋否則跳出
{
TreeNode n = (TreeNode)unVisitedBttsArr[0];
bool flag = true;
//檢查是否已經訪問過
foreach(TreeNode i in haveVisitedBttsArr)
{
if(i.Text.Equals(n.Text))
{
haveVisitedBttsArr.Add(unVisitedBttsArr[0]);
unVisitedBttsArr.RemoveAt(0);
flag = false;
break;
}
}
//若已經遍曆過的不需要繼續遍曆 跳到下一個
if(flag)
{
if(Search(n))
{
return;
}
}
}
}
//建立子結點並加入到unVisitedBttsArr中。
private bool CreateNode(TreeNode node)
{
TreeNode n = new TreeNode(BottlesState());
unVisitedBttsArr.Add(n);
if(bottle_10.CurrentVal == 5)
{
node.Nodes.Add(n);
SetPath(n);
return true;
}
node.Nodes.Add(n);
return false;
}
//回溯取得最佳路徑
private void SetPath(TreeNode n)
{
while(n.Parent!=null)
{
path = n.Text + " -> " + path;
n.ForeColor = System.Drawing.Color.Blue;
n = n.Parent;
}
}
四、實驗結果
如下是實驗結果:
1)深度優先隨機產生子結點的搜尋路徑
2)廣度優先演算法實現圖
從以上兩個結果來看,如果存在解則廣度優先總能找到最優解,但是從時間上來看深度優先更快而廣度優先需要遍曆每個結點造成時間空間都開銷比較大,所以時間上肯定花的比較多。但是可以保證找到最優解。此問題由於比較簡單,複雜度不高,只需在第九步就可以找到最優解了,因此深度優先是可取的,但是如果是在某些複雜的問題中,此演算法就可能導致組合爆炸,佔用空間過大導致演算法的不可行。
請指點。要來源程式的可email給我,代碼沒有最佳化。