1. 程式人生 > >24點演算法講解與實現

24點演算法講解與實現

題目描述:在52張撲克牌中(去掉大小王),隨機抽取4張牌,找到所有可能的情況和解。

前言

       博主曾在網上看到有很多關於24點的演算法,但很多都是沒有找全所有表示式,要麼就是沒有去重,而且搜尋的時間過長,有些慢的要半個小時才能得到結果難過。所以經過我的不懈努力奮鬥,經過幾天的研究,目前我的這個24點的演算法能夠高效的找出所有解的情況微笑。經過測試,平均在0.1幾秒左右就可以找到所有情況。

演算法分析

       本演算法採用窮舉的思路,對所有數字和操作符進行組合,從而找到所有的情況。 首先想想,如果是人來計算24點,應該怎麼計算?比如1,2,3,4.  首先我們可能會思考一下,然後得出結果(1+2+3)*4=24。 對,沒錯,那麼現在對上面思考的過程進行仔細分析一下,24是怎麼來的?6*4=24,對吧,那麼6是怎麼來的?6=1+2+3。 現在有個問題,雖然我們一眼就能看出1+2+3=6,但是這個過程還是經過了2步計算,首先我們計算1+2=3,然後在計算3+3=6,然後再計算6*4=24,對吧。也就是說我們會從這4個數中選擇2個數來進行計算,然後得到一個結果,在將這個結果與剩下的2個數中選擇一個數來計算,簡單來說就是就是每次我們只會計算2個數,當然這2個數可能有好幾種不同的運算。 下面我們再來看看13,13,13,12這幾個數,咋一看這個好像沒有上面的數字好算了,不能一下得出結果,這裡給出3種解(當然不止3種):(13+13)/(13/12) 和 13-((13/13)-12) 和 (13+12)-(13/13),我們再來想想,每次只運算2個數,直到得出結果。        OK,明白了上面,我們再來分析下計算機是怎麼運算的。其實也是同樣的思路,每次只運算2個數,然後將結果拿去進行下一次運算。那麼現在我們有4個數,如1,2,3,4. 在這4個數位置不變的情況下, 第一次選擇2個數來計算,有哪些情況?如果位置不變,那只有3種情況:(1-2)-3-4, 1-(2-3)-4, 1-2-(3-4)(先假設操作符都是-號)。那第2次計算呢,有下面幾種情況,如果第1次是(1-2)-3-4,則第2次可能是((1-2))-3-4和(1-2)-(3-4),如果第1次是1-(2-3)-4,則第2次可能是(1-(2-3))-4和1-(2-(3-4)),如果第1次是1-2-(3-4),則第2次可能是(1-2)-(3-4)和1-(2-(3-4)), 然後第3次運算就只剩2個數了,在計算這2個數的結果是不是24就行了。        上面我們假設的是操作符都是-號,但實際情況可能有很多種,現在我們還是在這4個數位置不變的情況下,再來改變操作符,即每次2個數進行運算的時候,有4種情況,即1-2,  1+2,  1*2,  1/2,(在這2個數位置不變的情況下)。那麼下次進行計算的時候呢?同樣有4種情況(+-*/),最後一次計算(第3次)同理。這樣我們就找到了在這4個數位置不變的情況下的所有解的情況。         那麼接下來再考慮這4個數位置變化的情況,即1,2,3,4  可以變成4,3,2,1   和1,4,2,3等。同理,當位置變化時,我們按照上面的方法重新計算。這樣就可以找出每一種情況啦。這裡用的是排列組合,如1,2,3,4有24種不同的排列。 以下是具體程式碼:
public class Expression {
	//用來判斷是否有解
	private static boolean flag = false;
	//存放操作符
	private static char[] operator = { '+', '-', '*', '/' };
	//存放所有結果
	private static ArrayList<String> results = new ArrayList<String>();
	
