演算法基礎(1) 列舉

2021-07-03 23:23:10 字數 3248 閱讀 2699

列舉在大家看來可能是乙個非常簡單的問題,不就是乙個遍歷演算法嘛,有什麼好說的,然而,在參加了北京大學mooc的演算法基礎後,我直接被震驚了。原來列舉演算法還能這麼玩!

好吧,不說有的沒得沒得了,先來看第乙個例子——熄燈問題

這個問題的描述如下:

乙個由按鈕組成的矩陣,其中每行有6個按鈕,共5行。每個按鈕的位置上有一盞燈。當按下乙個按鈕後,該按鈕以及周圍位置(上邊、下邊、左邊、右邊)的燈都會改變一次。即,如果燈原來是點亮的,就會被熄滅;如果燈原來是熄滅的,則會被點亮。在矩陣角上的按鈕改變3盞燈的狀態;在矩陣邊上的按鈕改變4盞燈的狀態;其他的按鈕改變5盞燈的狀態。

在上圖中,左邊矩陣中用x標記的按鈕表示被按下,右邊的矩陣表示燈狀態的改變。對矩陣中的每盞燈設定乙個初始狀態。請你按按鈕,直至每一盞等都熄滅。與一盞燈毗鄰的多個按鈕被按下時,乙個操作會抵消另一次操作的結果。在下圖中,第2行第3、5列的按鈕都被按下,因此第2行、第4列的燈的狀態就不改變。

請你寫乙個程式,確定需要按下哪些按鈕,恰好使得所有的燈都熄滅。

。。。。。。。。
但是,其實他的基本思路是和列舉演算法思路是差不多的。列舉的思路在於,先找到乙個不變數,在課程中,老師將它定義為區域性,不是那個菊。。。

這個區域性有什麼特徵呢?特徵是當你確定了這個區域性的具體值時,其他所有的資料都只能依照這個區域性的數值來變化。

在這個問題中,區域性就是第一行,為什麼這麼說呢?當我們只要確定了第一行按下的狀態,那麼其餘行的按下狀態就也已經確定了!如果你仔細思考一下,會發現這是理所當然的,第一行經歷了一次按下後,那麼勢必會出現燈亮或燈不亮的一些場景,那麼只有第二行的按下狀態才能影響第一行燈的狀態了。也就是說,只有第二行按鈕才能熄滅第一行亮著的燈!

同理,也只有第三行的按鈕才能熄滅第二行的燈,於是乎,一輪不可抗的力量就會向後面的燈按鈕湧去,致使你不按也得按!再掙扎也沒用了!人類!哈啊哈哈(好中二。。。

就像這樣
我們回過頭來再分析一下演算法的時間,我們首先一一枚舉第一行等的所有按鈕按下的狀態,第一行有6盞燈,每盞燈可以有按下和不按兩種狀態,於是他們所有的結果就是2^6=64,然後要將所有的燈遍歷一次,那麼總遍歷數就是64*5*6。如果我們從列的一行開始列舉,那麼列舉狀態數量將是2^5=32,但是在現實中,還要考慮快取記憶體的關係。所以隨便哪種都可以。

寬度=w,高度=h,w小於h

我們可以很容易的推算出時間複雜度為theta(2^w)。指數級也是沒辦法的事=_=#

下面就看一下它的偽**實現:

得到輸入puzzle[5][6]二維陣列

初始化按下陣列press[5][6]

for 第一行press[1]的每一種按下去的狀態

計算之後的的press陣列的按下狀態

看看puzzle陣列的地五行是否全部燈滅 puzzle[5]

如果全部燈滅,則列印press陣列

否則繼續

如果沒有正確的press陣列,那麼就報告說,沒有方案可以使得燈全部熄滅!

下面我們來把偽**轉換成c++**

經過作者一小時的程式設計。。。才覺得課程中的**簡直不要太好!它的優點是沒有進行賦值操作,如果我按照自然順序的話,也就是檢測按下燈,而改變燈的狀態,那是相當費時費空間的。。。。。。可是老師好像沒有給原始碼的樣子。。。

#include 

void printtowdimensionarray(size_t row, size_t col);

void printresult(int n);

bool guess();

int eachconvertonezero(int val);

bool checkisinrange(int value, int lower = 0, int upper = 5);

static

int press[5][6], puzzle[5][6];

int main() //for col

}//for row

if (guess())

bool isok = false;

//every case in first row of press

for (size_t i = 1; i < 64; i++)

if (guess())

}if (isok)

}//for case number

getchar();

getchar();

return0;}

void printtowdimensionarray(size_t row, size_t col)

std::cout

<< '\n';

}}void printresult(int i)

bool guess()

}auto presscopy = press;

for (size_t row = 0; row < 5; row++)

if (presscopy[row][col])

if (checkisinrange(row + 1, 0, 4))

if (checkisinrange(col - 1, 0, 5))

if (checkisinrange(col + 1, 0, 5))

puzzlecopy[row][col] = eachconvertonezero(puzzlecopy[row][col]);}}

}for (size_t i = 0; i < 6; i++)

}return

true;

}int eachconvertonezero(int val)

bool checkisinrange(int value, int lower, int upper)

雖然我這個要比老師的**直觀一點點。。。不過在執行效率和空間效率上以及**量上都不太理想。。。

好了,還有乙個青蛙跳的問題,以及兩道作業,額。。。人生好艱難=_=#

c 基礎筆記 1 列舉

定義列舉一般不用中文,就像sql的欄位名一樣,效率會有影響,下面就實現為列舉值添自定義中文說明,然後獲取。1.建立乙個類,既然要新增自定義屬性,就必須繼承attribute public class enumhelper attribute public string description 2.建...

演算法入門 1 列舉法。

以下都是通過列舉法解決問題的。其實列舉法的本質就是把所有問題可能的結果都嘗試一邊,再通過某種條件將錯誤的結果篩選出去,留下的便是正確的結果。輸出乙個直角三角形,符合 注釋中的那個三角形格式。輸出乙個個三角形 輸入 5 輸出的是乙個五行的三角形 第一行 1個 第二行 2個 第五行五個 第一步 我們需要...

演算法基礎(一) 列舉

慕課 程式設計與演算法 二 演算法基礎 郭瑋老師課程的學習筆記 列舉,基於逐個嘗試答案的一種文體求解策略,根據所有可能的情況,並且一一判斷。大家可能會說列舉不就是一種簡單的將資料全都遍歷一遍嗎,但是在這裡我們將列舉進行優化,讓列舉更加聰明,從而達到演算法的目的,即提供更快更好的解題的方法,這樣列舉就...