回溯法(backtracking)解決平衡集合問題

(原題出自微軟公司面試題)問題如下:

有兩個序列a,b,大小都爲n,序列元素的值任意整數,無序;
要求:通過交換a,b中的元素,使[序列a元素的和]與[序列b元素的和]之間的差最小。
例如:   
var a=[100,99,98,1,2, 3];
var b=[1, 2, 3, 4,5,40];

 

分析:

通過交換的方式,最終的狀態是在保證兩個序列中元素個數相同的條件下,任何一個元素都可以位於兩個序列中的任何一個。這樣問題可以轉化爲:在一個長度爲2*n的整數序列中,如何將元素個數分成兩個子集,記每個子集的元素之和分別爲S1和S2,使得|S1-S2|最小。顯然這是一個最優化問題,如果用brute-force方法,組合數是C(2n,n)=(2n)!/(2*(n!)), 如果n很大這個方法不奏效。

 

這裏採用回溯法(backtracking),即前序(preorder)遍歷狀態空間樹(state-space tree)。難點在於剪枝條件的確定,下面說明如何確定剪枝條件:

注意到如果將原序列按從小到大的順序排好序,每次從較大的元素開始取,可以得到一個這樣的規律:設長度爲2*n序列的元素總和爲Sigma,當前集合元素的和爲S,剩下的元素之和爲Sigma-S,如果二者滿足S>=Sigma-S,即Sigma<=2*S,那麼在當前集合中剩下需要添加進來的元素必須從餘下的元素中取最小的那些元素,這樣才能保證|S1-S2|最小。這是因爲如果在下一次任意從餘下的元素中取的元素分別爲e和f,那麼取e後的兩個子集差爲(S+e) - (Sigma-S-e) = 2S-Sigma +2e,取f後的兩個子集差爲2S-Sigma +2f,顯然如果e>f>0, 則有前者的子集差大於後者的子集差(注意這裏假設元素都爲非負整數,原序列中有負數的情況參考下面的討論)。

 

如果輸入序列中有負整數,可以通過平移操作轉化爲非負,因爲每個數都平移了,它們的差值保值不變。如果不平移,結果不一定正確,比如:輸入的2*n序列爲:-10,5,3,20,25,50,平衡的對半子集應該爲[-10,5,50]和[3,20,25],差值的絕對值爲3。在下面的實現中,如果不考慮平移,得到的錯誤結果卻是[-10,3,50]和[5,20,25],差值的絕對值爲7。

 

另外在狀態空間樹只需要考慮根節點的左枝子樹,因爲原問題考慮的是對半子集。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章