24點計算器問題 C 實現

2021-09-24 02:41:07 字數 4267 閱讀 3844

24 點遊戲是乙個很有意思的數字遊戲,也是一道常見的演算法面試題。題目是這樣的:任給四個數(為了便於人們心算或口算,一般都是小於 10 的數),對四個數字用各種組合進行加、減、乘、除四則運算,看看結果是否能等於 24?對於面試題來說,這是乙個典型的窮舉型別演算法問題。這個題目比較有意思的地方是它除了要對數字組合進行列舉,還要對四個運算子進行組合。我們要介紹的方法有點特殊,它沒有簡單地使用窮舉遍歷,而是採用窮舉法和分治法相結合的方法來解決這個問題,這種方法比數字 + 運算子一起列舉的方法簡單,容易理解,整個演算法只有大約 40 行有效**,其中主體部分有效**只有不到 20 行,快來看看是怎麼回事兒吧。

這個演算法的難點主要兩個

數字的運算子和窮舉遍歷

將四則運算表示式作為結果輸出

引言部分提到過,我們這個方法會用到分治法,分治法的主要特點之一就是通過分解子問題的方式減小問題的規模,怎麼劃分子問題呢?子問題和原始問題必須是同構的,所謂同構就是問題必須是一樣的,問題的模式不能變,能變的只是問題的規模。

對於這個問題來說,原始問題的規模是 4 個數字計算 24 點,那麼分解子問題可以從兩個方向考慮:

我們的思路是每次從 4 個數字中任選兩個,分別應用加、減、乘、除四種運算方法得到 4 個計算結果,每個計算結果與剩下的 2 個數字一起組成乙個規模為 3 個數字的子問題,一共可以得到 4 個子問題,其變化過程如圖所示(圖中第一行的數字為 4 個數字的索引位置,第二行的數字分別是 4 個待計算數字,第三行是計算過程),使用第乙個數字和第二個數字組合計算,得到了一組 4 個子問題,然後用第乙個數字和第三個數字組合計算,得到了另一組 4 個子問題:

從 3 個數字中任選兩個,然後應用加、減、乘、除四種運算方法得到 4 個計算結果,每個計算結果與剩下的 1 個數字組成乙個規模為兩個數字的子問題,又可以得到 4 個子問題。從 3 個數字中任選兩個進行不重複的排列,可以得到 $p_^ $= 6個組合結果,也就是說總共有 6 × 4 = 24 個規模為兩個數字的子問題,下圖展示了其中一組,也就是第乙個數 3/7 和第二個數 3 的組合情況:

第三層組合計算將問題規模減少到1個數字

以上就是我們介紹的窮舉法 + 分治法解決 24 點計算問題的演算法分析過程。前面提到過,這個問題的難點有兩個,上述分析過程解決了第乙個,即數字和運算子的窮舉遍歷問題。**還有第二個問題,也就是將四則運算表示式作為結果輸出的問題沒有解決。**可能大家已經從幾個圖上看到了,圖的第三行就是最後要輸出的中綴表示式,這是怎麼做到的呢?其實很簡單,我們給每個數字都指定了乙個「出身」,所謂的「出身」就是描述這個數字的來歷,或者是計算過程。每個數字的「出身」記錄了這個數字的計算過程,當數字被帶入到子問題的時候,這個計算過程也跟著被帶入到子問題,並隨著子問題的求解過程一步一步帶到最後。對於原問題來說,4 個數字的出身就是數字本身,當兩個數字參與一次計算稱為乙個結果數字時,就將這兩個數字的計算過程作為結果數字的「出身」。

好了,根據上面的分析,我們已經明確了問題和子問題的定義,就是「用 m 個數排列組合計算 24 點(1⩽

m⩽4)

(1\leqslant m \leqslant 4)

(1⩽m⩽4

)」。所以我們的子問題的引數就是 m 個數,考慮用陣列來組織這 m 個數。每個數除了數字本身,還有乙個出身,用以下資料結構來描述這個「數」:

typedef struct

number;

num_str是這個數的「出身」,用字串描述沒問題,num是數字本身,但是資料型別用了 double,這也是實際計算過程的需要,畢竟從上圖中也能看到,我們的計算方法是支援分數形式的,中間計算過程會出現浮點數,最終子問題定義就是calc24()函式的引數:

