寻找最繁忙时期的算法?

时间:2011-04-24 04:28:29

标签: python algorithm dynamic-programming

我有一些这样的数据:

1: 2 - 10
2: 3 - 15
3: 4 - 9
4: 8 - 14
5: 7 - 13
6: 5 - 10
7: 11 - 15

我会尝试表达以使其更清晰:

        1     2     3     4     5     6     7     8     9     10     11     12     13     14     15
1             |--------------------------------------X---------|
2                   |--------------------------------X--------------------------------------------|
3                         |--------------------------X---|
4                                                  |-X-------------------------------------|
5                                           |--------X------------------------------|
6                               |--------------------X----------|
7                                                                     |---------------------------|

因此,在示例情况下,8-9是使用第二个方案的关键时期,因为所有点都是活动的。在python中解决这个问题的快速而好的方法是什么?我正在考虑使用动态编程,但是还有其他建议的方法吗?

我的方法直到现在:

我从实时角度思考的更多。所以,每当我得到一个新点时,我都会这样做:假设我已经得到2-10并得到3-15然后我选择了最大的开始和结束的最小值,所以这种情况是{{1}并将此间隔的计数增加到2.然后第三个点进入3-10,选择最大值4,最小值为9并将值4-9更新为3-10并更新计数现在当4-9进入时,我选择此间隔的开头大于8-14,并且此间隔的结束小于4-9。在这种情况下,它不是真的所以我将创建一个新的桶4-9,我将计数设置为1.这不是整个算法,但应该高度了解我在这里做什么。我将看看是否可以绘制伪代码。

6 个答案:

答案 0 :(得分:26)

        1     2     3     4     5     6     7     8     9     10     11     12     13     14     15
1             |--------------------------------------X---------|
2                   |--------------------------------X--------------------------------------------|
3                         |--------------------------X---|
4                                                  |-X-------------------------------------|
5                                           |--------X------------------------------|
6                               |--------------------X----------|
7                                                                     |---------------------------|

             +1    +1     +1   +1           +1     +1    -1    -2     +1           -1     -1     -2
              1     2     3     4           5       6    5      3     4             3      2      0
                                                     ^^^^

得到它?

所以你需要改变它:

1: 2 - 10
2: 3 - 15
3: 4 - 9
4: 8 - 14
5: 7 - 13
6: 5 - 10
7: 11 - 15

成:

[(2,+), (3,+), (4,+), (5,+), (7,+), (8,+), (9,-), (10,-), (10,-), (11,+), (13,-), (14,-), (15,-), (15,-)]

然后你只需要迭代,当你看到一个+并倒计时时向上计数 - 。最繁忙的时间间隔将是计数最大值。

所以在代码中:

intervals = [(2, 10), (3, 15), (4, 9), (8, 14), (7, 13), (5, 10), (11, 15)]
intqueue = sorted([(x[0], +1) for x in intervals] + [(x[1], -1) for x in intervals])
rsum = [(0,0)]
for x in intqueue: 
    rsum.append((x[0], rsum[-1][1] + x[1]))
busiest_start = max(rsum, key=lambda x: x[1])
# busiest_end = the next element in rsum after busiest_start 

# instead of using lambda, alternatively you can do:
#     def second_element(x):
#         return x[1]
#     busiest_start = max(rsum, key=second_element)
# or:
#     import operator
#     busiest_start = max(rsum, key=operator.itemgetter(1))

运行时复杂性为(n+n)*log(n+n)+n+nO(n*log(n))

如果您在程序开头没有完整的时间间隔列表,但可以保证传入的时间间隔永远不会安排在过去的时间点,也可以将此想法转换为online algorithm。不是排序,你将使用优先级队列,每当一个间隔到来时,你推入两个项目,起点和终点,每个项目分别为+1和-1。然后你会弹出并计算并跟踪高峰时段。

答案 1 :(得分:6)

我首先考虑点x的忙碌作为x左边的激活次数减去x左边的失效次数。我会根据它们发生的时间(在O(nlog(n))时间内对激活和停用进行排序)。然后,您可以遍历列表,跟踪活动数字(y),递增和递减该数字,同时激活和停用。最繁忙的时期将是y处于最大值的点。我无法想到一个比O(nlog(n))更好的解决方案。蛮力将是O(n ^ 2)。

答案 2 :(得分:4)

我认为你可以使用set()来实现这一点,如果你确信所有时期至少相交一点,它就会有用。

但是,只要句点不相交,这就不起作用。 您可以添加额外的逻辑来涵盖这一点,因此我将发布我的想法:

>>> periods = [(2, 10), (3, 15), (4, 9), (8, 14), (7, 13), (5, 10),]
>>> intersected = None
>>> for first, second in periods:
...     if not intersected:
...         intersected = set(range(first, second + 1))
...     else:
...         intersected = intersected.intersection(set(range(first, second + 1)))
...
>>> intersected
set([8, 9])

注意:这不包括11-15期间。 你可能最好只创建R.K。

提到的bin对

答案 3 :(得分:4)

这就是我对基于bin的方法的想法,并且适合于动态处理添加,基本上是R.K.说我相信。

from collections import defaultdict
from operator import itemgetter

class BusyHour(object):
    def __init__(self):
        self.pairs = defaultdict(int)
    def add_period(self, period):
        start, end = period
        for current_period in range(start, end):
            pair_key = (current_period, current_period + 1) 
            self.pairs[pair_key] += 1
    def get_max(self):
        # sort, defaults to smallest to largest
        # --> items() returns (key, value) pairs
        # --> itemgetter gets the given index of the first argument given to sorted
        return max(self.pairs.items(), key=itemgetter(1))


if __name__ == '__main__':
    periods = [(2, 10), (3, 15), (4, 9), (8, 14), (7, 13), (5, 10), (11, 15)]
    bh = BusyHour()
    for period in periods:
        bh.add_period(period)
    print bh.get_max()

已更新:仅在调用get_max时进行排序,并使用defaultdict(int)。

答案 4 :(得分:3)

不确定我是否理解你的问题。如果您要查找最常见的“间隔”,则可以按间隔对它们求和。这样,上面的例子就有12个桶。对于每次使用,您将为该特定用途中使用的每个桶添加1,最后,在所有桶中找到最大值。在这里,对于8-9区间,这将是6。

答案 5 :(得分:0)

如果您想在这里具有线性性能,我整理了一个小型C ++程序。 我知道它不是Python,但是这里的想法很简单。

我们首先创建一个包含所有点的数组,如果间隔从该索引处开始,则递增该数组中的项,如果该间隔在该索引处结束,则递减该数组。

构造数组后,我们只需遍历并计算打开间隔最大的位置。

时间复杂度为O(M + N)

空间复杂度为O(N)

其中M是间隔数,N是间隔对中的最大值。

#include <iostream>
#include <vector>

int maxLoad(const std::vector<std::pair<int, int>>& intervals) {
    std::vector<int> points;
    for(const auto& interval : intervals) {
        if(interval.second >= points.size()) points.resize(interval.second + 1);
        ++points[interval.first];
        --points[interval.second];
    }

    int ans = 0;
    int sum = 0;
    for(const auto point : points) {
        sum += point;
        ans = std::max(ans, sum);
    }
    return ans;
}

int main() {
    std::vector<std::pair<int, int>> intervals {
        {2, 10}, {3, 15}, {4, 9}, {8, 14}, {7, 13}, {5, 10}, {11, 15}
    };
    std::cout << maxLoad(intervals) << std::endl;
}