【问题标题】:Merging Ranges In C++在 C++ 中合并范围
【发布时间】:2011-03-11 18:10:15
【问题描述】:

我有一个随机排序的唯一封闭范围列表 R0...Rn-1 其中

Ri = [r1i, r2i] (r1i我)

随后一些范围重叠(部分或完全),因此需要合并。

我的问题是,用于合并这些范围的最佳算法或技术是什么。此类算法的示例或执行此类合并操作的库的链接会很棒。

【问题讨论】:

    标签: c++ algorithm merge range


    【解决方案1】:

    你需要做的是:

    1. 按字典顺序对项目进行排序,其中范围键为 [r_start,r_end]

    2. 迭代排序列表并检查当前项是否与下一项重叠。如果它确实将当前项目扩展为 r[i].start,r[i+1].end,然后转到下一个项目。如果不重叠,则将当前添加到结果列表并移至下一项。

    这里是示例代码:

        vector<pair<int, int> > ranges;
        vector<pair<int, int> > result;
        sort(ranges.begin(),ranges.end());
        vector<pair<int, int> >::iterator it = ranges.begin();
        pair<int,int> current = *(it)++;
        while (it != ranges.end()){
           if (current.second > it->first){ // you might want to change it to >=
               current.second = std::max(current.second, it->second); 
           } else {
               result.push_back(current);
               current = *(it);
           }
           it++;
        }
        result.push_back(current);
    

    【讨论】:

    • 这种方法的整体复杂度会是 O(nlogn) {Essentially sort-complexity + 1 linear scan of N} 吗?
    • 根据值适合的空间大小,使用基数排序可能比快速排序更有效。基数排序是 O(kn),其中 k 是键空间的大小。
    • r[i].end + 1 == r[i+1].start 时,您的算法如何处理案例? - 实际上,这个范围也可以合并。
    【解决方案2】:

    Boost.Icl 可能对你有用。

    该库提供了一些您可以在您的情况下使用的模板:

    • interval_set — 将集合实现为一组间隔 - 合并相邻的间隔。
    • separate_interval_set — 将集合实现为一组间隔 - 将相邻的间隔分开
    • split_interval_set — 将集合实现为一组间隔 - 在插入时,重叠间隔被拆分

    有一个example 用于将区间与库合并:

    interval<Time>::type night_and_day(Time(monday,   20,00), Time(tuesday,  20,00));
    interval<Time>::type day_and_night(Time(tuesday,   7,00), Time(wednesday, 7,00));
    interval<Time>::type  next_morning(Time(wednesday, 7,00), Time(wednesday,10,00));
    interval<Time>::type  next_evening(Time(wednesday,18,00), Time(wednesday,21,00));
    
    // An interval set of type interval_set joins intervals that that overlap or touch each other.
    interval_set<Time> joinedTimes;
    joinedTimes.insert(night_and_day);
    joinedTimes.insert(day_and_night); //overlapping in 'day' [07:00, 20.00)
    joinedTimes.insert(next_morning);  //touching
    joinedTimes.insert(next_evening);  //disjoint
    
    cout << "Joined times  :" << joinedTimes << endl;
    

    以及该算法的输出:

    Joined times  :[mon:20:00,wed:10:00)[wed:18:00,wed:21:00)
    

    这里是关于他们算法的复杂性:

    Time Complexity of Addition

    【讨论】:

      【解决方案3】:

      一个简单的算法是:

      • 按起始值对范围进行排序
      • 从头到尾遍历范围,每当找到与下一个重叠的范围时,将它们合并

      【讨论】:

      • 可以使用 std::priority_queue 代替排序吗 = 有点像扫描线方法?
      • 既然你只想从最低到最高遍历它们,std::priority_queue 应该可以工作,但我认为它不会比排序更快/...。毕竟,您按顺序遍历所有项目,因此最终将它们排序。
      • @Rikardo 优先队列仅在物品随时间到达时才有用。如果您拥有所有这些,只需对它们进行排序。同类最佳优先级队列和排序都是 O(nlogn)(优先级队列是 n 次插入,每次插入 O(logn)),但排序性能更好且开销更少。
      • @JimBalter 你能在下面看看我的回答,让我知道你的意见吗?
      【解决方案4】:

      O(n*log(n)+2n):

      • 制作r1_i -&gt; r2_i的映射,
      • r1_i 的快速排序,
      • 遍历列表为每个r1_i-value 选择最大的r2_i-value,
      • 使用该r2_i-value,您可以跳过所有小于r2_i后续 r1_i

      【讨论】:

      • 一点点:O(nlog(n) + 2n) = O(nlog(n) + n) = O(n*log(n) )
      • 当然。但是(尽管理论上不是)这种差异在实践中是显着的
      • 说在实践中有区别是没有意义的,因为big-O是一个理论上定义的概念,根据它的定义,O(nlogn+2n) = O(nlogn)。
      • 考虑快速排序是 O(nlogn) 但这可能意味着它的 O(nlogn+40n) 使您的算法实际上是 O(nlogn+42n) ... = O(nlogn)。
      • @Jim Balter:我同意andand,理论上没有区别!不,说“实践中存在差异”并非毫无意义。在实践中实践就是一切,理论上没有任何区别的大哦可能会完全毁了你!
      【解决方案5】:

      jethro 的回答包含错误。 应该是

      if (current.second > it->first){
          current.second = std::max(current.second, it->second);        
      } else { 
      

      【讨论】:

      • 这应该是对 jethro 答案的编辑,而不是它自己的答案。
      【解决方案6】:

      我的算法不使用额外的空间,而且很轻量级。我使用了2-pointer 方法。 'i' 不断增加,而 'j' 跟踪正在更新的当前元素。 这是我的代码:

      bool cmp(Interval a,Interval b)
       {
           return a.start<=b.start;
       }
      vector<Interval> Solution::insert(vector<Interval> &intervals, Interval newInterval) {
          int i,j;
          sort(intervals.begin(),intervals.end(),cmp);
          i=1,j=0;
          while(i<intervals.size())
          {
              if(intervals[j].end>=intervals[i].start)  //if overlaps
              {
                  intervals[j].end=max(intervals[i].end,intervals[j].end); //change
              }
              else
              {
                  j++;
                  intervals[j]=intervals[i];  //update it on the same list
              }
              i++;
          }
          intervals.erase(intervals.begin()+j+1,intervals.end());
          return intervals;
      }
      

      Interval 可以是具有数据成员“start”和“end”的公共类或结构。 快乐编码:)

      【讨论】:

        【解决方案7】:

        我知道这是在最初接受的答案之后很长时间。但在 c++11,我们现在可以通过如下方式构造一个priority_queue`

        priority_queue( const Compare& compare, const Container& cont )
        

        在 O(n) 次比较中。

        请看https://en.cppreference.com/w/cpp/container/priority_queue/priority_queue 了解更多详情。

        所以我们可以在 O(n) 时间内创建一个priority_queue(min heap) 对。获取 O(1) 中的最低间隔并在 O(log(n)) 时间内将其弹出。 所以整体时间复杂度接近O(nlog(n) + 2n) = O(nlogn)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-05-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-21
          • 2012-04-26
          • 2022-11-03
          相关资源
          最近更新 更多