【问题标题】:How to find all overlapping ranges and partition them into chunks?如何找到所有重叠的范围并将它们分成块?
【发布时间】:2015-05-27 03:19:35
【问题描述】:

我有一个范围数组,我希望能够找到所有重叠的范围:

例如:

var examples = [
    // Group 1
    {start: 9, end: 10.50},       // Range 1
    {start: 10, end: 10.50},      // Range 5

    // Group 2
    {start: 11, end: 13},         // Range 2
    {start: 13.5, end: 14.5},     // Range 3
    {start: 11.5, end: 14}        // Range 4
]
  1. 范围 2 与范围 4 重叠
  2. 范围 3 与范围 4 重叠
  3. 虽然 Range 2 与 Range 3 不重叠,因为它们都与 Range 4 重叠。它们将被放在同一个组中
  4. 范围 1 和范围 5 仅相互重叠,因此它们将在自己的组中

JSFiddle 在这里:

http://jsfiddle.net/jukufon7/2/

【问题讨论】:

  • 范围是包含还是不包含?换句话说,如果我们有 {start:9, end:10} 和 {start:10,end:11},这是两个独立的组还是我们认为它们在单个值 10 处重叠?
  • @rici 独家。你给出的例子应该被认为是两组!
  • 区别在于我提出的算法步骤5.1中的比较是<还是<=

标签: javascript algorithm


【解决方案1】:

这是我的看法:

jsfiddle

var examples = [
    {start: 17, end: 20},
    {start: 9, end: 10.50},
    {start: 15, end: 17},
    {start: 11, end: 12},
    {start: 18, end: 19.5},
    {start: 19.5, end: 22},
    {start: 11.5, end: 12.5},
    {start: 11.5, end: 13},
    {start: 17.5, end: 18.5},
    {start: 19, end: 19.5},
    {start: 22, end: 25}
]

function partitionIntoOverlappingRanges(array) {
  array.sort(function (a,b) {
    if (a.start < b.start)
      return -1;
    if (a.start > b.start)
      return 1;
    return 0;
  });
  var getMaxEnd = function(array) {
    if (array.length==0) return false;
    array.sort(function (a,b) {
      if (a.end < b.end)
        return 1;
      if (a.end > b.end)
        return -1;
      return 0;
    });
    return array[0].end;    
  };
  var rarray=[];
  var g=0;
  rarray[g]=[array[0]];

  for (var i=1,l=array.length;i<l;i++) {
    if ( (array[i].start>=array[i-1].start)
         &&
         (array[i].start<getMaxEnd(rarray[g]))
    ) {    
      rarray[g].push(array[i]);
    } else {
      g++;   
      rarray[g]=[array[i]];
    }
  }
  return rarray;
} // end partitionIntoOverlappingRanges

以上examples的结果:

【讨论】:

    【解决方案2】:

    这是一个简单的扫描算法。由于需要对范围进行排序,它在 O(n log n) 中执行。

    基本思想是从左到右扫描寻找起点和终点(这需要每个点的排序列表)。扫描时,跟踪活动范围的数量(即,已遇到起点且尚未遇到终点的范围)。每次到达起点时,都需要将范围添加到当前组中。范围计数通过在每个起点递增并在每个终点递减来保持。每次计数返回 0 时,就找到了一个完整的组。

    如果您想计算简化的范围集而不是组,您可以简化。不是在组中保留一组范围,而是在活动范围计数从 0 增加到 1 时设置当前组合组的起点,当活动范围计数从 1 减少到 0 时设置结束点。在这种情况下,您只需要一个排序的起点列表和一个排序的终点列表(在所呈现的算法中,排序的起点是通过按起点对范围本身进行排序来找到的。需要组以便范围可以被添加到累积组中。)

    1. 按范围的起始值对范围进行排序。

    2. 制作一个结束值列表,并对其进行排序(不必知道哪个范围属于端点)。将此称为 end_values。

    3. 将 current_group 初始化为空集,将 active_range_count 初始化为 0。将 current_range 和 current_end 初始化为 0。

    4. 循环直到完成:

      1. 如果 current_range 是范围和范围的有效索引[current_range].start 小于 end_values[current_end]:

        • 将范围[current_range] 添加到 current_group,增加 current_range 并增加 active_range_count。
        • 循环。
      2. 否则,如果 current_end 是 end_values 的有效索引:

        • 减少 active_range_count 并增加 current_end。
        • 如果 active_range_count 为 0,则 current_group 是完整的;保存它,然后将 current_group 重新初始化为一个空集。
        • 循环。
      3. 否则,完成。

    以下是 javascript 中的两个版本:

    /* Partition an array of ranges into non-overlapping groups */
    /* Side-effect: sorts the array */
    function partition(ranges) {
      var end_values = ranges.map(function(r){return r.end}).sort(function(a, b){return a - b})
      ranges.sort(function(a, b){return a.start - b.start})
      var i = 0, j = 0, n = ranges.length, active = 0
      var groups = [], cur = []
      while (1) {
        if (i < n && ranges[i].start < end_values[j]) {
          cur.push(ranges[i++])
          ++active
        } else if (j < n) {
          ++j  
          if (--active == 0) {
            groups.push(cur)
            cur = [] 
          }    
        } else break   
      }
      return groups
    }
    
    /* Given a array of possibly overlapping ranges, produces
     * an array of non-overlapping ranges covering the same
     * values.
     */
    function compose_ranges(ranges) {
      var starts = ranges.map(function(r){return r.start}).sort(function(a, b){return a - b})
      var ends = ranges.map(function(r){return r.end}).sort(function(a, b){return a - b})
      var i = 0, j = 0, n = ranges.length, active = 0
      var combined = []
      while (1) {
        if (i < n && starts[i] < ends[j]) {
          if (active++ == 0) combined.push({start: starts[i]})
          ++i
        } else if (j < n) {
          if (--active == 0) combined[combined.length - 1].end = ends[j]
          ++j
        } else break;
      } 
      return combined
    } 
    

    【讨论】:

    • 非常感谢。如果写实际代码太费时间,你介意写一些伪代码吗?我对答案有点困惑。 (例如,我已经阅读了您的答案几次,但我仍然不明白为什么我们需要一个 end_values 数组) - 在其他一些我不太了解的事情中
    • @samol:我添加了一些解释。以上是伪代码:)
    • 什么是“ranges[current_range].start == scan_value”,这个scan_value是在哪里初始化的?
    • @samol:我忘了删除不必要的初始化。当我第一次输入算法时,我使用了一个明确的扫描值来表示我认为具有指导意义的内容,但我重新考虑了。对不起。现在修好了。
    • @samol:现在在javascript中,我不是专家。但它似乎有效。
    【解决方案3】:

    概念很简单:记录每个特定组的最大范围。

    试一下下面的代码。

    function contains(range, number) {
        return number > range.start && number < range.end
    }
    
    
    function partitionIntoOverlappingRanges(array) {
        var groups = [];
        for (var i = 0; i < array.length; i++) {
            for (var j = 0; j < groups.length; j++) {
                if (contains(groups[j], array[i].start) || contains(groups[j], array[i].end)) {
                    groups[j].arrays.push(array[i]);
                    if (groups[j].start > array[i].start) groups[j].start = array[i].start;
                    if (groups[j].end < array[i].end) groups[j].end = array[i].end;
                    break;
                }
            }
            if (j == groups.length) {
                groups.push({
                    start: array[i].start,
                    end: array[i].end,
                    arrays: [array[i]]
                })
            }
        }
        return groups
    }
    

    【讨论】:

    • 我认为这可能是错误的。它可能需要假设范围已经排序(它不是)。我用这个运行了一些测试用例,随机打乱了范围,它产生了不正确的结果。否则,如果我们先对数组进行排序,我认为它会产生正确的结果:) 谢谢
    • @samol 对不起,你是对的。我只是使用了您在 jsfiddle 上给出的示例,假设所有其他都已排序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-09
    • 1970-01-01
    • 2021-02-24
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多