优先重叠日期范围

时间:2013-10-02 18:53:33

标签: algorithm date overlapping

我发现了类似的问题,但没有考虑优先考虑。

R : (------------|xxxxxxx|ooo|xx|----------------)   (---)

S1: (------------)              (----------------)   (---)
S2:      (xxxxxxxxxxxxxxx)   (xxxxxx)
S3:                   (ooooooo)           (oo)

假设我有3个日期范围来源,名称S1,S2和S3分别为优先级1,2和3(1为最高)和结果R.我需要结果为非重叠日期范围优先级最高的地方。

我想到了一个解决方案,但它非常顺序。首先,我创建一个按升序日期排序的表,按优先级递减(在日期冲突的情况下,最高优先级在表中首先出现),其ID和动作(开放或近距离):

ID  | Action | Priority | Date |
--------------------------------
S1a | Open   |    1     |  1   |
S2a | Open   |    2     |  2   |
S1a | Close  |    1     |  3   |
S3a | Open   |    3     |  4   |
S2a | Close  |    2     |  5   |
S2b | Open   |    2     |  6   |
S3a | Close  |    3     |  7   |
S1b | Open   |    1     |  8   |
S2b | Close  |    2     |  9   |
S3b | Open   |    3     |  10  |
S3b | Close  |    3     |  11  |
S1b | Close  |    1     |  12  |
S1c...

然后我开始迭代这个表并填写有序列表和结果表:

所以第一行是:

Order List:          Result:
ID | Priority |      ID | Action | Date  |  
S1a|    1     |      S1a|  Open  |  1    |

第二行,添加了S2a的开放日期但没有写任何东西,因为表中存在更大的优先级:

Order List:          Result:
ID | Priority |      ID | Action | Date  |  
S1a|    1     |      S1a|  Open  |  1    |  
S2a|    2     |      

第三行,关闭S1a,写入结束日期,由于S2a移动到列表顶部,它也会写入S2a的开放日期。

  Order List:          Result:
  ID | Priority |      ID | Action | Date  |  
x S1a|    1     |      S1a|  Open  |  1    |  
  S2a|    2     |      S1a|  Close |  3    |
                       S2a|  Open  |  3    | 

我想你可以看到这是怎么回事......很多交叉检查等但是在纸上似乎有效。如果有人需要,我可以更好地解释算法,但我不认为这很难理解。如果有序列表中有更高的优先级,则不会写任何内容。删除较高优先级后,下一个最大优先级将再次打开。

也许有人有更好,更具体的想法?

谢谢你的时间!

4 个答案:

答案 0 :(得分:1)

另一种方法是以相反的方式构建它,首先填充具有最低优先级的表,并在用较高优先级项填充日期时简单地覆盖日期。这样就不必创建订单列表并跟踪打开等待启动的项目。

  

注意 :我应该事先说下面的算法几乎肯定效率不高(我没有做数学而是直觉   告诉我它的效率较低)。这只是另一种接近的方式   问题

以这种方式对每个事件的开放日期和结束日期进行分组会更有益。为了简单起见,我将把开始日期称为结束日期作为“事件”。因此,您首先添加最低优先级的每个事件。然后,您开始查看按优先级组织的数据,然后按日期查看。像这样:

ID  | Action | Priority | Date |
--------------------------------
S3a | Open   |    3     |  4   |
S3a | Close  |    3     |  7   |
S3b | Open   |    3     |  10  |
S3b | Close  |    3     |  11  |
S2a | Open   |    2     |  2   |
S2a | Close  |    2     |  5   |
S2b | Open   |    2     |  6   |
S2b | Close  |    2     |  9   |
S1a | Open   |    1     |  1   |
S1a | Close  |    1     |  3   |
S1b | Open   |    1     |  8   |
S1b | Close  |    1     |  12  |
-------------------------------

因此,只需执行最低优先级并将所有内容添加到结果中:

<强> RESULT

ID  | Action | Priority | Date |
--------------------------------
S3a | Open   |    3     |  4   |
S3a | Close  |    3     |  7   |
S3b | Open   |    3     |  10  |
S3b | Close  |    3     |  11  |

