递归回溯

时间:2011-02-23 20:04:35

标签: c++ recursion backtracking knapsack-problem

我的回溯功能有问题,它循环使用某些数据我不能在这里写完整个程序代码,但可以放在这里我的功能。

bool shareMoney(int quantity, int *values, int index, int moneyA, int half, bool *ifChosen)
{

    if(moneyA == half)
        return true;
    else if(index >= quantity)
        return false;

    moneyA += values[index];

    if(shareMoney(quantity, values,index+1, moneyA, half, ifChosen))
    {
        ifChosen[index] = true;
        return true;
    };

    moneyA -= values[index];

    if(shareMoney(quantity, values,index+1, moneyA, half, ifChosen))
    {
        ifChosen[index] = false;
        return true;
    };

    return false;
}

现在这里是解释:

数量 - 值数组中的元素数量
值 - 数字数组
index - 用于控制递归的变量
moneyA - 存储数组值元素之和的变量
在递归完成后,moneyA应该达到的一半数量 ifChosen - 引用数组值的布尔元素数组

该函数获取值数组长度的元素数量,值为一个数组,其中数字从最高到最低排序,索引控制递归,默认为0,所以它从第一个元素moneyA变量开始存储来自values数组的数字,它应该达到一半,这是从数值数组中取出的数字的一半,ifChosen存储了所选择的数字。

整个函数执行此操作,它将值数组中的元素相加并检查它是否达到了一半,如果它低于一半将其添加到moneyA并将其标记为ifChosen然后它转到下一个,如果总和更高超过一半它回来并在ifChosen数组中取消标记并从moneyA中减去。应始终获得最高要素。

以下是一个简单的例子:

6 - number of elements
50, 20, 19, 15, 2, 2 - elements stored in values array
total sum is - 108
half of elements - 54

这个结果应该是:

50, 2, 2 - marked as true in ifChosen
20, 19, 15 - marked as false in ifChosen 

当然,对于这个简单的例子,它做得很好但是对于更复杂的有更多数字和一个数字出现不止一次它循环和递归永远不会停止。我已经实际工作了1.5周,并问了我所有的朋友,但没有人知道它有什么问题。我知道它与背包问题有点关系,但我还没有那个,但仍然需要学习。

我期待任何有用的答案。

我很抱歉我的标点符号,但我第一次来这里并且不习惯格式化。

这里有一个例子:

89 86 83 67 53 45 5 1    

44 43 34 33 33 24 23 23 23 22 21 21 19 12 11 9 8 7 5 3 2 2 2 1 1 1 1 1     

real    0m28.007s    

user    0m27.926s    

sys 0m0.028s

现在,我觉得它永远循环: 43个要素:

12 2 2 1 3 4 5 6 7 89 33 12 45 23 44 23 11 44 1 3 5 7 88 33 8 19 43 22 86 5 34 23 21 67 83 24 21 53 9 11 34 1 1

@ Karl Bielefeldt是的我知道有这么多组合,这就是为什么我要加快速度。现在这就是我所得到的,但它给了我某些输入错误的结果。任何人都可以做到这一点,它比以前更快吗?

bool shareMoney(int quantity, int *values, int index, int moneyA, int half, bool *ifChosen){

if(index>=quantity && moneyA == half){ return true;}
else if(index>=quantity) return false;

moneyA+=values[index];
ifChosen[index]=true;

if(moneyA<=half){       
    shareMoney(quantity,values,index+1,moneyA, half,ifChosen);
    if(moneyA==half) return true;

    return true;
}else{
    shareMoney(quantity,values,index+1,moneyA, half,ifChosen);      
    moneyA-=values[index];
    ifChosen[index]=false;

    return true;
}


return false;}

2 个答案:

答案 0 :(得分:2)

减少像这样的问题的迭代次数的典型方法是通过求解线性程序来计算子树上的边界(就像你的问题一样,但其余的变量可以采用小数值)。 Simplex以近似二次时间而非指数方式求解线性程序。线性程序的最佳解决方案至少与具有相同约束的最佳整数或二进制解决方案一样好,因此如果线性解决方案比当前最佳解决方案更差,则可以丢弃整个子树而无需进行详尽的评估。

编辑:让我们从简化强力算法开始:

int* shareMoney( int pool_size, int *pool, int *solution, int cumsum, int goal)
{
    if (cumsum == goal) return solution;
#if PRUNE_ABOVE
    if (cumsum > goal) return 0;
#endif
    if (!pool_size) return 0;

#if PRUNE_BELOW
    int max = cumsum;
    for( int n = pool_size; n--; max += pool[n] );
    if (max < goal) return 0;
#endif

    int* subproblem = shareMoney(pool_size-1, pool+1, solution, cumsum, goal);
    if (subproblem) return subproblem;

    *solution = *pool;
    return shareMoney(pool_size-1, pool+1, solution+1, cumsum+*pool, goal);
}

执行后,solution包含用于达到目标的值列表,返回的指针表示列表的结尾。

条件块是我的第一个建议的改进。在这些情况下,不需要递归。

我们可以消除迭代计算每步的最大值的需要:

int* shareMoney( int pool_size, int *pool, int *solution, int cumsum, int poolsum, int goal)
{
    if (cumsum == goal) return solution;
#if PRUNE_ABOVE
    if (cumsum > goal) return 0;
#endif
    if (!pool_size) return 0;

#if PRUNE_BELOW
    if (cumsum + poolsum < goal) return 0;
#endif

    int* subproblem = shareMoney(pool_size-1, pool+1, solution, cumsum, poolsum - *pool, goal);
    if (subproblem) return subproblem;

    *solution = *pool;
    return shareMoney(pool_size-1, pool+1, solution+1, cumsum+*pool, poolsum - *pool, goal);
}

这是一个解决整数版本的函数(对于重复的硬币面额更好):

int* shareMoney( int pool_size, int *pool_denom, int *pool_cardinality, int *solution, int cumsum, int poolsum, int goal)
{
    if (cumsum == goal) return solution;
#if PRUNE_ABOVE
    if (cumsum > goal) return 0;
#endif
    if (!pool_size) return 0;

#if PRUNE_BELOW
    if (cumsum + poolsum < goal) return 0;
#endif

    poolsum -= *pool_cardinality * *pool_denom;
    for (*solution = *pool_cardinality; *solution >= 0; --*solution) {
        int* subproblem = shareMoney(pool_size-1, pool_denom+1, pool_cardinality+1, solution+1, cumsum + *solution * *pool_denom, poolsum, goal);
        if (subproblem) return subproblem;
    }

    return 0;
}

它不是获得单个硬币的直接列表,而是取一个面额列表和每个硬币的可用硬币数量。结果是解决方案所需的每种面额的硬币数量。

答案 1 :(得分:0)

对于43个元素,有近9万亿种可能的组合。如果你必须检查所有9万亿,那么就没办法加快速度,但是如果你不想等那么久,诀窍就是试着让答案更接近循环的开始。我认为你可能通过按升序排序来找到正确的解决方案。这可能更快,因为它首先排列了大块(因为你正在进行深度优先递归)。

如果我正确理解了问题,那么会找到最小元素的组合,这些元素总计恰好是总值的一半。这意味着选择的元素应该加起来总值的一半,并且将是最大的元素。