	public static void main(String[] args) {
		//所有正確的解的個數
		int rightCount = 0;
		//所有情況的個數
		int allCount = 0;
		//存放4個數字
		double[] number = new double[4];
		
		long startTime = System.currentTimeMillis();
		
	/*	第1次去重,過濾掉可能產生的重複的情況,比如1,2,3,4  和4,3,2,1
		因為後面是通過排列組合來找出所有情況,1,2,3,4可以組合成4,3,2,1
		這樣就重複了,這裡為了過濾掉這些重複的*/
		for (int i = 1; i <= 13; i++) {
			for (int j = i; j <= 13; j++) {
				for (int k = j; k <= 13; k++) {
					for (int m = k; m <= 13; m++) {
						number[0] = i;
						number[1] = j;
						number[2] = k;
						number[3] = m;
						//由於過濾掉重複的,這裡重新計算重複的次數(在計算所有情況的個數時需要)
						//如果你不需要計算所有情況的個數,可以不需要
						int count = times(i, j , k ,m);
						allCount += count;
						duplicateRemoval(number);
						//判斷是否有解
						if(flag == true){
							rightCount += count;
							flag = false;
						}
					}
				}
			}
		}
		long endTime = System.currentTimeMillis();
		
		for (int i = 0; i < results.size(); i++) {
			System.out.println(results.get(i));
		}
		System.out.println("共耗費時間:" + (endTime - startTime) + "ms");
		System.out.println("所有可能的個數:" + allCount);
		System.out.println("有解的個數:" + rightCount);
		System.out.println("有解的機率" + (double)rightCount/allCount);
	}
	
	/**
	 * 由於最開始過濾掉一部分重複的情況,但這些重複情況是存在的
	 * 這裡是為了計算每種重複情況有多少次數,如當3張牌相同,另一張牌不同時,
	 * 如3,3,3,5  抽牌時有16種不同的情況(根據花色的不同)
	 * 而在計算時為了去重把這些過濾掉了,這裡是為了重新計算這些情況
	 * 如果你不需要計算所有情況的個數,可以不需要次方法
	 */
    private static int times(int i,int j,int k,int m){
        //判斷有多少種重複
        Set<Integer> set = new HashSet<Integer>();
        set.add(i);
        set.add(j);
        set.add(k);
        set.add(m);
        if(set.size() == 1){
        	//當4個數的數字全部一樣時(不同花色),只可能有一種組合
            return 1;
        } else if(set.size() == 3){
        	//當4個數中,有兩個數相同,其餘的數都不相同時
            return 96;
        } else if(set.size() == 4){
        	//當4個數全部不同時
            return 256;
        } else{
            if((i == j && k == m)||(i == k && j == m)||(i == m && k == j)){
            	//當4個數中,兩兩相同時
                return 36;
            } else {
            	//當4個數中有三個數相同,另外一個數不同時
                return 16;
            }
        }
    }
    
         /**
	 * 第2次去重,由於排列組合可能導致數字組合的重複
	 * 這裡進行第2次過濾,只計算給定4個數的所有不同的排列
	 */
	private static void duplicateRemoval(double[] number){
		Map<Double, Integer> map = new HashMap<Double, Integer>();
		//存放數字,用來判斷輸入的4個數字中有幾個重複的,和重複的情況
		for (int i = 0; i < number.length; i++) {
			if(map.get(number[i]) == null){
				map.put(number[i], 1);
			} else {
				map.put(number[i], map.get(number[i]) + 1);
			}
		}
		if(map.size() == 1){
			//如果只有一種數字(4個不同花色的),此時只有一種排列組合,如6,6,6,6
			calculation(number[0], number[1],number[2],number[3]);
		} else if(map.size() == 2){
			//如果只有2種數字,有2種情況,如1,1,2,2和1,1,1,2
			int index = 0;//用於資料處理
			int state = 0;//判斷是那種情況
			for (Double key : map.keySet()) {
				if(map.get(key) == 1){
					//如果是有1個數字和其他3個都不同,將number變為 number[0]=number[1]=number[2],
					//將不同的那個放到number[3],方便計算
					number[3] = key;
					state = 1;
				} else if(map.get(key) == 2){
					//兩兩相同的情況,將number變為number[0]=number[1],number[2]=number[3]的情況,方便計算
					number[index++] = key;
					number[index++] = key;
				} else {
					number[index++] = key;
				}
			}
			//列出2種情況的所有排列組合,並分別計算
			if(state == 1){
				calculation(number[3], number[1], number[1], number[1]);
				calculation(number[1], number[3], number[1], number[1]);
				calculation(number[1], number[1], number[3], number[1]);
				calculation(number[1], number[1], number[1], number[3]);
			}
			if(state == 0){
				calculation(number[1], number[1], number[3], number[3]);
				calculation(number[1], number[3], number[1], number[3]);
				calculation(number[1], number[3], number[3], number[1]);
				calculation(number[3], number[1], number[1], number[3]);
				calculation(number[3], number[3], number[1], number[1]);
				calculation(number[3], number[1], number[3], number[1]);
			}
		} else if(map.size() == 3){
			//有3種數字的情況
			int index = 0;
			for (Double key : map.keySet()) {
				if(map.get(key) == 2){
					//將相同的2個數字放到number[2]=number[3],方便計算
					number[2] = key;
					number[3] = key;
				} else {
					number[index++] = key;
				}
			}
			//排列組合,所有情況
			calculation(number[0], number[1], number[3], number[3]);
			calculation(number[0], number[3], number[1], number[3]);
			calculation(number[0], number[3], number[3], number[1]);
			calculation(number[1], number[0], number[3], number[3]);
			calculation(number[1], number[3], number[0], number[3]);
			calculation(number[1], number[3], number[3], number[0]);
			calculation(number[3], number[0], number[1], number[3]);
			calculation(number[3], number[0], number[3], number[1]);
			calculation(number[3], number[1], number[0], number[3]);
			calculation(number[3], number[1], number[3], number[0]);
			calculation(number[3], number[3], number[0], number[1]);
			calculation(number[3], number[3], number[1], number[0]);
		} else if(map.size() == 4){
			//4個數都不同的情況
			getNumber(number);
		}
	}
	