这是事情变得有趣的地方,现在你开始查看下一个最高优先级的事件。因此,我们遇到了第一个事件S2a,我们要做的是搜索S2a OpenS2a Close之间的结果中的日期。如果我们抽象地思考一下,我们会得到3种不同的情况:

  1. 我们找到了一个事件的开始。
  2. 我们找到了一个事件的结尾。
  3. 我们发现了一个事件的开始和结束。
  4. 在第一种情况下,由于事件的开始将被推到更高优先级事件的末尾。这是将包含的事件设置为当前更高优先级事件的Close

    在第二种情况下,由于事件的结束是在较高优先级事件开始之后,它必须提前结束。因此,我们将包含事件的结尾设置为当前优先级较高的事件的Open

    在最后一种情况下,整个事件包含在优先级较高的事件中,因此将被完全取消。那就是删除开头和结尾。

    因此,如果我们查看您的示例,我们会S2a Open = 2且S2a Close = 5.该范围中包含的唯一日期是S3a Open。因此,我们会将S3a Open的日期更改为S2a Close或5的值。所以现在我们的结果如下:

    <强> RESULT

    ID  | Action | Priority | Date |
    --------------------------------
    S2a | Open   |    2     |  2   |
    S2a | Close  |    2     |  5   |
    S3a | Open   |    3     |  5   |
    S3a | Close  |    3     |  7   |
    S3b | Open   |    3     |  10  |
    S3b | Close  |    3     |  11  |
    

    从其中推断其余部分是如何形成的并不难。 (但如果您想要更多解释,请告诉我。)

    根据信息的组织方式和所涉及的数据结构,这可能不如您所描述的那样有效。但我认为这一点更直观,因为您首先调度最低优先级,然后修改它们以便为更高优先级腾出时间。我没有看到你给出的解决方案有问题,它确保你只看一次每个条目(而我可以多次查看该项目,或修改最终被后来的事件破坏的项目)。 / p>

    我不推荐我的解决方案超过你的解决方案,但你没有要求提高效率,只是采用不同的方式来看待它。

答案 1 :(得分:0)

我最近编写了一个执行此操作的算法。我保留两个数组:一个像你的结果和一个间隔S的数组(不是日期,而是你所谓的ID)。最初,数组S在打开'('并且结果为空。

我还保留了一种订单清单,但与你的不同之处在于它没有按优先级排序,而是关闭')'。这样可以快速从订单列表中删除不再需要考虑的时间间隔,因为它们“在过去”:您可以在索引过去之前保留索引。

然而,实际的实现并没有单独的订单列表,而是重新使用S:

对于i的S [i]&gt; index_1是打开'('

)的输入

对于i&lt; i [i] index_1是在关闭')'

上排序的订单列表

对于i&lt; i [i] index_2'位于过去'

因此,当创建结果条目并且我们需要获得下一个S时,只有范围index_1 - &gt;需要搜索index_2(对于具有最高优先级的区间)。

让我们看看我是否可以重新创建前几次迭代:

// initialize
S = [S1a,S2a,S3a,S2b,S1b,S3b,S1c]
R = []
index_1 = 0;
index_2 = 0;

// first result starts at smallest open
R1.start = S1a.open()
date = S1a.open() // 'current time'

// check if next entry in S ends the result R1
S[++index_1] = S2a: S2a.open() < S1a.close and S2a.Priority < S1a.Priority

// this does not end R1 -> keep S sorted, update index_2
S[index_1 - 1] = S1a, S[index_1] = S2a, S1a.close < S2a.close // -> OK! no swap needed
date = S2a.open()
S[index_2] = S1a: S1a.close() < date // -> OK! no update of index_2 needed

// check if next entry in S ends the result R1
S[++index_1] = S3a: S3.open() > S1a.close

// this ends R1 -> create it, update index_2
date = S1.close()
R1.close() = date
S[index_2] = S1a: S1a.close() = date // -> S1a is 'past'
index_2++ // update index_2
S[index_2] = S2a: S2a.close() < date // -> OK! no further update of index_2 needed

// find the next interval for R2
search S[i] index_2 <= i < index_1 and pick max priority
in this case index_2 = 1 and index_1 = 2 so we only need to check one entry (S2a)

