1

I have the following homework:

We have N works, which durations are: t1, t2, ..., tN, which's deadlines are d1, d2, ..., dN. If the works aren't done till the deadline, a penalty is given accordingly b1, b2, ..., bN. In what order should the jobs be done, that the penalty would be minimum?

I've written this code so far and it's working but I want to improve it by skipping unnecessary permutations. For example, I know that the jobs in order: 1 2 3 4 5 - will give me 100 points of penalty and if I change the order let's say like this: 2 1 ..... - it gives me instantly 120 penalty and from this moment I know I don't have to check all of the rest permutations which start with 2 1, I have to skip them somehow. Here's the code:

int finalPenalty = -1;
bool z = true;
while(next_permutation(jobs.begin(), jobs.end(), compare) || z)
{
    int time = 0;
    int penalty = 0;
    z = false;
    for (int i = 0; i < verseNumber; i++)
    {
        if (penalty > finalPenalty && finalPenalty >= 0)
            break;
        time += jobs[i].duration;
        if (time > jobs[i].deadline)
            penalty += jobs[i].penalty;
    }
    if (finalPenalty < 0 || penalty < finalPenalty)
    {
        sortedJobs = jobs;
        finalPenalty = penalty;
    }
    if (finalPenalty == 0)
        break;
}

I think I should do this somewhere here:

if (penalty > finalPenalty && finalPenalty >= 0)
            break;

But I'm not sure how to do this. It skips me one permutation here if the penalty is already higher, but it doesn't skip everything and it still does next_permutation. Any ideas?

EDIT: I'm using vector and my job structure looks like this:


    struct job
    {
        int ID;
        int duration;
        int deadline;
        int penalty;
    };

ID is given automatically when reading from file and the rest is read from file (for example: ID = 1, duration = 5, deadline = 10, penalty = 10)

Tripper
  • 27
  • 1
  • 7
  • The first thing that strikes me is how vaguely the penalty measure is defined **in the example you provide**. The order of the first two elements may cause an extra penalty for 2 1 3 4 5, but I guess its penalty would be different from that of 2 1 3 5 4 or 2 1 4 3 5, unless b3, b4 and b5 are given specific values that make the swaps between the last 3 elements redundant. In other words, how can you *skip* certain permutations without explicit guarantee(e.g. some limitation on b# values) that their penalty can be pre-computed? – ilim Apr 13 '16 at 20:03
  • Yes, 2 1 3 5 4 might be different from 2 1 4 3 5, but if I see at the beginning that 2 1 ..... has already higher penalty than 1 2 3 4 5, then there's no reason I should check for every other permutation which starts 2 1 ...... Another example: 1 2 3 4 5 - 100 2 1 ........ - 80 BUT 2 1 3 ..... - 120, so there's no need to check the last elements, because it's alreaady higher than previous permutation. From this moment I should skip to: 2 1 4 .... – Tripper Apr 14 '16 at 07:33
  • Just to clarify, all your penalties are positive, right? – ilim Apr 14 '16 at 07:45

1 Answers1

1

If you are planning to use next_permutation function provided by STL, there is not much you can do.

Say the last k digits are redundant to check. If you will use next_permutation function, a simple, yet inefficient strategy you can use is calling next_permutation for k! times(i.e. number of permutations of those last k elements) and just not go through with computing their penalties, as you know they will be higher. (k! assumes there are not repetitions. if you have repetitions, you would need to take extra measures to be able to compute that) This would cost you O(k!n) operations on the worst case, as next_permutation has linear time complexity.

Let's consider how we can improve this. A sound strategy may be, once an inefficient setting is found, before calling next_permutation again, ordering those k digits in descending order so that the next call would effectively skip the inefficient portion of permutations that need not be checked. Consider the following example.

Say our method found 1 2 3 4 5 has a penalty of 100. Then, while computing 2 1 3 4 5 at the next step, if our method finds that we got a penalty higher than 100 only after computing 2 1, if could just sort 3 4 5 in descending order using sort along with your custom comparison mechanism, and just skip the rest of the loop, arriving at another next_permutation call, which would give you 2 1 4 3 5, the next sequence to continue.

Let's consider how much skipping costs. This method requires sorting those k digits and calling next_permutation, which has an overall time complexity of O(klogk + n). This is a huge improvement over the previous method which has O(k!n).

See below for an crude implementation of the method I propose as an improvement over your existing code. I had to use type auto as you did not provide the exact type for jobs. I also sorted then reversed those k digits, as you did not provide your comparison function and I wanted to emphasize that what I was doing was reversing the ascending order.

int finalPenalty = -1;
bool z = true;
while(next_permutation(jobs.begin(), jobs.end(), compare) || z)
{
    int time = 0;
    int penalty = 0;
    z = false;
    auto it = jobs.begin();
    for (int i = 0; i < verseNumber; i++)
    {
        time += jobs[i].duration;
        if (time > jobs[i].deadline)
        {
            penalty += jobs[i].penalty;
            if(finalPenalty >= 0 && penalty > finalPenalty)
            {
                it++; // only the remaining jobs need to be sorted in reverse
                sort(it, jobs.end(), compare);
                reverse(it, jobs.end());
                break;
            }
        }
        it++;
    }
    if (finalPenalty < 0 || penalty < finalPenalty)
    {
        sortedJobs = jobs;
        finalPenalty = penalty;
    }
    if (finalPenalty == 0)
        break;
}
Community
  • 1
  • 1
ilim
  • 4,477
  • 7
  • 27
  • 46
  • Thanks, gonna try it out later. My jobs are vector type of a job structure (int ID, time, deadline and penalty) and my compare() function is like this: bool compare(job left, job right) { return left.ID > right.ID } – Tripper Apr 14 '16 at 08:38
  • The program compiles faster now, but it doesn't give me the correct answer. It just sorts it vice versa (from 1 2 3 4 5 to 5 4 3 2 1). I've written `vector::iterator it = jobs.begin();` instead of your `auto it` – Tripper Apr 14 '16 at 09:44
  • Can you provide us with the sample contents of a vector instance? You keep providing the IDs in your examples but not the exact duration, penalty and deadline values – ilim Apr 14 '16 at 09:45
  • Post edited. All those are `int`'s, if time > deadline, then a penalty is given – Tripper Apr 14 '16 at 09:53
  • What I was asking for was a complete example for jobs with IDs 1,2,3,4,5. (i.e. their duration, deadline and penalties) This method should theoretically work, but how many of the permutations it skips depends greatly on the specific values you use for deadlines, durations and penalties. In my sample case [here](http://ideone.com/kUFEmP), for example, 53 of the total 120 permutations are skipped. – ilim Apr 14 '16 at 09:59
  • For instance in the link I provided in the comment above, at the 59th permutation investigated (1 5 4 3 2) we skip all the ones starting with 1 5. – ilim Apr 14 '16 at 10:04
  • Oh, sorry. I've got one with 10 jobs, like I said, their ID is given automatically, first numbers are durations, second - deadline and third - penalty. [Here](https://jpst.it/H14V) EDIT: God, I forgot to do `it++` .... Works very good, thanks! – Tripper Apr 14 '16 at 10:09