如何确定给定范围内的最佳间隔计数?

时间:2012-08-26 14:01:22

标签: c# algorithm intervals

我正在努力确定这个棘手问题的最佳解决方案。我有一个长度(让我们说11)。所以这是一个0-10的一维空间。现在我已经得到了相同长度的这些区间(在本例中我们假设为2)。现在它们随机分布(重叠或不重叠)。让我举一个例子:

Situation:

|00|01|02|03|04|05|06|07|08|09|10| <- Space (length = 11)

|-----|       
         |-----|
            |-----|
               |-----|
                  |-----|
                           |-----| <- single interval of length = 2

现在解决方案需要找到一次可以适合的最大间隔数而不重叠。

The solution is: 4 intervals

有4个区间的三个结果:

|00|01|02|03|04|05|06|07|08|09|10|

|-----|  |-----|-----|     |-----| <- result 1
|-----|  |-----|  |-----|  |-----| <- result 2
|-----|     |-----|-----|  |-----| <- result 3

但也有两个限制因素。

  1. 如果有更多结果(最佳解决方案,在这种情况下= 4),那么间隙数最少的那个。

  2. 如果还有更多结果,那么所有空格的最小长度都会最长。例如,具有空间(长度)2和2的空间。 3具有最小的空间长度= 2,优于1&amp; 4其中最小空间长度仅为1.

  3. 所以结果2有4个“连续”块,另外两个只有3个,所以改进是:

    |00|01|02|03|04|05|06|07|08|09|10|
    
    |-----|  |-----------|     |-----| <- result 1
    |-----|     |-----------|  |-----| <- result 3
    

    这两个人之间有相同的空间分布,所以我们先拿一个。

    输入集的结果是:

    Interval count  : 4
    Optimal solution: |-----|  |-----------|     |-----|
    

    算法必须普遍适用于所有空间长度(不仅是11),所有间隔长度(间隔长度始终为&lt; =空间长度)和任意数量的间隔。

    更新

    有问题的情景:

    |00|01|02|03|04|05|06|07|08|09|
    
    |-----|  
             |-----| 
                |-----|     
                            |-----|
    |-----|
    

1 个答案:

答案 0 :(得分:4)

这是一个简单的动态编程问题。

让总长度为N,任务长度为L

