码迷,mamicode.com
首页 > 其他好文 > 详细

拼100

时间:2016-07-09 19:27:49      阅读:179      评论:0      收藏:0      [点我收藏+]

标签:

 

  给定一个整数序列,要求将这些整数的和尽可能拼成 100。

  比如 [17, 17, 4, 20, 1, 20, 17, 6, 18, 17, 12, 11, 10, 7, 19, 6, 16, 5, 6, 21, 22, 21, 10, 1, 10, 12, 5, 10, 6, 18] , 其中一个解是 [ [17, 17, 4, 20, 1, 17, 6, 18] [20, 18, 17, 12, 11, 10, 6, 6] [7, 19, 16, 5, 6, 21, 10, 1, 5, 10] ] , 没有用到的数字是 [12, 10, 21, 22] 

 

     累加性的序列问题,通常可以使用动态规划法。 这个也不例外。

  算法:

        STEP1:  从序列 origin 中取出一个数 NUM ;

        STEP2:  在剩下的数中寻找和为 100 - NUM 的数字组合 matched = m1,m2,..., mN ;

     A.  如果找到, 那么在 origin 中移除 NUM 和 matched ,  将 [matched, NUM] 加入匹配解集合 finalResults,  然后跳转到 STEP1 ;

            B.  如果没有找到, 将 NUM 加入未匹配序列 unMatched, 跳转到 STEP3 ;

        STEP3:   检测 origin 是否还有元素; 如果还有元素, 跳转到 STEP1 ; 否则跳转到 STEP4

        STEP4:   退出算法。

  

  注意到这里需要一个函数 matchSum,实现以下功能: 给定一个数字 sum 及一个序列 seq, 如果能够在 seq 中找到和为 sum 的数字组合 matched,则返回 (True, matched) ; 否则返回 (False, []) .  这个函数可以递归实现。递归的三要素是: (解结构,终止条件, 递归方式)。如果解结构是一个无序序列,那么可以直接将解结构传入递归函数,在递归函数中逐步添加解结构的组成元素; 如果解结构是有序序列,则要注意递归添加解元素确保其顺序。

     A0.  设置解结构 matched , 传入递归函数 matchSum(sum, seq, matched) , 在递归调用 matchSum 的过程中在 matched 中添加解元素;

   A1.  规定终止条件。通常就是最简单的情况,为空或只有一个元素。

     a1.  如果 sum > 0 , seq 为空,那么必然不存在匹配,返回 False;

            a2.  如果 sum > 0, seq 仅含有一个数字, 且 seq[0] != sum, 那么仍然不存在匹配,返回 False ;

            a3.  如果 sum > 0, seq 仅含有一个数字, 且 seq[0] == sum, 那么存在匹配,将 seq[0] 加入 matched, 返回 True; 

     A2.  设置递归方式。 seq 非空, 且含有两个及以上元素,该如何递归呢? 可以将 sum 与 seq[0] 比较,分情况讨论:

     a1.  如果 sum < seq[0] , 那么很显然 sum 只能在除去 seq[0] 之外的序列 seq[1:] 中寻找匹配了: matchSum(sum, seq, prematched) =  matchSum(sum, seq[1:], prematched) ;  

     a2.  如果 sum == seq[0] , 那么很显然找到了一个匹配, 可以直接返回: matchSum(sum, seq, prematched) =  seq[0] + prematched ;  注意到, seq[0] 并不一定是指初始序列的第一个值,而是指在递归过程中获取到的当前序列的第一个值,而 prematched 在之前的递归过程中可能已经填充部分数字了;

     a3.  如果 sum > seq[0],就分两种情况,要么匹配结果包含 seq[0], 为 seq[0] + matchSum(sum-seq[0], seq[1:], prematched) ; 要么匹配结果不包含 seq[0], 为 matchSum(sum, seq[1:], prematched) . 

 

     解结构是一个列表,每个列表元素是一个小的列表,其元素之和为 100 ; 比如 [[54,36,10], [32, 63, 5], [24, 20, 34, 6, 12, 4]] , 未匹配数字为 [16, 20, 32] ; 

     更优的解法: 在算法结束时,很可能存在未匹配数字组合。将已有解结构的数字替换成未匹配数字的组合,可以减少未匹配数字的数量。比如解结构里的一个元素是 [54, 36, 10] , 未匹配数字为 [16, 20, 32] , 那么可以将 36 与 16, 20 替换。

   更多的解法: 将解结构的子列表中的大数字与小数字组合交换,即可得到新的解法,比如将 54 与 (20,34) , 10 与 (6,4) , 32 与 (20,12) 交换。

   这些都会用到 matchSum(sum, seq, matched) , 可以说这个函数是核心而基本的组件。通过这个组件可以衍生出各种的方法和解法。这就像业务开发中的基础接口,通过基础接口的叠加可以衍生出多样灵活的业务。

     应对大数据量: 当序列中数字比较多,且远小于要拼成的和时,就会出现递归深度很深的问题。Python 中有递归深度的限制。一种方法是提高递归深度设置;一种是将递归改写成非递归;一种是分治法,将大数字拆解为较小数字之和,在序列中先寻找较小数字的解结构,然后再拼接出最终的解结构。问题是,较小数字的选取很难确定,因为序列中可能根本不存在和为某一个较小数字的数字组合。 

 

     完整程序 (Python 实现):