	/**
	 * 排列組合,用來處理4個數都不同的情況
	 * 如1,2,3,4  可以轉化為1,3,2,4   2,3,1,4    1,4,2,3等
	 * 並計算每種的結果
	 */
	public static void getNumber(double[] number){
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				if(i == j){
					continue;
				}
				for (int k = 0; k < 4; k++) {
					if(k == j || k == i){
						continue;
					}
					for (int m = 0; m < 4; m++) {
						if(m == k || m == j || m == i){
							continue;
						}
						calculation(number[i], number[j], number[k], number[m]);
					}
				}
			}
		}
	}
	
	/**
	 * 給定4個數,當這4個數位置不變時,只改變操作符號,計算所有的可能性
	 * 如1+2+3+4  ,1*2*3*4 , 1-2+3*4 等
	 * 如果能得到24點,就將表示式新增到結果集
	 */
	public static boolean calculation(double num1, double num2, double num3, double num4){
		for (int i = 0; i < 4; i++) {
		/*	第一次計算,儲存此時的操作符和計算結果
			此時有3中情況,相當於從4個數中選擇2個相鄰的數來計算
			如(1-2)-3-4, 1-(2-3)-4, 1-2-(3-4)
			則儲存此時第一次計算的結果和操作符*/
			char operator1 = operator[i];
			//根據操作符,先計算第1,2兩個數,如輸入數字是1,2,3,4  則計算1+2(1-2,1*2,1/2等),
			//這裡通過迴圈來改變操作符,下同
			double firstResult = calcute(num1, num2, operator1);
			//根據操作符,先計算第2,3兩個數,如輸入數字是1,2,3,4  則計算2+3
			double midResult = calcute(num2, num3, operator1);
			//根據操作符,先計算第3,4兩個數,如輸入數字是1,2,3,4  則計算3+4
			double tailResult = calcute(num3, num4, operator1);
			for (int j = 0; j < 4; j++) {
				/*	第2次計算,儲存此時的操作符和計算結果
				此時有5中情況,相當於從4個數中選擇2個相鄰的數來計算
				如((1-2)-3)-4, (1-(2-3))-4, (1-2)-(3-4),1-((2-3)-4),1-(2-(3-4))
				則儲存此時第2次計算的結果和操作符*/
				char operator2 = operator[j];
				//根據操作符和第1次計算的結果,計算第2次的情況,如第一次計算是(1-2)-3-4,
				//就計算((1-2)-3)-4 ,則第一次計算結果為1-2=-1  -->   即計算-1-3,即firstResult-3
				//下面的原理類似
				double firstMidResult = calcute(firstResult, num3, operator2);
				double firstTailResult = calcute(num3, num4, operator2);
				double midFirstResult = calcute(num1, midResult, operator2);
				double midTailResult = calcute(midResult, num4, operator2);
				double tailMidResult = calcute(num2, tailResult, operator2);
				for (int k = 0; k < 4; k++) {
					//最後1次計算,得出結果,如果是24則儲存表示式,原理同上
					char operator3 = operator[k];
					if(calcute(firstMidResult, num4, operator3) == 24){
						String expression = "((" + (int)num1 + operator1 + (int)num2 + ")" + operator2 + (int)num3 + ")" + operator3 + (int)num4;
						results.add(expression);
						flag = true;
					}
					if(calcute(firstResult, firstTailResult, operator3) == 24){
						String expression = "(" + (int)num1 + operator1 + (int)num2 + ")" + operator3 + "(" + (int)num3 + operator2 + (int)num4 + ")";
						results.add(expression);
						flag = true;
					}
					if(calcute(midFirstResult, num4, operator3) == 24){
						String expression = "(" + (int)num1 + operator2 + "(" + (int)num2 + operator1 + (int)num3 + "))" + operator3 + (int)num4;
						results.add(expression);
						flag = true;
					}
					if(calcute(num1, midTailResult, operator3) == 24){
						String expression = "" + (int)num1 + operator3 + "((" + (int)num2 + operator1 + (int)num3 + ")" + operator2 + (int)num4 + ")";
						results.add(expression);
						flag = true;
					}
					if(calcute(num1, tailMidResult, operator3) == 24){
						String expression = "" + (int)num1 + operator3 + "(" + (int)num2 + operator2 + "(" + (int)num3 + operator1 + (int)num4 + "))";
						results.add(expression);
						flag = true;
					}
				}
			}
		}
		return flag;
	}
	
	/**
	 * 給定2個數和指定操作符的計算
	 * @date 2017年12月22日 下午2:47:49 
	 */
	 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;
	        }
	    }

}
這是GitHub:https://github.com/1404510094/24-java-.git

