找到非重叠序列的最大覆盖范围的算法。 (即,加权区间调度问题。)

时间:2014-06-03 22:32:27

标签: algorithm complexity-theory dynamic-programming intervals resource-scheduling

我的问题与algorithm to find longest non-overlapping sequences非常相似。

链接问题的唯一区别是,我不需要找到代表最长序列的非重叠元组集合,而是需要找到代表<的非重叠元组集合strong>最大覆盖率,我的意思是元组长度的总和是最大的(元组长度last - first + 1给定<的定义em> tuple 在下一句中。)

我代表我的元组与链接问题不同。而不是(starting index, length),我将我的元组表示为(first, last);例如,元组(3,7)表示数字[3, 4, 5, 6, 7]的集合。 (元组重叠另一个元组,即使端点匹配;即(2,6)(6,8) 重叠,因此不能同时出现在解决方案中。)

作为示例,请考虑以下{Q 1}组排序的元组:

first

此处的最大设置为[(0,100), (2,50), (30,150), (60,95), (110,190), (120,150), (191,200)],覆盖范围为[(0,100), (110,190), (191,200)]。 (请注意,此解决方案中的元组不重叠。)

解决此问题的最简单算法的例子是什么,该算法的复杂性是什么? (如果能在101 + 81 + 10 = 192中解决这个问题会很棒,但我现在还不知道它是否可以。)

ADDENDUM:回想起来,事实证明我在这里提出的问题等同于weighted interval scheduling problem。这是interval scheduling problem的一个特例。

@ j_random_hacker的答案,实际上是加权区间调度问题的已知解决方案,时间复杂度为O(N)

2 个答案:

答案 0 :(得分:4)

这是 O(nlog n)-time,O(n)-space 算法。首先,如果元组数组尚未按此顺序排序,则按元组的起始位置排序。我将假设从零开始的数组索引。

让我们调用元组ib(i)的起始位置和结束位置e(i),使其总长度为e(i) - b(i)+ 1.同时让&#39; s定义一个函数next(i),它返回第一个元组的元组列表中可以出现在元组i右边的位置。注意next(i)可以用O(log n)时间用二进制搜索计算:只需将所有元组开始位置b(i)保存在数组b []中,并在子数组b中搜索第一个j [具有b [j]&gt;的i + 1 ... n-1]的E(i)。

让我们将f(i)定义为从开始或元组i后开始的任何非重叠元组的最大覆盖范围。由于元组本身或者处于这个最优集合中,我们有:

f(i) = max(e(i) - b(i) + 1 + f(next(i)), f(i+1)) for 0 <= i < n

我们也有边界条件f(n) = 0

显然,最大可能的覆盖范围由f(0)给出。这很容易计算出来。在伪C ++中:

int b[] = /* Tuple beginning positions, in nondecreasing order */;
int e[] = /* Tuple end positions */;
int n = /* Number of tuples */;

// Find the array position of the leftmost tuple that begins to the right of
// where tuple i ends.
int next(int i) {
    return upper_bound(b + i + 1, b + n, e[i]);
}

int maxCov[n + 1];    // In practice you should dynamically allocate this

// After running this, maxCov[i] will contain the maximum coverage of any
// nonoverlapping subset of the set of n - i tuples whose beginning positions
// are given by b[i .. n-1] and whose ending points are given by e[i .. n-1].
// In particular, maxCov[0] will be the maximum coverage of the entire set.
void calc() {
    maxCov[n] = 0;
    for (int i = n - 1; i >= 0; --i) {
        maxCov[i] = max(e[i] - b[i] + 1 + maxCov[next(i)], maxCov[i + 1]);
    }
}

calc()中的循环运行n次,每次迭代都会对二进制搜索函数upper_bound()进行一次O(log n)调用。

我们可以通过计算f(0)的max()输入,查看哪一个实际产生最大值,记录它是否暗示元组0的存在与否,然后递归来重建这个大小的实际集合处理余数(对应于f(next(0))或f(1))。 (如果两个输入相等,则有多个最优解,我们可以跟随其中任何一个。)

答案 1 :(得分:2)

下面的算法通过递归检索每个元素是最左边成员的最大非重叠集合,然后返回覆盖范围最大的集合。请参阅代码中的注释。

在PHP中实现。您可以在此处http://viper-7.com/xowTRF

进行测试

我认为此算法的复杂性为O(2^N)O(N^2)缓存,如果您不同意,请随时发表评论。

$set = [[0,100], [2,50], [30,150], [60,95], [110,190], [120,150], [191,200]];
$GLOBALS['cache'] = array(); //cache for overlapping sub problems

function maximumSet($set) {

    if(count($set) === 1) {
        return $set;
    }

    $cache_key = [];

    foreach($set as $k) {
        $cache_key[] = implode($k,'.');
    }

    $cache_key = implode('_',$cache_key);

    if(isset($GLOBALS['cache'][$cache_key])) {
        return $GLOBALS['cache'][$cache_key];
    }

    $maximumResult = null;

    //for each element in the set,
    //get the largest non-overlapping set that the element is a member of
    //once all N sets have been computed, return the largest one
    foreach($set as $k => $element) {

        unset($set[$k]);

        //create a new set $copy, which contains the remaining elements that
        //do not overlap with $element            
        $copy = $set;

        foreach($set as $k2 => $element2) {
            if($element2[0] <= $element[1]) { 
                //element is considered non overlapping if its start 
                //is less than or equal to current element's end
                unset($copy[$k2]);
            }
            else break; //because the list is sorted we can break the 1st time
            //see a non-overlapping element
        }

        if(!empty($copy)) {
            //if there is at least one non-overlapping element
            //recursively get the maximum set
            $maximumSubset = maximumSet($copy);
            //prepend the current element to it
            array_unshift($maximumSubset,$element);
        }
        else {
            //otherwise the maximum non-overlapping set which contains this element
            //is the element itself                
            $maximumSubset = [$element];
        }

        //store the set in the results by coverage
        $coverage = getCoverage($maximumSubset);
        if(is_null($maximumResult) || $maximumResult['coverage'] < $coverage) {
            $maximumResult = [
                'coverage' => $coverage,
                'set' => $maximumSubset
            ];
        }
    }

    $GLOBALS['cache'][$cache_key] = $maximumResult['set'];
    return $maximumResult['set'];
}

function getCoverage($set) {
    $range = 0;
    foreach($set as $v) {
        $range += ($v[1] - $v[0]);
    }
    return $range;
}

$x = maximumSet($set);
print "<pre>";
print_r($x);
print "</pre>";