前面我們定義了表達式的算法,通常的 24 點常用的算法,盡管都是窮舉,也有幾個常用的不同的算法,其中之一有人稱為動態(tài)規(guī)劃算法:
把多元運算轉(zhuǎn)化為兩元運算,先從四個數(shù)中取出兩個數(shù)進行運算,然后把運算結(jié)果和第三個數(shù)進行運算, 再把結(jié)果與第四個數(shù)進行運算。在求表達式的過程中,最難處理的就是對括號的處理,而這種思路很好的避免了對括號的處理?;谶@種思路的一種算法: 因為能使用的 4 種運算符 – * / 都是2元運算符,所以本文中只考慮 2 元運算符。2元運算符接收兩個參數(shù),輸出計算結(jié)果,輸出的結(jié)果參與后續(xù)的計算。
由上所述,構(gòu)造所有可能的表達式的算法如下:
(1) 將 4 個整數(shù)放入數(shù)組中
(2) 在數(shù)組中取兩個數(shù)字的排列,共有 P(4,2) 種排列。對每一個排列,
(2.1) 對 – * / 每一個運算符,
(2.1.1) 根據(jù)此排列的兩個數(shù)字和運算符,計算結(jié)果
(2.1.2) 改表數(shù)組:將此排列的兩個數(shù)字從數(shù)組中去除掉,將 2.1.1 計算的結(jié)果放入數(shù)組中
(2.1.3) 對新的數(shù)組,重復步驟 2
(2.1.4) 恢復數(shù)組:將此排列的兩個數(shù)字加入數(shù)組中,將 2.1.1 計算的結(jié)果從數(shù)組中去除掉
可見這是一個遞歸過程。步驟 2 就是遞歸函數(shù)。當數(shù)組中只剩下一個數(shù)字的時候,這就是表達式的最終結(jié)果,此時遞歸結(jié)束。
在程序中,一定要注意遞歸的現(xiàn)場保護和恢復,也就是遞歸調(diào)用之前與之后,現(xiàn)場狀態(tài)應(yīng)該保持一致。
在上述算法中,遞歸現(xiàn)場就是指數(shù)組,2.1.2 改變數(shù)組以進行下一層遞歸調(diào)用,2.1.3 則恢復數(shù)組,以確保當前遞歸調(diào)用獲得下一個正確的排列。
括號 () 的作用只是改變運算符的優(yōu)先級,也就是運算符的計算順序。所以在以上算法中,無需考慮括號。括號只是在輸出時需加以考慮。
使用這個算法的一個 Scala 實現(xiàn)如下:
def solve(vs: List[Int],n: Int = 24){
def isZero(d: Double) = Math.abs(d) < 0.00001
//解析為恰當?shù)闹芯Y表達式
def toStr(any: Any): String = any match {
case (v: Double,null,null,null) => v.toInt.toString
case (_,v1: (Double,Any,Any,Any),v2: (Double,Any,Any,Any),op) =>
if(op=='-'&&(v2._4=='+'||v2._4=='-'))
"%s%c(%s)".format(toStr(v1),op,toStr(v2))
else if(op=='/'){
val s1 = if(v1._4=='+'||v1._4=='-') "("+toStr(v1)+")" else toStr(v1)
val s2 = if(v2._4==null) toStr(v2) else "("+toStr(v2)+")"
s1 + op + s2
}
else if(op=='*'){
val s1 = if(v1._4=='+'||v1._4=='-') "("+toStr(v1)+")" else toStr(v1)
val s2 = if(v2._4=='+'||v2._4=='-') "("+toStr(v2)+")" else toStr(v2)
s1 + op + s2
}
else toStr(v1) + op + toStr(v2)
}
//遞歸求解
val buf = collection.mutable.ListBuffer[String]()
def solve0(xs: List[(Double,Any,Any,Any)]): Unit = xs match {
case x::Nil => if(isZero(x._1-n) && !buf.contains(toStr(x))){ buf += toStr(x); println(buf.last)}
case _ => for{ x @ (v1,_,_,_) <- xs;val ys = xs diff List(x)
y @ (v2,_,_,_) <- ys;val rs = ys diff List(y)
}{ solve0((v1+v2,x,y,'+')::rs)
solve0((v1-v2,x,y,'-')::rs)
solve0((v1*v2,x,y,'*')::rs)
if(!isZero(v2)) solve0((v1/v2,x,y,'/')::rs)
}
}
solve0(vs map {v => (v.toDouble,null,null,null)})
}
測試如下:
scala> solve(List(5,5,5,1))
(5-1/5)*5
5*(5-1/5)
scala> solve(List(3,3,8,8))
8/(3-8/3)
這個算法的來源于網(wǎng)上,很簡短的代碼就實現(xiàn)了算 24 的算法,Scala 還是比較強大的:-)
不過我們這里還是采用另外一種方法,來介紹Scala編程的多個方面。
這個算法就是列出 4 個數(shù)字加減乘除的各種可能性。我們可以將表達式分成以下幾種:首先我們將 4 個數(shù)設(shè)為 a,b,c,d,,將其排序列出四個數(shù)的所有排序序列組合(共有 24 種組合)。再進行符號的排列表達式,其中算術(shù)符號有+,—,*,/,(,)。其中有效的表達式有 a*(b-c/b),a*b-c*d,等等。列出所有有效的表達式。其中我們用枚舉類型將符號定義成數(shù)字常量。