5

I am having a problem with my backtracking function it loops with certain data I can't write here the whole program code but can put here my function.

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;
}

Now here is the explanation:

quantity - number of elements in values array
values - array of numbers
index - variable to control the recursion
moneyA - variable that stores the sum of element from array values
half - number that moneyA should reach after recursion is done
ifChosen - array of boolean elements that refers to array values

The function gets quantity of elements which is lenght of values array, values an array with numbers in it's sorted from the highest to the lowest one, index controls recursion and default it's 0 so it starts from the first element, moneyA variable that stores numbers from the values array and it should reach half which is the half of numbers sumed from values array and ifChosen stores numbers that are chosen.

The whole function does this, it sums the elements from the values array and checks wether it reached the half if it's lower than half adds it to moneyA and mark it in ifChosen then it goes to next one, if the sum is higher than half it gets back and unmark it in ifChosen array and substract from moneyA. It should always get the highest elements.

Here is the simple example:

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

The result for this one should be:

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

Of course for this simple example it does great job but for more complicated that have more numbers and one number occurs more than once it loops and recursion never stops. I've been actually working on this for 1.5 weeks and asked all my friends but nobody knows what is wrong with it. I know it has a bit to do with knapsack problem but I didn't have that one yet and still have to study.

I'm looking forward to any answer that could help.

I'm sorry for my punctuation but I'm here for the first time and didn't got used to formatting.

Here you got one example:

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

Now the one I think it loops forever: 43 elements:

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 Yes I know that there are so many combinations that's why I am trying to speed it up. For now this is all I got but it gives me wrong results for certain input. Can anyone make that correct, it works much faster than before ?

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;}
Paul
  • 163
  • 7
  • 1
    There doesn't appear to be an infinite loop in the code (assuming `quantity` is greater than the starting value of `index`); it is probably just very slow. That claim assumes that your actual code always increments `index` on recursive calls. Problems like this are often solved using dynamic programming; your code is likely trying to solve the same inputs many times. – Jeremiah Willcock Feb 23 '11 at 20:08
  • Would you please tell me how to change that so it goes out the loop? – Paul Feb 23 '11 at 20:09
  • 1) Use punctuation. 2) Post the simplest example you can construct that shows the error. – Beta Feb 23 '11 at 20:10
  • The loop is just the recursion, and it will always exit eventually. Note that it takes `pow(2, quantity)` function calls to do it, though. – Jeremiah Willcock Feb 23 '11 at 20:12
  • Is there any way to make it faster? I noticed one thing that if I sort it from the lowest to the highest it does eveyrthing so fast, but I have no idea how to make it take the greatest numbers when its sorted that way. – Paul Feb 23 '11 at 20:19

2 Answers2

2

The typical way to cut down on the number of iterations on a problem like this is to calculate a bound on a subtree by solving a linear program (like your problem, but the remaining variables are allowed to take on fractional values). Simplex solves the linear program in approximately quadratic time instead of exponential. The best solution for the linear program is at least as good as the best integer or binary solution with the same constraints, so if the linear solution is worse that your current best, you can throw away the whole subtree without exhaustive evaluation.

EDIT: Let's start by simplifying the brute force algorithm:

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);
}

After execution, solution contains a list of the values used to reach the goal, and the returned pointer indicates the end of the list.

The conditional blocks are my first suggested improvement. No recursion is necessary in these cases.

We can eliminate the need to iterate to calculate max at each step:

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);
}

Here's a function to solve the integer version (better for repeated coin denominations):

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;
}

