1. 程式人生 > >24點演算法詳解--Java程式碼實現

24點演算法詳解--Java程式碼實現

在網上看了很多的24點,結果都不盡人意,然後從學長那弄來了程式碼仔細研究了一番,以下是我對該演算法原理及實現的理解 
注:對於52張 撲克牌構成的27萬多種可能的組合,程式碼經測試最快能達到0.35秒,即可計算出所有解得情況,本文就輸入四個數,得到其所有24點的解的高效的演算法進行詳解

理解原理(前提): 
1.採用四元轉三元, 三元轉二元,二元轉一元的方法,在有四個數時,我們取出其中兩個數,進行加減乘除運算,返回一個數,這樣就實現了四元到三元的簡化。 
PS:到這裡你可能會問了,我怎麼知道該取那兩個數。回答:這裡我們取數是根據優先順序來的(這裡能理解吧,優先順序高的肯定是要先計算的呀)。 
例如:a,b,c,d四個數,在計算時,要麼我們先計算a,b,要麼我們先計算b,c要麼我們先計算c,d,所以,到底該取兩個數,其實已經很明瞭了,如果還有什麼問題,等會可以看看下面的程式碼。 
三元到二元也是同樣的道理,最後二元到一元,就可以得到結果了。 
2.對於四個數,根據算術優先順序,從高到低,一定會有三個運算子。 
3.由1,2我們就可以大概瞭解了,我們可以通過組合不同的優先順序順序,然後嘗試在各個優先順序計算中新增不同的運算子,就可以得到各種結果

步驟: 
1.輸入四個數,對這四個數進行排列組合,比如(10,2,3,6)有(2,3,10,6),(2,6,10,3)等這些組合呀,以便構成不同的算式。 
2.採用三重迴圈的方式,每一次迴圈都會是不同的運算子,這一步是為了得到各種運算子的組合。 
3,然後在三重迴圈裡面,分別進行優先順序組合,同時進行計算,將四元轉化為三元,如下,這裡只展示了四元轉三元,三元轉二元,同理,這一步是為了得到各個優先順序層次上的各種組合。與步驟2一起構成了所有的組合。

PS:這裡看不懂也沒關係,等會將程式碼全部連通了看,就會明瞭些了 
貼程式碼: 
PS: 
allResult()方法是對52張撲克牌進行的組合,如果你需要的不是撲克牌的話,請自行更改,然後這裡的所有方法為靜態方法,便於呼叫。 
PS: 
對撲克牌遊戲演算法的優化方法,來源於我的另一位同學,真的厲害,我只是程式碼的搬運工/笑哭 

times(int i,int j,int k,int l)方法是為了避免撲克牌花色的重複,如果你不需要,可以直接忽略

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

public class CalculateRatio {

    // 將4個運算元簡化為3個運算元時,將這三個運算元存放在temp1中
    private static double[] temp1 = new double[3];
    // 將3個運算元簡化為2個運算元時,將這兩個運算元存放在temp2中
    private static double[] temp2 = new double[2];

    private static double sum;
    private static int[] cardArray = new int[4];
    private static char[] operator = { '+', '-', '*', '/' };
    private static double[] scard = new double[4];
    private static boolean isCorrect = false;

    /**
     * 列出所有不重複的組合
     */
    public static void allResult() {
        int allcount = 0;
        int count = 0;
        int times = 0;
        // 這裡只考慮對資料
        // 為了減少運算量,避免重複的排列組合,比如1,2,3,4與1,2,4,3,這樣就是重複的,採用了j = i, k = j這種來控制迴圈
        for (int i = 1; i < 14; i++) {
            for (int j = i; j < 14; j++) {
                for (int k = j; k < 14; k++)
                    for (int l = k; l < 14; l++) {
                        //判斷花色重複的數量
                        times = times(i, j, k, l);
                        allcount += times;
                        // 判斷該表示式是否正確
                        if (getExpression(i, j, k, l) != null) {
                            count += times;
                        }
                    }
            }
        }

        double rate = (double) count / allcount;
        System.out.println("所有可能的組合共有:" + allcount);
        System.out.println("結果為24的組合共有: " + count);
        System.out.println("成功的機率是:" + rate);
    }