相關推薦

24演算法講解實現

題目描述:在52張撲克牌中(去掉大小王),隨機抽取4張牌,找到所有可能的情況和解。 前言        博主曾在網上看到有很多關於24點的演算法,但很多都是沒有找全所有表示式,要麼就是沒有去重,而且搜尋的時間過長,有些慢的要半個小時才能得到結果。所以經過我的不懈努力,經過幾

最簡單24演算法,可任意實現n數n,一看就明!

介紹 網上的24點演算法基本上都專注於4張牌湊24點,有的演算法甚至枚舉了所有括號的組合,讓人看得頭暈眼花。這些演算法若是推廣到n個數湊n點,基本都歇菜了。 本演算法採用暴力列舉法,能處理任意個數湊任意值。以24點為例,本演算法會枚舉出4個數+3個運算子能組成的所

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

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

關於24演算法的思想和程式碼實現

先簡單介紹一下24點遊戲:給出4個1-9之間的自然數,其中每個數字只能使用一次;任意使用 + - * / ( ) ,構造出一個表示式,使得最終結果為24,這就是常見的算24點的遊戲。比如兩道比較經典的題目:1,5,5,5和3,3,8,8,先自己試試,答案貼在文章最後^_^

24計算器的Javascript實現

實現 百度 itl 分享圖片 叠代優化 ddl 好運 重復 cat 前段時間小舅子(小學生)過來玩,沒事一起玩3*8=24,遇到難算的半天都想不出來,所以就想有沒有app或者小工具啥的,搜了一下,有工具,但是不好用,所以就想自己寫個簡單易用的。 開始著手做的時候,發現運算

文字分類——NLV演算法研究實現

內容提要 1 引言 2 NLV演算法理論 2.1 訓練模型 2.2 分類模型 3 NLV演算法實現 3.1 演算法描述 4 實驗及效能評估 4.1 實驗設計 4

特徵選擇——Matrix Projection演算法研究實現

內容提要 引言 MP特徵選擇思想 MP特徵選擇演算法 MP特徵選擇分析 實驗結果 分析總結 引言   一般選擇文字的片語作為分類器輸入向量的特徵語義單元,而作為單詞或詞語的片語,在任何一種語言中都有數萬或數十萬個。另外

機器學習 (七) 決策樹演算法研究實現

前言        從決策樹這三個字中我們既可以看出來它的主要用途幫助決策某一類問題,樹是輔助我們來決策用的,如下圖一個簡單的判斷不同階段人年齡的圖:       &

DSL 系列(1) - 擴充套件的論述實現

