分治算法设计:切割篱笆问题

切割篱笆问题

假设有道篱笆用N个同宽的木条拼接而成。因年久失修,有些木板已经折断,因而整个篱笆呈现出参差不齐的轮廓,所以要用新的木板替换。不过为了环保,可以用一部分旧篱笆切割出长方形的木板充当木料。图(b)表示在(a)形状的篱笆中能切割出的最大长方形。给定构成篱笆的各个木板的高度,编写程序计算能够切割出的最大长方形面积。不能斜线切割,即不允许采用如图(c)的切割方法。  

       图1    切割篱笆问题

暴力算法

给出保存各个木板高度的数组h[],截取第l个木板到第r个木板的长方形面积可用如下公式表示:

                                            (r-l+1)\cdot min(h[i])

最简单的解法就是,用双重for语句把可能的l和r值都带入上述公式,求出最终答案。于是可以得到一个O(n^2)时间复杂度的暴力算法。

// 给定保存木板高度的数组h[]时,返回长方形的最大宽度。
int bruteforce(const vector<int>& h){
  int ret = 0;
  int N = h.size();
  // 检索所有可能的left、right组合
  for(int left = 0; left < N; ++left){
    int minHeight = h[left];
    for(int right = left; right < N; ++right){
      minHeight =min(minHeight, h[right]);
      ret = max(ret, (right-left+1)*minHeight);
    }
  }

  return ret;
}

分治算法

为设计出分治算法,首先要确定以何种方式分割给定的问题。如图所示,先把n个木板平均分成两个子问题。那么我们期望的长方形会符合以下三种可能性之一:

  • 最大面积的长方形只能在左侧的子问题中获得
  • 最大面积的长方形只能在右侧的子问题中获得
  • 最大面积的长方形横跨左右两侧的子问题

                                             图2  切割篱笆问题的分治算法

对于前两种情况,只要递归调用一半子问题即可得到答案。然后选取较大值即可。之后再找出快速解决第3种情况的方法,就可以完成对此问题的分治算法。

横跨左右两侧子问题的解法

怎样才能找出横跨左右两侧子问题的面积最大的长方形呢?

首先我们需要明白一个事实:该长方形必定横跨两个子问题边界的两个木板。假设从这个长方形开始分别向左右两侧一格一格扩展下去,即可向左移动一格,也可向右移动一格,那么应该选择图2(c)中虚线表示的哪个长方形呢?

正确答案当然是:选择包含更高木板的右侧长方形。图(3)表示这种寻找方向。

图3   切割篱笆问题中合并算法的操作过程

于是我们得到切割篱笆问题的分治算法如下:

//保存各个木板高度的数组
vector<int>  h;
//返回h[left..right]区间中可截取的面积最大长方形的宽度。
int solve(int left, int right){
  // 初始部分:只有一个木板的情况
  if(left==right) return h[left];
  //分割为[left, mid]和[mid + 1, right]两个区间的子问题
  int mid = (left + mid) / 2;
  // 分别计算两个子问题。
  int ret = max(solve(left, mid), solve(mid + 1, right));
  // 子问题3:找出横跨两个子问题的面积最大的长方形
  int l0 = mid, hi = mid;
  int height = min(h[l0], h[hi]);
  //只考虑包含[mid, mid+1]的两个长方形
  ret = max(ret, height * 2);
  // 扩展长方形直到覆盖所有输入值。
  while(left< l0 || hi < right){
    // 总是向高度更高的方向扩展
    if(hi < right && (l0 == left || h[l0-1] < h[hi+1])){
       ++hi;
      height = min(height, h[hi]);
    }
    else{
      --l0;
      height = min(height, h[l0]);
    }

    //扩展后的长方形宽度
    ret = max(ret, height*(hi -l0 + 1));
  }

  return ret;

}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值