import random
import sys   
sys.setrecursionlimit(10000)

UPLIMIT = 100
NUMBERS = 30
NUMBER_RANGE = (1,UPLIMIT/4)


def randNums(n):
    return [randGen(NUMBER_RANGE[0], NUMBER_RANGE[1]) for i in range(n)]

def randGen(start, end):
    return random.randint(start, end)

def solve(numlist):
    (finalResults, unMatchedNums) = useBacktracking(numlist)
    improve(finalResults, unMatchedNums)
    return (finalResults, unMatchedNums)

def useBacktracking(numlist):    
    if not numlist or len(numlist) == 0:
       return ([],[]);
    finalResults = []
    unMatchNums = []
    while len(numlist) > 0: 
        num = numlist.pop()
        matched = []
        if matchSum(UPLIMIT-num, numlist, matched):
            for e in matched:
                if e in numlist:
                    numlist.remove(e)
            matched.append(num)        
            finalResults.append(matched)        
        else:
            unMatchNums.append(num)

    return (finalResults, unMatchNums)       

def matchSum(rest, restlist, prematched):
        
    if rest > 0 and len(restlist) == 0:
        return False

    if rest > 0 and len(restlist) == 1 and restlist[0] != rest:
        return False

    if rest > 0 and len(restlist) == 1 and restlist[0] == rest:
        prematched.append(restlist[0])
        return True

    getone = restlist[0]
    if rest > getone:
        prematched.append(getone)
        if matchSum(rest-getone, restlist[1:], prematched):
            return True
        else:
            prematched.remove(getone)
            return matchSum(rest, restlist[1:], prematched)
    elif rest == getone:
        prematched.append(getone)
        return True;
    else:
        return matchSum(rest, restlist[1:], prematched)

def improve(finalResults, unMatchedNums):
    for comb in finalResults:
        for num in comb:
            matched = []
            if matchSum(num, unMatchedNums, matched) and len(matched) > 1:
                print Improved:  , num,  , matched
                comb.remove(num)
                comb.extend(matched)
                unMatchedNums.append(num)
                for e in matched:
                    unMatchedNums.remove(e)
                if len(unMatchedNums) == 0:
                    return

def printResult(finalResults, unMatchedNums, numlist):
    
    f_res = open(/tmp/res.txt, w)
    f_res.write(origin:  + str(numlist) + \n)
    f_res.write(averag:  + str((float(sum(numlist))/len(numlist))) + \n)
    f_res.write(solution: )
    
    usedNums = 0
    finalNumList = []
    for comb in finalResults:
        f_res.write(str(comb) +  )
        assert sum(comb) == UPLIMIT
        usedNums += len(comb)
        finalNumList.extend(comb)
    finalNumList.extend(unMatchedNums)
    f_res.write(\nUnMatched Numbers:  + str(unMatchedNums) + \n)
    f_res.write(Used numbers: %s, UnMatched numbers: %d.\n % (usedNums, len(unMatchedNums)))

    f_res.write(origin: %d , final: %d\n % (len(numlist), len(finalNumList)))
    for e in finalNumList:
        numlist.remove(e)
    if len(numlist) > 0:
        f_res.write(Not Occurred numbers:  + str(numlist))
    
    f_res.close() 

def copylist(numlist):
    return [num for num in numlist]


def to100(numlist):
   
    newnumlist = copylist(numlist)
    (finalResults, unMatchedNums) = solve(newnumlist)

    newnumlist = copylist(numlist)
    printResult(finalResults, unMatchedNums, newnumlist)

if __name__ == __main__:
    to100(randNums(NUMBERS))

     

拼100

标签:

原文地址:http://www.cnblogs.com/lovesqcc/p/5656318.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!