R2.open = max( date, S2a.open() )

// continue...
抱歉任何小错误,但我希望能传达算法的想法(不打算提供所有细节)

不确定性能,它取决于数据的类型,我猜(如果你有多个区间之间的重叠,范围index_2 - index_1很大并且搜索该范围以及在关闭时对它进行排序')'每次a新的间隔添加到订单列表变得昂贵;但是,如果你在后续的时间间隔内只有重叠index_2 - index_1只有1个元素,那么前面提到的两个动作都非常便宜)

如果这是一个问题,那肯定会减少内存的成本。

答案 2 :(得分:0)

上周我写了另一个算法;改善了最坏情况的复杂性。它有一个优先级队列(我使用了堆);这最初是空的。并且在打开日期排序的一系列间隔。

A = [S1a,S2a,S3a,S2b,S1b,S3b,S1c]
H = [] 

我们迭代A(提前时间); A的每个元素都将排入队列。由于它是优先级队列,因此根元素H [0]包含具有最高优先级的元素。一旦关闭,根元素就会出列。

您将看到结果是优先级队列的根元素,其间隔对应于该元素成为根(最高优先级)的“时间”,直到它从队列中出队(它已关闭) )或被排队到队列中的新元素“推下”(在关闭之前被优先级更高的区间取代)。

更正式:

当我们采用A [i ++]中的下一个元素时(提前时间);我们将它与优先级队列中当前的最高元素H [0]进行比较;并检查A [i] .Open&gt; H [0] .Close。

while( A[i].Open > H[0].Close )
{
    if( H[0].Close >= from ):
        We have a new result; R( S = H[0]; Open = from; Closed = H[0].Closed )
        Set from = H[0].Closed + 1;
    Dequeue H[0]
}

一旦A [i]。打开&lt; H [0]。关闭,我们将A [i]排入优先级队列。有两件事可以发生;

A [i]是具有最高优先级的区间,并成为新的根。然后我们有了新的结果; R(S =老根H [0]; Open = from; Closed = A [i] .Open);设置自= A [i]。打开

或者A [i]“消失”到堆中,H [0]不会改变;我们继续。

一旦我们迭代了A;我们继续将队列排队,必要时创建结果;直到队列为空。

对于内存,您可以使用单个数组实现此操作,并且只在此数组中进行交换;第一个x索引是堆;最后的y个元素是数组A.

运行您的示例:

// i = 0:入队S1a

A = [S2a,S3a,S2b,S1b,S3b,S1c]
H = [S1a] 

// i = 1:入队S2a

A = [S3a,S2b,S1b,S3b,S1c]
H = [S1a,S2a] 

// i = 2:第一个结果(---- |;出队S1a;入队S3a

A = [S2b,S1b,S3b,S1c]
H = [S2a,S3a] 

// i = 3:第二个结果| xx |;出列S2a(这使得S3a成为根);入队S2b;第三个结果| oo | (因为S2b从根位置驱动S3a)

A = [S1b,S3b,S1c]
H = [S2b,S3a]

// i = 4;排队S1b;第四个结果| xx |

A = [S3b,S1c]
H = [S1b,S3a,S2b]

// i = 5;排队S3b

A = [S1c]
H = [S1b,S3a,S2b,S3b]

// i = 6;第五个结果| ----);出列S1b,使S3a,S2b和S3b出列而不产生结果;入队S1c

A = []
H = [S1c]

//第六个结果( - );出列S1c;停止

答案 3 :(得分:0)

使用优先级队列的Bartel算法相当不错 - 算法因不相交而间断,因为:

    while( A[i].Open > H[0].Close )
{
    if( H[0].Close >= from ):
        We have a new result; R( S = H[0]; Open = from; Closed = H[0].Closed )
        Set from = H[0].Closed + 1;
    Dequeue H[0]
}

非重叠区间的情况实际应该是:

while( A[i].Open > H[0].Close )
{
    if( H[0].Close >= from ):
        We have a new result; R( S = H[0]; Open = from; Closed = H[0].Closed )
        Set from = A[i].from;
    Dequeue H[0]
}

这是新的来自下一个时间间隔