    /**
     * 方法簡述:輸入4個數判斷其能產生多少個結果等於24的算式,就是選出來的四個數一個會有多少種排列組合,這樣是為了得到更多
     * 
     * @param i
     * @param j
     * @param k
     * @param l
     * @return
     */
    public static ArrayList<String> getExpression(int num1, int num2, int num3, int num4) {
        //宣告一個ArrayList,用於存放所有可能的表示式
        ArrayList<String> expressionList = new ArrayList<String>();
        for (int a = 0; a < 4; a++)
            for (int b = 0; b < 4; b++) {
                // 這些判斷是為了防止產生錯誤的組合,比如傳進來的數是1,4,6,8,如果沒有判斷,就可能導致產生1,1,6,8這種組合
                if (b == a) {
                    continue;
                }

                for (int c = 0; c < 4; c++) {
                    if (c == a || c == b) {
                        continue;
                    }

                    for (int d = 0; d < 4; d++) {

                        if (d == a || d == b || d == c) {
                            continue;
                        }
                        // 讓四個數產生了不同的組合,比如1,4,6,8--8,1,6,4等
                        cardArray[a] = num1;
                        cardArray[b] = num2;
                        cardArray[c] = num3;
                        cardArray[d] = num4;

                        for (int m = 0; m < 4; m++) {
                            // 這裡轉換為double型別是為了方便後面的除法運算
                            scard[m] = (double) cardArray[m] % 13;
                            if (cardArray[m] % 13 == 0) {
                                scard[m] = 13;
                            }
                        }

                        // 進行一次搜尋
                        expressionList = search();

                        // 如果搜尋出來的是正確的,那麼將isCorrect置為false,便於下次使用
                        if (isCorrect) {
                            isCorrect = false;
                            return expressionList;
                        }
                    }
                }
            }

        return null;
    }

    /**
     * 方法簡述:基本計算
     * 
     * @param number1
     *            數字1
     * @param number2
     *            數字2
     * @param operator
     *            數字3
     * @return
     */
    private static double calcute(double number1, double number2, char operator) {
        if (operator == '+') {
            return number1 + number2;
        } else if (operator == '-') {
            return number1 - number2;
        } else if (operator == '*') {
            return number1 * number2;
        } else if (operator == '/' && number2 != 0) {
            return number1 / number2;
        } else {
            return -1;
        }
    }