前言 DSL 全稱為 domain-specific language(領域特定語言),本系列應當會很長,其中包含些許不成熟的想法,歡迎私信指正。 1. DSL 簡述 我理解的 DSL 的主要職能是對領域的描述,他存在於領域服務之上,如下圖所示: 其實,我們也可以認為 DomainService

DSL 系列(1) - 擴展的論述實現

instance RoCE union caller move llb his java 偽代碼 前言 DSL 全稱為 domain-specific language(領域特定語言),本系列應當會很長,其中包含些許不成熟的想法,歡迎私信指正。 1. DSL 簡述? 我理

RSA演算法講解Go語言例項

一、RSA演算法概述 RSA是"非對稱加密演算法",非對稱加密演算法需要兩個金鑰:公開金鑰(publickey)和私有金鑰(privatekey)。公鑰與私鑰是配對的,用公鑰加密的資料只有配對的私鑰才能解密,反之亦然。因加解密使用兩個不同的金鑰,所以這種演算法叫

歸併排序,堆排序,快速排序的講解實現

前言 本文主要講的是歸併排序,堆排序和快速排序的原理與實現。當然我的實現不一定不是最好的,如果有更好的實現大家也可以在評論區貼出更好的實現程式碼。 時間複雜度的計算:時間複雜度大概的意思是以每一條執行的語句為單位,一個排序演算法在隨著資料的增大時間上會以什麼形式去增長(這

希爾排序演算法原理實現

1.問題描述 輸入:n個數的序列<a1,a2,a3,...,an>。 輸出:原序列的一個重排<a1*,a2*,a3*,...,an*>;,使得a1*<=a2*<=a3*<=...<=an*。 2. 問題分析 例如,假設有

十大經典排序演算法 講解,python3實現

接受各位指出的錯誤 重點推薦!!! 這個網址可以看到各個演算法的執行的直觀過程,找到sort 勉強推薦這個吧,前面的幾個演算法圖解還好,後面的幾個就不好了 演算法概述 這部分內容來自這麼大牛 演算法分類 十種常見排序演算法可以分為兩大類: 非線性時間比較類排序:通過

磁碟排程演算法設計實現——C語言

一、設計分析共享裝置的典型代表為磁碟,磁碟物理塊的地址由柱面號、磁頭號、扇區號來指定,完成磁碟某一個物理塊的訪問要經過三個階段:尋道時間Ts、旋轉延遲時間Tw和讀寫時間Trw。尋道時間Ts是磁頭從當前磁軌移動到目標磁軌所需要的時間;旋轉延遲時間Tw是當磁頭停留在目標磁軌後,目

演算法導論-最大子陣列問題-線性時間複雜度演算法分析實現

之前寫了最大子陣列問題的分治法,今天把這個問題的線性時間複雜度的演算法寫出來。 這個方法在演算法導論最大子陣列問題的課後思考題裡面提出來了,只是說的不夠詳細。 思考題如下:使用如下思想為最大子陣列問題設計一個非遞迴的,線性時間複雜度的演算法。從陣列左邊界開始,由左至右處理,

Java排序演算法分析實現:快排、氣泡排序、選擇排序、插入排序、歸併排序(一)

轉載  https://www.cnblogs.com/bjh1117/p/8335628.html   一、概述:   本文給出常見的幾種排序演算法的原理以及java實現,包括常見的簡單排序和高階排序演算法,以及其他常用的演算法知識。   簡單排序:氣泡排序、選擇排序、

演算法--中級演算法題目實現

1、區間求值 我們會傳遞給你一個包含兩個數字的陣列。返回這兩個數字和它們之間所有數字的和。 最小的數字並非總在最前面。 2、找出陣列間的差別 比較兩個陣列,然後返回一個新陣列,該陣列的元素為兩個給定陣列中所有獨有的陣列元素。換言之,返回兩個陣列的差異 3、數

常見限流演算法研究實現

一、限流場景        很多做服務介面的人或多或少的遇到這樣的場景,由於業務應用系統的負載能力有限,為了防止非預期的請求對系統壓力過大而拖垮業務應用系統。也就是面對大流量時,如何進行流量控制?服務介面的流量控制策略:分流、降級、限流等。本文討論下限流策略,雖然降低了服務

SM2演算法第二十五篇:ECDSA數字簽名演算法原理實現

---------------------------------------------轉載原因------------------------------------------------- 這邊部落格中有關 EC_KEY_set_private_key和EC_KEY_set_public_key