數獨求解 物件導向解決演算法問題(一)

2022-03-07 18:22:37 字數 3979 閱讀 1601

最近遇到乙個演算法題,名字叫做數獨求解,問題描述如下:

在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 任何時刻都不能將...