您的位置:首页 > 其它

最长递增子序列问题——动态规划

2017-10-26 08:12 239 查看

最长递增子序列问题

LIS问题描述:给出一个数列A,求A的一个长度最大的子数列B,使得B是一个递增数列。

例如:数列A:5,2,8,6,3,6,9,7

一个递增的子数列为5,8,9;

一个长度最大的递增子数列为2,3,6,9或者2,3,6,7,则其最大长度为4.

解法一:动态规划法(时间复杂度O(n^2))

设长度为n的数组为[a0, a1, …, an-1],假定以aj结尾的数组序列的最长递增子序列长度为L[j],则可以得出公式L[j]=1+{max(L[i]), i < j且a[i] < a[j]}.

也就是说,我们需要遍历在j之前的所有位置i(从0到j-1),找到满足条件a[i] < a[j]的L[i],求出max(L[i])+1即为L[j]的值。

最后,我们遍历数组L[j] (j从0到n-1),找出其最大值即为最大递增子序列的长度。

由此可见,需要两层循环,时间复杂度为O(n^2)



我们根据上面所述算法描述求数列A:5,2,8,6,3,6,9,7的最大递增子序列的过程。

5=>L[0]=1

2=>L[1]=1

8=>L[2]=2

6=>L[3]=2

3=>L[4]=2

6=>L[5]=3

9=>L[6]=4

7=>L[7]=4

代码如下:

#include <iostream>
using namespace std;

int lis(int arr[], int len) {
int longest[len];
for (int i = 0; i < len; i++) {
longest[i] = 1;
}

for (int j = 1; j < len; j++) {
for (int i = 0; i < j; i++) {
if (arr[j] > arr[i] && longest[j] < longest[i] + 1) {// 注意longest[j] 小于 longest[i]+1 不能省略
longest[j] = longest[i] + 1;// 计算以arr[j]结尾的序列的最长递增子序列的长度
}
}
}

int max = 0;
for (int j = 0; j < len; j++) {
cout << "longest[" << j << "]=" << longest[j] << endl;
if (longest[j] > max) max = longest[j];// 从longest[j]中找出最大值,即为最长长度
}
return max;
}
int main() {
int arr[] = {5, 2, 8, 6, 3, 6, 9, 7};// 测试数组
cout << "The Length of Longest Increasing Subsequence is " << lis(arr, sizeof(arr) / sizeof(arr[0])) << endl;
return 0;
}


程序运行结果如下:



解法二:时间复杂度O(nlogn)

算法求解过程:

同样假设存在待求解的数列A,我们定义一个数组B,和一个变量resLen表示当前最长长度,初始化为1。

然后令i=0到7考察数列A[i]和B[resLen-1]的大小:若A[i]>B[resLen-1],则更新B[resLen]的值为A[i],resLen加1;否则,采用二分法将A[i]的值插入B数组中。

例如针对上述数列A:5,2,8,6,3,6,9,7,根据算法过程可得:

B[0]=A[0]=5,即长度为1的LIS的最小末尾为5

因为A[1]=2小于B[0]=5,所以B[0]的值更新为2,即长度为1的LIS的最小末尾为2

因为A[2]=8大于B[0]=5,因此B[1]的值更新为8,即长度为2的LIS的最小末尾为8

因为A[3]=6小于B[1]=8,因此B[1]的值更新为6,即长度为2的LIS的最小末尾为6

因为A[4]=3小于B[1]=6,因此B[1]的值更新为3,即长度为2的LIS的最小末尾为3

因为A[5]=6大于B[1]=3,因此B[2]的值更新为6,即长度为3的LIS的最小末尾为6

因为A[6]=9大于B[2]=6,因此B[3]的值更新为9,即长度为4的LIS的最小末尾为9

因为A[7]=7小于B[3]=9,因此B[3]的值更新为7,即长度为4的最小末尾为7

其实,从这个过程中,我们可以得知,数组B维护的是当前最长递增子序列的最小末尾值,从而保证后面不断检查的数依然能够正确地插入到数组B中,并得到正确的LIS的长度。

而且,在数组B中插入数据是有序的,是进行替换插入而非挪动,我们可以使用二分查找,将每一个数的插入时间优化到O(logn),因此算法的时间复杂度就降到O(nlogn)了。

代码如下:

#include <iostream>
using namespace std;

// 二分查找,返回数组元素需要插入的位置
int BiSearch(int b[], int len, int value) {
int left = 0, right = len - 1;
int mid;
while (left <= right) {
mid = left + (right - left) / 2;
if (b[mid] > value) {
right = mid - 1;
}
else if (b[mid] < value) {
left = mid + 1;
}
else {
return mid;
}
}
return left;
}

int LIS(int arr[], int len) {
int resLen = 1;// 用于记录B数组中的元素个数,最终结果即为最长长度
int B[len];// 在动态规划中使用的数组,用于记录中间结果
B[0] = arr[0];

for (int i = 1; i < len; i++) {
if (arr[i] > B[resLen - 1]) {// 如果大于B中最大的元素,则直接插入到B数组末尾
B[resLen] = arr[i];
++resLen;
}
else {
int pos = BiSearch(B, resLen, arr[i]);// 二分查找需要插入的位置
B[pos] = arr[i];
}
}
// 输出B数组的结果
for (int i = 0; i < resLen; i++) {
cout << "B[" << i << "]=" << B[i] << endl;
}
return resLen;
}

int main() {
int array[] = {5, 2, 8, 6, 3, 6, 9, 7};// 测试数组
cout << "LIS: " << LIS(array, sizeof(array) / sizeof(array[0]));
return 0;
}


运行结果如下:



未完待续…(待我理解LCS:最长公子序列问题)

参考博客:最长递增子序列
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  动态规划 算法