最近遇到乙個演算法題,名字叫做數獨求解,問題描述如下:解決這樣的演算法問題,面向過程語言通常是最佳方法,因為第一:出題的人不希望看到你愛物件導向語言中封裝的一些特性,第二,物件導向的效率相對較低。在9*9的方陣中,包含了81個小格仔(九行九列),其中又再分成九個小正方形(稱為宮),每宮有九小格。遊戲剛開始時,盤面上有些小格已經填了數字(稱為初盤),遊戲者要在空白的小格中填入1到9的數字,使得最後每行、每列、每宮中都包含1到9,但不允許出現重複的數字,而且每乙個遊戲都只有乙個唯一的解答(稱為終盤)。如下給出兩個示例:
但是另一方面,物件導向的利用性、易讀性、易維護性以及其大量的物件特性和內庫,為我們解決演算法問題提供了良好的解決方法,費話少說,下面講一下我的解題步驟。
第一步:抽象
在這裡,我共抽象出三個類,分別是grid(每個單元格)、gridrow(每一行)、gridrowcollection(整個棋盤),具體如下:剩下的就是前台了,介面如下:1. grid類:
本題中的最小單元,代表乙個單元格,且必須屬於一行。實現了icompare介面,重寫了copareto()方法,用以比較不兩隻單元格的大小,其各個成員的解釋如下:
rowinde:單元格所在行的座標
cellindex:單元格所在列的座標
gridrow:唯讀屬性,單元格所在的行物件
value:單元格上的值,如1~9
version:單元格上的數字被修改的次數
chooseablevalues:單元值上可選的值的陣列,如示例一中,單元格[3,0]可選的數字為{1,2}
chooseindex:當前所行的值在chooseablevalues中的座標,以來做回溯用
2. gridrow類:
實現了icollection介面,用以實現該行上單元格的新增、刪除等操作。
count:該行上單元格的數目
grids:該行上單元格組成的陣列
gridrowcollection:該行所屬的棋盤物件
rowindex:該行的索引
this:索引,返回該行指定列上的單元格
3. gridrowcolllection類:
實現了icollection介面,用以實現該行上單元格的新增、刪除等操作。
count:返回該棋盤上所有的格仔陣列
gridaspects:維數,即行數或列數,本題中為9
rows:返回當前棋盤的所有行陣列
this:返回指定行
this[ , ]:返回指定位置的單元格
其中下拉列表用來選擇不同的初始化情況,即不兩隻的棋盤。numberupdown控制項用來設定棋盤維數,panel中的各個按鈕就來示棋盤中的格仔了,其中藍色的來示初始化的資料,不允許被修改。
看**:
下拉列表中的事件:
private void combobox1_selectedindexchanged(object sender, eventargs e)///foreach (grid g in gridcollection.grids) }
// 來用載入按鈕
firstloadgrids();
}其中函式結尾處的firstloadgrids()表示第一次載入(初始化)時,載入各個按鈕,**如下:
/// 第一次載入每行上的格仔
///
private void firstloadgrids()
_", g.rowindex, g.cellindex);
btn.tabstop = false;
if (g.value != 0)
btn.width = 25;
btn.height = 25;
if (g.version == -1 && g.value != 0)
int x = 0 + btn.width * g.cellindex + 5;
int y = 0 + btn.height * g.rowindex + 5;
btn.location = new point(x, y);
this.panel1.controls.add(btn); } }
好了,接下來講核心部分,即演算法的實現:stacks = new stack();
///
/// 遞迴、回溯求解
///
private void loadgrids()
catch (invalidoperationexception)
if (g == null)
int mincount = this.gridcollection.gridaspects;
foreach (grid gitem in this.gridcollection.grids)
gitem.chooseablevalues = this.getchooseablevalues(gitem);
if (gitem.chooseablevalues.count < mincount) }
if (g.chooseablevalues.count > 0)
_", g.rowindex, g.cellindex);
this.panel1.controls[btnname].text = g.value.tostring();
} else
grid pregrid = s.peek();
while ((pregrid.chooseablevalues.count <= 1) || (pregrid.chososeindex == pregrid.chooseablevalues.count - 1))
pregrid.value = 0;
pregrid.version = pregrid.version - 2;
pregrid.chososeindex++;
} loadgrids();
}講解:
第一步:找出所有格仔中未被賦值的單元格
第二步:從這些單元格中,根據其行、列上其他的已經確定的值,判斷每個單元格上可選值的數目,選取可選數最小的乙個單元格。如示例一中,單元格【3,0】,可選值只有{1,2}兩個值,其他的所有單元格的可選值均大於(或等於2),所以我們第一步就選【3,0】
第三步:從其可選的值中挑選乙個,並值給該單元格。並將該單元格加入乙個全域性的棧中,記錄起來
第四步:遞迴呼叫第二步和第三步,直到最後會有兩種走向:
1.如果所有的單元格中都有數字時,問題求解出來了(當然這這情況很少發生);
2.計算到某個單元格時,發現其無可選值了,那麼就說明前面所選的單元格中,至少有乙個是選錯了的。如是,後退一步(我們稱為回溯),到上乙個計算的單元上(該單元格已經入庫)。
2.1 從棧中將當元格出棧,從其可選的數字中,選取其他的可選的值(即chooseindex++),然後遞迴到第二步。
2.2 如果棧中的值限完,或者所有的單元格均已填滿,則表示演算法完成。
由於時間關係,現在必須去睡覺了,接下來的我可以明天再寫。具體要寫的為:
1.這樣物件導向的寫法有什麼好處?有什麼缺點呢?
2.系統中存在很多嚴重的設計問題,你是否發現了?
3.本題中,用棧來進行記錄操作,那麼是否可以用佇列呢?哪個更好?為什麼?
4.這個題還有很多其他的演算法,希望大家一起來討論。
最好附上原碼:
數獨求解 物件導向解決演算法問題(二)
昨天發了一篇 數獨求解 物件導向解決演算法問題 估計有很多人想拍磚了吧,呵呵,我先頂住,接著上上第二篇,在此篇中,主要回答以下幾個問題 1.這樣物件導向的寫法有什麼好處?有什麼缺點呢?有很多人都說這題不應該用物件導向的做法做。因為首先,我的演算法中借助了過多的語言特性,如汎型,如棧和佇列,如類。而這...
分治思想解決演算法問題
不多bb,o n 2 include using namespace std void solve int f 5000 int m cin m for int i 0 i m i cin a i f 0 0 for int i 1 i m i f i f i 1 sum cout f m 1 en...
漢諾塔問題解決演算法
問題描述 假設有3 個分別命名為x,y,z的塔座,在塔座 x上插有 n個直徑大小各不相同 依小到大編號為1,2,n個圓盤。現要求將 x軸上的 n個圓盤移至塔座 z上並仍按同樣順序疊排,圓盤移動時必須遵守下列規則 1 每次只能移動乙個圓盤 2 圓盤可以插在x,y和z中的任一塔座上 3 任何時刻都不能將...