Instead of getting a straight list of individual coins, it takes a list of denominations, and the number of available coins of each one. The result is the number of coins of each denomination needed by the solution.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • @Ben Voigt I am not that good in algorithms yet and still learning I don't know about trees etc. yet. Would you if you know make my recursion correct? Maybe I am missing a condition and it doesn't work good. – Paul Feb 23 '11 at 22:04
  • @Paul: This case is a boolean program and there are two tests you can use to check whether the first `index` values you've chosen are infeasible: if the sum of the included values so far is greater than half, the remaining values can't give less than zero (since money is non-negative) so you don't need to check the combinations. Similarly if the sum of the included values plus all values past the current point is less than half, there's no solution and you don't need to check the combinations. – Ben Voigt Feb 23 '11 at 22:34
  • @Paul: A quick example. Say you have a bunch of numbers adding to 100, including 40 and 20 (the remaining numbers sum to 40). Clearly there is no solution that includes both 40 and 20 (40 + 20 + ... > 50 always), and there is no solution that doesn't include either of 40 and 20. So you had four sets of candidates: (w/40 w/20) (w/40 wo/20) (wo/40 w/20) (wo/40 wo/20) and half of them go away without checking the thousand ways the other items can be combined. – Ben Voigt Feb 23 '11 at 22:39
  • @Paul: Also, when you have repeated coins, you can turn this binary problem into a much smaller integer problem. Instead of your solution format being (include coin N: true or false), your solution format is (how many of denomination M: 1 or 2 or 3). – Ben Voigt Feb 23 '11 at 22:41
  • @all_above Would you please write your comments in pseudo code. I am not that good in english to understand how to change some things, I have enough with this problem. Thank you – Paul Feb 23 '11 at 23:33
  • @Paul: Can you look and see whether this makes sense to you? – Ben Voigt Feb 23 '11 at 23:59
  • @Ben Voigt It makes sense to me, I forgot to mention that this function must use backtracking and should be simple. It's my homework actually and teacher said it must be like that. BTW Thank you very much for the answers – Paul Feb 24 '11 at 00:06
  • @Ben Voigt Could you please try to modify my recursion so it still uses backtracking but works faster, I think I've ran out of ideas and ways to speed it up, because when I add some other conditions it does good job for long and complicated input but for short intup it messes up eveyrthing and gives the wrong results. – Paul Feb 24 '11 at 00:28
  • @Paul: You ought to be able to do something very similar to my `PRUNE_ABOVE` and `PRUNE_BELOW` logic in your own function. Since this is apparently a homework assignment you need to do that yourself, I've given you a huge push in the right direction. – Ben Voigt Feb 24 '11 at 00:34
0

For 43 elements, there are close to 9 trillion possible combinations. There's no way to speed that up if you have to check all 9 trillion, but in case you don't want to wait that long, the trick is to try to put the answer closer to the start of the loop. I think you've probably hit on the right solution by sorting it in increasing order. This is probably faster because it gets the big pieces arranged first (because you are doing depth-first recursion).

If I understand the problem correctly, that will find combination of the smallest elements that add up to exactly half the total value. That means the elements that aren't selected also should add up to exactly half the total value, and will be the largest elements.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • Putting the biggest elements at the beginning is not advantageous for his code. Consider the set { `40`, `19`, 41 x `1` }, which is also 43 elements. The first thing the algorithm does is try picking `40` and `19`, now `moneyA` is 59, but it has to test all 2**41 combinations of the pennies in order to determine this is impossible. Not good, not good at all. A pruning technique needs to be used. – Ben Voigt Feb 24 '11 at 00:05
  • However, when pruning IS used, I think a descending sort would be helpful, because big steps get you out of the relaxed feasibility region faster. – Ben Voigt Feb 24 '11 at 00:06
  • @Karl Bielefeldt I don't think I understand that, I'm just starting with algorithms and this is difficult to me. I know that my function the first I put in here is working good but for longer or other certain input it works very very slow. I should use backtracking in this case, because thats the teacher's objective he gave us. I know there should be easy way to add a few conditions to it and would work much faster. I did some and for some that long input it works great and fast but for small it gives the wrong results. – Paul Feb 24 '11 at 00:17
  • @Ben, I wholeheartedly agree on pruning in general, but in your example you don't have to go through all the penny combinations. It only has to backtrack 10 pennies, which is 4096 iterations including the 40 and 19. Putting the big pieces in first probably wasn't the best way to describe it. What I meant is the decisions about the big pieces are made first on each descent, as they are the first to be backtracked. – Karl Bielefeldt Feb 24 '11 at 01:20
  • @Karl: why 10... his algorithm is going to try with all 43 coins, then without the last penny, then without the next-to-last (but with the last) penny, then without the last two pennies... it will test 2**41 solutions before trying the first solution without 19 included. – Ben Voigt Feb 24 '11 at 02:19
  • @Ben, the index is lowest the higher up in the call stack you go, so if they are sorted in increasing order, the lowest in the call stack, and therefore the first to backtrack, is the 40, which won't even get tried at all with pruning. Next highest in the call stack is the 19, so it will keep trying with and without the 19, removing pennies one by one. – Karl Bielefeldt Feb 24 '11 at 02:35
  • @Karl: Sorry, I saw the phrase "big pieces arranged first" and thought "descending sort", but you did specifically say sorted in increasing order. My mistake. Anyway, I think the sorting in the opposite order will be better with pruning. – Ben Voigt Feb 24 '11 at 02:39
  • @BenVoigt my simple solution for this was to sort it descending, then repeatedly add taking the items first from the bigger end and then (when adding a bigger item is no longer possible) from the smaller end. It won't produce the *optimal* solution, but hopefully, near enough to the optimal. – Will Ness Jun 20 '17 at 08:59