void calc24(const std::vector& nums)

//原始問題的定義

std::vectornumbers = ,,, };

calc24(numbers);

遞迴作為一種演算法的實現方式,與分治法是一對兒天然的好朋友

calc24()函式對子問題進行處理的時候,要對子問題規模是 1 個數的情況做處理,這實際上也是遞迴函式的退出(遞迴終止)條件。對於這個問題來說,當子問題的規模是 1 個數的時候,就要檢查這個數是否是 24,如果是則輸出一組結果,並退出遞迴處理;如果不是,說明這個窮舉出來的結果是個無效結果,直接退出遞迴處理。這部分判斷和處理的實現在第 4 行開始的 if (count == 1) 處理流程裡,比較簡單,就不多說了。對於子問題規模大於 1 的情況,就要選兩個數進行計算,對於 pn2

p_^pn

2​問題,常用的**實現模式就是兩重迴圈。

void calc24(std::vector& nums)

return;

}//兩重迴圈,從 nums 中找兩個數的組合

for (std::size_t i = 0; i < count; i++)

}calc24(sub_nums); //解決子問題}}}}}

現在說說acops,它是乙個計算函式的陣列,定義了對兩個運算元的加、減、乘、除四種運算。std::function是個可呼叫物件包裝器,這裡包裝的是乙個這樣的呼叫介面:

bool (const number&, const number&, number&)
這個介面有兩個const number&型別的入參,乙個 number& 型別的出參和乙個bool型別的返回值,兩個入參是參與計算的運算元,出參是計算的結果。四個操作符對應的可呼叫物件是用lamda表示式定義的操作函式。這些操作函式的的作用很簡單,就是計算兩個運算元,當然,還有很重要的一點,就是拼裝計算結果的「出身」。前面分析演算法的時候提到過,乙個數的「出身」很重要,即使數字本身算對了,如果「出身」拼裝的不正確,輸出的結果也不正確。「出身」拼裝很簡單,就是將參與計算的兩個數的「出身」用操作符連線在一起,然後兩端加上一對兒括號,就得到結果數字的「出身」了。這裡面只有除法比較特殊一點,因為被除數不能為 0,所以加了個判斷。當其返回 false 的時候,相當於做了一次剪枝操作。

std::functionacops = 

, (const number& d1, const number& d2, number& dr)

,(const number& d1, const number& d2, number& dr)

,(const number& d1, const number& d2, number& dr)

};

#include #include #include #include #include typedef struct

number;

std::functionacops =

, (const number& d1, const number& d2, number& dr)

,(const number& d1, const number& d2, number& dr)

,(const number& d1, const number& d2, number& dr)

};void calc24(const std::vector& nums)

return;

}//兩重迴圈,從numbers中找兩個數的組合

for (std::size_t i = 0; i < count; i++)

}calc24(sub_nums); //解決子問題}}

}}}int main()

,,, };

//std::vectornumbers = ,,, };

//std::vectornumbers = ,,, };

//std::vectornumbers = ,,, };

calc24(numbers);

return 0;

}

24點 計算器

24點計算器 沒別的辦法,只有把各種可能都計算一遍,然後得到能計算出24的表示式,並且盡可能的排除掉重複的表示式 假設有a b c d四個數,操作符用op代替,則有 a op b op c op d 或 a op b op c op d 兩種形式 程式用c 2.0開發,支援計算 8 3 8 3 24...

24點紙牌遊戲計算器

includebool flag 判斷是否有解 function 兩個數之間的計算 param float x 第乙個數 float y 第二個數 int z 運算符號 return float 運算結果 float calculate float x,float y,int z function ...

計算器c 實現

1.中綴表示式轉字尾表示式 中綴 a b c d e 字尾 abc de 轉換步驟 1 從左到右掃瞄中綴表示式,遇到 轉 6 2 遇到運算元直接輸出 不進棧 3 遇到 則連續出棧輸出,直到遇到 為止 出棧但不輸出 否則 4 若是其它操作符,則與棧頂的操作符比較優先順序 若優先順序小於棧頂的優先順序,...