动态规划 (Dynamic Programming) 之 最长递增子序列(Longest Increase Subsequence)
2009-03-02 16:43
423 查看
既然已经说到了最长公共子序列,就把这个递增子序列也说了。同样的,这里subsequence表明了这样的子序列不要求是连续的。比如说有子序列{1, 9, 3, 8, 11, 4, 5, 6, 4, 19, 7, 1, 7 }这样一个字符串的的最长递增子序列就是{1,3,4,5,6,7}或者{1,3,4,5,6,19}。
其实这个问题和前面的最长公共子序列问题还是有一定的关联的。假设我们的初始的序列S1。那我们从小到大先排序一下。得到了S1'。这样我们再球S1和S1'的最长公共子序列就可以知道答案了:)是不是有点巧妙啊。这个过程还是比较直观的。但是这个不是这次要说的重点,这个问题有比较传统的做法的.
我们定义L(j)是一个优化的子结构,也就是最长递增子序列.那么L(j)和L(1..j-1)的关系可以描述成
L(j) = max {L(i), i<j && Ai<Aj } + 1; 也就是说L(j)等于之前所有的L(i)中最大的的L(i)加一.这样的L(i)需要满足的条件就是Ai<Aj.这个推断还是比较容易理解的.就是选择j之前所有的满足小于当前数组的最大值.
很容易的我们写出了下面的代码:
和以往的代码有些类似,这里还有一些辅助的二维数组p用来回溯最长的这个subsequence.这整个算法的时间复杂度达到了O(n∧2).当然存在一些nlog(n)的实现.但不是动态规划的重点,这里就不说明了.
重点可以讲的是这个问题的扩展.下面的两个问题也是很经典的问题.
问题1.造桥问题. 原题是这样:Building Bridges. Consider a 2-D map with a
horizontal river passing through its center. There are n cities on
the southern bank with x-coordinates a(1) ... a(n) and n cities
on the northern bank with x-coordinates b(1) ... b(n). You want
to connect as many north-south pairs of cities as possible with
bridges such that no two bridges cross. When connecting cities, you
can only connect city i on the northern bank to city i on the southern
bank. (Note: this problem was incorrectly stated on the paper copies
of the handout given in recitation.)
大致就是要在一条河的南北两边的各个城市之间造若干座桥.桥两边的城市分别是a(1)...a(n)和b(1)...b(n).这里的要求a(i)只可以和b(i)之间造桥,同时两座桥之间不能交叉.希望可以得到一个尽量多座桥的方案.
比如上面这张图,初看上去让人有些没有思路.但是仔细一想,其实这就是一个完美的最长公共子序列的问题的变形.怎么讲呢?如果把河南边的a城市做一个排序,可以得到一个序列.如上图,我们得到的就是S1 = {2,1,3,5,4}在这里,同时北边也进行依次排序,得到序列S2 = {1,2,5,4,3}.进一步从南边的第一座桥开始计算在北边序列中的index.也就是S1中的每个值相对于S2中的位置.比如说A2在南边是第一个在北边是第二个,所以第一个元素是2.A1在北边的对应位置是1.A3在北边的对应位置是5,A5在北边的对应位置是3,最后一个A4在北边的对应位置是3.这样我们就得到一个新的序列S3= {2,1,5,3,4}.这个序列的实际意义就是南边的第几座桥需要连接到北边的第几座桥.做理想的情况就是递增的,那样所有的桥都可以建造:)也就是说我们的造桥问题就转化成了寻找这个序列的最长递增子序列的问题.当然就是{1,3,4}.也就是红线所代表的桥.
代码不贴了,但是问题确实比较奥妙.
问题2.Box Stacking. You are given a set of n types of
rectangular 3-D boxes, where the i^th box has height h(i), width
w(i) and depth d(i) (all real numbers). You want to create a stack
of boxes which is as tall as possible, but you can only stack a box on
top of another box if the dimensions of the 2-D base of the lower box
are each strictly larger than those of the 2-D base of the higher box.
Of course, you can rotate a box so that any side functions as its
base. It is also allowable to use multiple instances of the same
type of box.
这个问题的简要描述就是有若干个不同的箱子.你需要把他叠放的尽量的高.但是箱子的摆放必须满足大的在下面,小的在上面的原则.箱子可以旋转且数量不限.要求给出一组箱子就能求出能摆放的最大高度. 其实这个问题也是一个最长递增子序列的问题.只是隐藏的更深一点. 因为箱子可以翻转的缘故.我们首先定义我们的箱子的长宽高分别是h,w,d.为了避免重复计算,我们约定w<=d.这样每个箱子可以改成3个箱子.这样我们就不用在考虑旋转的问题了.第一步,我们先把箱子按照w*d的值来排序.(为社么要排序读者可以自己想想).然后我们的模型就开始用H(j)来表示第j个箱子时这个箱子的高度.记得最长递增子序列的约束条件是Ai<Aj.这里的条件只是改成了对应的di<dj&&wi<wj.同时原来的+1也变成了+hi.
最后贴一下不是很好的代码.但是大致上还是工作了:
到现在为止,我还是觉得学习动态规划没有什么捷径,就是尝试不同种类的各式的问题,通过分析问题把这些问题归类.从而有新问题出现的时候,想方设法的往已知的问题上套,希望可以用已知的解来解决未知的问题.
其实这个问题和前面的最长公共子序列问题还是有一定的关联的。假设我们的初始的序列S1。那我们从小到大先排序一下。得到了S1'。这样我们再球S1和S1'的最长公共子序列就可以知道答案了:)是不是有点巧妙啊。这个过程还是比较直观的。但是这个不是这次要说的重点,这个问题有比较传统的做法的.
我们定义L(j)是一个优化的子结构,也就是最长递增子序列.那么L(j)和L(1..j-1)的关系可以描述成
L(j) = max {L(i), i<j && Ai<Aj } + 1; 也就是说L(j)等于之前所有的L(i)中最大的的L(i)加一.这样的L(i)需要满足的条件就是Ai<Aj.这个推断还是比较容易理解的.就是选择j之前所有的满足小于当前数组的最大值.
很容易的我们写出了下面的代码:
// Longest_Increase_subsequence.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <vector> #include <iostream> #include "windows.h" /** * Description: Calulate the longest increase subsequence *@param s1, source sequence *@param s2, output, longest increase sequence */ template<typename T> void longest_increase_subsequence(const std::vector<T>& s1, std::vector<T>& s2) { int n = s1.size(); if (n<1) return; int m = 0; int k = 0; std::vector<unsigned int> b(n+1, 1); std::vector<unsigned int> p(n+1, 0); for (int i=1;i<=n;i++) { for (int j=1;j<i;j++) { if ( s1[i-1] > s1[j-1] && b[i] < b[j] +1 ) { b[i] = b[j] + 1; p[i] = j; } } } for ( int i=1;i<=n;i++) { if (m<b[i]) { m = b[i]; k = i; } } s2.resize(m); while (k>0) { s2[m-1] = s1[k-1]; m--; k = p[k]; } } int _tmain(int argc, _TCHAR* argv[]) { int a[] = { 1, 9, 3, 8, 11, 4, 5, 6, 4, 19, 7, 1, 7 }; std::vector<int> seq(a, a+sizeof(a)/sizeof(a[0])); std::vector<int> r; longest_increase_subsequence(seq, r); for (int i=0;i<r.size();i++) std::cout<<r[i]<<" "; std::cout<<std::endl; system("pause"); return 0; }
和以往的代码有些类似,这里还有一些辅助的二维数组p用来回溯最长的这个subsequence.这整个算法的时间复杂度达到了O(n∧2).当然存在一些nlog(n)的实现.但不是动态规划的重点,这里就不说明了.
重点可以讲的是这个问题的扩展.下面的两个问题也是很经典的问题.
问题1.造桥问题. 原题是这样:Building Bridges. Consider a 2-D map with a
horizontal river passing through its center. There are n cities on
the southern bank with x-coordinates a(1) ... a(n) and n cities
on the northern bank with x-coordinates b(1) ... b(n). You want
to connect as many north-south pairs of cities as possible with
bridges such that no two bridges cross. When connecting cities, you
can only connect city i on the northern bank to city i on the southern
bank. (Note: this problem was incorrectly stated on the paper copies
of the handout given in recitation.)
大致就是要在一条河的南北两边的各个城市之间造若干座桥.桥两边的城市分别是a(1)...a(n)和b(1)...b(n).这里的要求a(i)只可以和b(i)之间造桥,同时两座桥之间不能交叉.希望可以得到一个尽量多座桥的方案.
比如上面这张图,初看上去让人有些没有思路.但是仔细一想,其实这就是一个完美的最长公共子序列的问题的变形.怎么讲呢?如果把河南边的a城市做一个排序,可以得到一个序列.如上图,我们得到的就是S1 = {2,1,3,5,4}在这里,同时北边也进行依次排序,得到序列S2 = {1,2,5,4,3}.进一步从南边的第一座桥开始计算在北边序列中的index.也就是S1中的每个值相对于S2中的位置.比如说A2在南边是第一个在北边是第二个,所以第一个元素是2.A1在北边的对应位置是1.A3在北边的对应位置是5,A5在北边的对应位置是3,最后一个A4在北边的对应位置是3.这样我们就得到一个新的序列S3= {2,1,5,3,4}.这个序列的实际意义就是南边的第几座桥需要连接到北边的第几座桥.做理想的情况就是递增的,那样所有的桥都可以建造:)也就是说我们的造桥问题就转化成了寻找这个序列的最长递增子序列的问题.当然就是{1,3,4}.也就是红线所代表的桥.
代码不贴了,但是问题确实比较奥妙.
问题2.Box Stacking. You are given a set of n types of
rectangular 3-D boxes, where the i^th box has height h(i), width
w(i) and depth d(i) (all real numbers). You want to create a stack
of boxes which is as tall as possible, but you can only stack a box on
top of another box if the dimensions of the 2-D base of the lower box
are each strictly larger than those of the 2-D base of the higher box.
Of course, you can rotate a box so that any side functions as its
base. It is also allowable to use multiple instances of the same
type of box.
这个问题的简要描述就是有若干个不同的箱子.你需要把他叠放的尽量的高.但是箱子的摆放必须满足大的在下面,小的在上面的原则.箱子可以旋转且数量不限.要求给出一组箱子就能求出能摆放的最大高度. 其实这个问题也是一个最长递增子序列的问题.只是隐藏的更深一点. 因为箱子可以翻转的缘故.我们首先定义我们的箱子的长宽高分别是h,w,d.为了避免重复计算,我们约定w<=d.这样每个箱子可以改成3个箱子.这样我们就不用在考虑旋转的问题了.第一步,我们先把箱子按照w*d的值来排序.(为社么要排序读者可以自己想想).然后我们的模型就开始用H(j)来表示第j个箱子时这个箱子的高度.记得最长递增子序列的约束条件是Ai<Aj.这里的条件只是改成了对应的di<dj&&wi<wj.同时原来的+1也变成了+hi.
最后贴一下不是很好的代码.但是大致上还是工作了:
// box_stacking.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <iostream> #include <vector> #include <algorithm> #include <functional> #include "windows.h" /* Box Stacking. You are given a set of n types of rectangular 3-D boxes, where the i^th box has height h(i), width w(i) and depth d(i) (all real numbers). You want to create a stack of boxes which is as tall as possible, but you can only stack a box on top of another box if the dimensions of the 2-D base of the lower box are each strictly larger than those of the 2-D base of the higher box. Of course, you can rotate a box so that any side functions as its base. It is also allowable to use multiple instances of the same type of box. */ struct Box { int h; // height; int w; // width; int d; // depth; }; struct sizeSort: public std::binary_function <Box, Box, bool> { bool operator()(const Box& b1, const Box& b2) { return b1.w*b1.d > b2.w*b2.d; } }; int highest_box_stacking( const std::vector<Box>& b) { // first make the box 1*2*3 // to like this: 1*(2*3), 2*(1*3), 3*(1*2); // let's assume width<=depth, then we can get 3*n boxes. if (b.size()<1) return 0; std::vector<Box> box (b.size()*3); for (int i=0;i<b.size();i++) { box[i*3+0].h = b[i].h; box[i*3+0].w = b[i].w < b[i].d ? b[i].w : b[i].d; box[i*3+0].d = b[i].w < b[i].d ? b[i].d : b[i].w; box[i*3+1].h = b[i].w; box[i*3+1].w = b[i].h < b[i].d ? b[i].h : b[i].d; box[i*3+1].d = b[i].h < b[i].d ? b[i].d : b[i].h; box[i*3+2].h = b[i].d; box[i*3+2].w = b[i].h < b[i].w ? b[i].h : b[i].w; box[i*3+2].d = b[i].h < b[i].w ? b[i].w : b[i].h; } std::sort(box.begin(),box.end(),sizeSort()); std::vector <int> m(b.size()*3); m[0] = box[0].h; for ( int i = 1; i < box.size(); i++ ) { for ( int j = 0; j < i; j++ ) { if ( (box[i].w <= box[j].w) && (box[i].d <= box[j].d) && (m[i] < m[j]+box[i].h) ) m[i] = m[j] + box[i].h; } } return *std::max_element(m.begin(),m.end()); } int _tmain(int argc, _TCHAR* argv[]) { std::vector<Box> box(3); box[0].h = 2; box[0].w = 3; box[0].d = 4; box[1].h = 2; box[1].w = 3; box[1].d = 1; box[2].h = 5; box[2].w = 3; box[2].d = 4; std::cout<<highest_box_stacking(box)<<std::endl; system("pause"); return 0; }
到现在为止,我还是觉得学习动态规划没有什么捷径,就是尝试不同种类的各式的问题,通过分析问题把这些问题归类.从而有新问题出现的时候,想方设法的往已知的问题上套,希望可以用已知的解来解决未知的问题.
相关文章推荐
- [动态规划] 最长递增子序列 (Longest Increasing Subsequence)
- POJ-2533-Longest Ordered Subsequence-最长递增子序列-动态规划
- [动态规划-1] 最长递增子序列-Longest Increasing Subsequence
- 动态规划 (Dynamic Programming) 之 最长递增子序列(Longest Increase Subsequence)
- 动态规划(最长相同子序列,递增)
- POJ 2533--Longest Ordered Subsequence【最长递增子序列 + 二分优化】
- 最长递增子序列(动态规划)
- [LeetCode] Number of Longest Increasing Subsequence 最长递增序列的个数
- [LeetCode] Longest Continuous Increasing Subsequence 最长连续递增序列
- 最长递增子序列详解(longest increasing subsequence)
- poj 2533:Longest Ordered Subsequence 求最长递增序列长度
- 最长递增子序列详解(longest increasing subsequence)
- 动态规划——最大子序列、最长递增子序列、最长公共子串、最长公共子序列、字符串最小编辑距离日记整理
- Longest Continuous Increasing Subsequence(最长递增连续子序列)
- 动态规划之最长递增子序列 最长不重复子串 最长公共子序列
- 动态规划 —— 最长递增子序列(LIS)
- 动态规划求一道 类最长递增公共子序列
- POJ 2533 Longest Ordered Subsequence (DP,最长递增序列)
- hdu 1069 Monkey and Banana 动态规划(最长递增子序列变形)
- 动态规划 - 最长递增子序列(LIS)