F(T)为可以从子区间(T, N)中选择的最大任务数,然后在每个单位时间T,有3种可能性:

  1. 没有任务从T开始。
  2. 有一项任务从T开始,但我们不会将其包含在结果集中。
  3. 有一项任务从T开始,我们确实将其包含在结果集中。
  4. 案例1很简单,我们只有F(T) = F(T + 1)

    如果是2/3,请注意选择启动T的任务意味着我们必须拒绝在此任务运行时启动的所有任务,即TT + L之间的任务。我们得到F(T) = max(F(T + 1), F(T + L) + 1)

    最后,F(N) = 0。因此,您只需从F(N)开始,然后回到F(0)

    编辑:这将为您提供最大间隔数,但不会为您提供满足2个约束的设置。你对这些限制的解释我不清楚,所以我不知道如何帮助你。特别是,我无法分辨出1的约束意味着什么,因为您的示例集的所有解决方案显然都是相同的。

    编辑2:根据要求提供了一些进一步的解释:

    考虑您发布的示例,我们有N = 11L = 2。有些任务从T = 0, 3, 4, 5, 6, 9开始。从F(11) = 0开始并向后工作:

    • F(11) = 0
    • F(10) = F(11) = 0(因为没有任务从T = 10开始)
    • F(9) = max(F(10), F(11) + 1) = 1
    • ...

    最终我们到达F(0) = 4

    T   |00|01|02|03|04|05|06|07|08|09|10|
    F(T)| 4| 3| 3| 3| 3| 2| 2| 1| 1| 1| 0|
    

    编辑3:我很好奇,我写了一个解决方案,所以也可以发布。这将为您提供具有最多任务,最小间隙数和最小最小间隙的集合。问题中示例的输出是:

    • (0, 2) -> (4, 6) -> (6, 8) -> (9, 11)
    • (0, 2) -> (4, 6) -> (8, 10)

    显然,我不保证正确性! :)

    私人课程任务     {         public int Start {get;组; }         public int Length {get;组; }         public int End {get {return Start + Length; }}

        public override string ToString()
        {
            return string.Format("({0:d}, {1:d})", Start, End);
        }
    }
    
    private class CacheEntry : IComparable
    {
        public int Tasks { get; set; }
        public int Gaps { get; set; }
        public int MinGap { get; set; }
        public Task Task { get; set; }
        public Task NextTask { get; set; }
    
        public int CompareTo(object obj)
        {
            var other = obj as CacheEntry;
            if (Tasks != other.Tasks)
                return Tasks - other.Tasks; // More tasks is better
            if (Gaps != other.Gaps)
                return other.Gaps = Gaps; // Less gaps is better
            return MinGap - other.MinGap; // Larger minimum gap is better
        }
    }
    
    private static IList<Task> F(IList<Task> tasks)
    {
        var end = tasks.Max(x => x.End);
        var tasksByTime = tasks.ToLookup(x => x.Start);
        var cache = new List<CacheEntry>[end + 1];
    
        cache[end] = new List<CacheEntry> { new CacheEntry { Tasks = 0, Gaps = 0, MinGap = end + 1 } };
    
        for (int t = end - 1; t >= 0; t--)
        {
            if (!tasksByTime.Contains(t))
            {
                cache[t] = cache[t + 1];
                continue;
            }
    
            foreach (var task in tasksByTime[t])
            {
                var oldCEs = cache[t + task.Length];
                var firstOldCE = oldCEs.First();
                var lastOldCE = oldCEs.Last();
    
                var newCE = new CacheEntry
                {
                    Tasks = firstOldCE.Tasks + 1,
                    Task = task,
                    Gaps = firstOldCE.Gaps,
                    MinGap = firstOldCE.MinGap
                };
    
                // If there is a task that starts at time T + L, then that will always 
                // be the best option for us, as it will have one less Gap than the others
                if (firstOldCE.Task == null || firstOldCE.Task.Start == task.End)
                {
                    newCE.NextTask = firstOldCE.Task;
                }
                // Otherwise we want the one that maximises MinGap.
                else
                {
                    var ce = oldCEs.OrderBy(x => Math.Min(x.Task.Start - newCE.Task.End, x.MinGap)).Last();
                    newCE.NextTask = ce.Task;
                    newCE.Gaps++;
                    newCE.MinGap = Math.Min(ce.MinGap, ce.Task.Start - task.End);
                }
    
                var toComp = cache[t] ?? cache[t + 1];
                if (newCE.CompareTo(toComp.First()) < 0)
                {
                    cache[t] = toComp;
                }
                else
                {
                    var ceList = new List<CacheEntry> { newCE };
    
                    // We need to keep track of all subsolutions X that start on the interval [T, T+L] that
                    // have an equal number of tasks and gaps, but a possibly a smaller MinGap. This is
                    // because an earlier task may have an even smaller gap to this task.
                    int idx = newCE.Task.Start + 1;
                    while (idx < newCE.Task.End)
                    {
                        toComp = cache[idx];
                        if
                        (
                            newCE.Tasks == toComp.First().Tasks &&
                            newCE.Gaps == toComp.First().Gaps &&
                            newCE.MinGap >= toComp.First().MinGap
                        )
                        {
                            ceList.AddRange(toComp);
                            idx += toComp.First().Task.End;
                        }
                        else
                            idx++;
                    }
    
                    cache[t] = ceList;
                }
            }
        }
    
        var rv = new List<Task>();
        var curr = cache[0].First();
        while (true)
        {
            rv.Add(curr.Task);
            if (curr.NextTask == null) break;
            curr = cache[curr.NextTask.Start].First();
        }
    
        return rv;
    }
    
    public static void Main()
    {
        IList<Task> tasks, sol;
    
        tasks = new List<Task>
        {
            new Task { Start = 0, Length = 2 },
            new Task { Start = 3, Length = 2 },
            new Task { Start = 4, Length = 2 },
            new Task { Start = 5, Length = 2 },
            new Task { Start = 6, Length = 2 },
            new Task { Start = 9, Length = 2 },
        };
    
        sol = F(tasks);
        foreach (var task in sol)
            Console.Out.WriteLine(task);
        Console.Out.WriteLine();
    
        tasks = new List<Task>
        {
            new Task { Start = 0, Length = 2 },
            new Task { Start = 3, Length = 2 },
            new Task { Start = 4, Length = 2 },
            new Task { Start = 8, Length = 2 },
        };
    
        sol = F(tasks);
        foreach (var task in sol)
            Console.Out.WriteLine(task);
        Console.Out.WriteLine();
    
        tasks = new List<Task>
        {
            new Task { Start = 0, Length = 5 },
            new Task { Start = 6, Length = 5 },
            new Task { Start = 7, Length = 3 },
            new Task { Start = 8, Length = 9 },
            new Task { Start = 19, Length = 1 },
        };
    
        sol = F(tasks);
        foreach (var task in sol)
            Console.Out.WriteLine(task);
        Console.Out.WriteLine();
    
        Console.In.ReadLine();
    }