    private static ArrayList<String> search() {

        //宣告一個ArrayList,用於存放所有可能的表示式
        ArrayList<String> expressionList = new ArrayList<String>();
        // 第一次放置的符號(算術優先順序最高)
        for (int i = 0; i < 4; i++) {

            // 第二次放置的符號(算術優先順序次高)
            for (int j = 0; j < 4; j++) {

                // 第三次放置的符號(最後一個計算)
                for (int k = 0; k < 4; k++) {

                    // 首先計算的兩個相鄰數字,共有3種情況,相當於括號的作用,也就是各種優先順序順序組合
                    for (int m = 0; m < 3; m++) {
                        // 如果出現除數為零則表示式出錯,結束此次迴圈
                        if (scard[m + 1] == 0 && operator[i] == '/') {
                            break;
                        }
                        // 從4個運算元中提取出兩個數,然後將可能進行優先計算的兩個數計算,然後得到值,注意:這裡是優先順序最高的運算子,也就是第一次放置的符號
                        temp1[m] = calcute(scard[m], scard[m + 1], operator[i]);
                        // 將其餘兩個沒有進行計算的值賦值給temp1陣列
                        temp1[(m + 1) % 3] = scard[(m + 2) % 4];
                        temp1[(m + 2) % 3] = scard[(m + 3) % 4];

                        // 先確定首先計算的兩個數字,計算完成相當於剩下三個數,按順序儲存在temp1陣列中
                        // 三個數字選出先計算的兩個相鄰數字,兩種情況,相當於第二個括號
                        for (int n = 0; n < 2; n++) {
                            if (temp1[n + 1] == 0 && operator[j] == '/') {
                                break;
                            }
                            // 簡化運算,將三個運算元簡化為兩個運算元,注意:這裡是優先順序最高的運算子,也就是第二次放置的符號
                            temp2[n] = calcute(temp1[n], temp1[n + 1], operator[j]);
                            temp2[(n + 1) % 2] = temp1[(n + 2) % 3];

                            if (temp2[1] == 0 && operator[k] == '/') {
                                break;
                            }

                            // 將兩個運算元簡化為一個運算元,注意:這裡是優先順序最高的運算子,也就是第三次放置的符號
                            sum = calcute(temp2[0], temp2[1], operator[k]);

                            // 如果能夠24,那麼將該算式輸出來
                            if (sum == 24) {
                                isCorrect = true;
                                String expression = "";

                                // 根據組合列出算式
                                if (m == 0 && n == 0) {
                                    expression = "((" + (int) scard[0] + operator[i] + (int) scard[1] + ")"
                                            + operator[j] + (int) scard[2] + ")" + operator[k] + (int) scard[3] + "="
                                            + (int) sum;
                                } else if (m == 0 && n == 1) {
                                    expression = "(" + (int) scard[0] + operator[i] + (int) scard[1] + ")" + operator[k]
                                            + "(" + (int) scard[2] + operator[j] + (int) scard[3] + ")=" + (int) sum;
                                } else if (m == 1 && n == 0) {
                                    expression = "(" + (int) scard[0] + operator[j] + "(" + (int) scard[1] + operator[i]
                                            + (int) scard[2] + "))" + operator[k] + (int) scard[3] + "=" + (int) sum;
                                } else if (m == 2 && n == 0) {
                                    expression = "(" + (int) scard[0] + operator[j] + (int) scard[1] + ")" + operator[k]
                                            + "(" + (int) scard[2] + operator[i] + (int) scard[3] + ")=" + (int) sum;
                                } else if (m == 2 && n == 0) {
                                    expression = (int) scard[0] + operator[k] + "(" + (int) scard[1] + operator[j] + "("
                                            + (int) scard[2] + operator[i] + (int) scard[3] + "))=" + (int) sum;
                                }

                                System.out.println(expression);
                                expressionList.add(expression);
                            }
                        }
                    }
                }
            }
        }
        return expressionList;
    }

    /**
     * 利用對花色的排列組合,避免花色重複,大量地減少運算量,提高效率
     * @param i
     * @param j
     * @param k
     * @param l
     * @return
     */
    private static int times(int i,int j,int k,int l){
        //利用set判斷有多少種重複
        Set<Integer> set =new HashSet<Integer>();
        set.add(i);
        set.add(j);
        set.add(k);
        set.add(l);
        //當4個數的數字全部一樣時,只可能有一種花色的組合,比如紅桃6,梅花6,方塊6,黑桃6
        if(set.size()==1){
            return 1;
        }
        //當4個數中,有兩個數相同,其餘的數都不相同時,就有C4取2乘以C4取1乘以C4取1種可能的花色組合,比如紅桃6,梅花6,方塊7,黑桃8
        else if(set.size()==3){
            return 96;
        }
        //當4個數全部不同時,就有C4取1乘以C4取1乘以C4取1乘以C4取1種(256)情況
        else if(set.size()==4){
            return 256;
        }
        else{
            //當4個數中,兩兩相同時,同樣的,利用排列組合,一共有C4取2乘以C4取2,共36種情況
            if((i==j&&k==l)||(i==k&&j==l)){
                return 36;
            }
            //當4個數中有三個數相同,另外一個數不同時
            else {
                return 16;
            }
        }
    }
}