您的位置:首页 > 其它

三种算法求一个数字序列的最长递增子序列

2017-04-17 10:24 204 查看
也有很多博客写如何实现最长递增子序列的算法,自己查阅了一些资料总结出三种实现的算法,两种是常见的处理思路,还有一种是本人自己想出来的算法,很好理解,但是效率不是特别高。

算法一:

将n个数的原序列A
排序后得到递增序列B
,则把求A的最长单调递增子序列问题转化成求A、B序列的最长公共子序列(LCS)。

这个算法是算法导论书后面习题的标准答案解法了,有很多人写过实现。时间复杂度O(n^2)。

#include<iostream>
#include<stdio.h>
#include<cmath>
#include<string>
#include<string.h>
#include<set>
#include<map>
#include <algorithm>
using namespace std;
/*方法1:将这n个数的序列排序之后,将最长递增子序列转变为LCS*/
int main() {
int n;
int A[100], B[100], res[100], len[105][105];
while (scanf("%d", &n) == 1) {
memset(res, 0, sizeof(res));
memset(len, 0, sizeof(len));
for (int i = 0; i < n; i++) {
scanf("%d", &A[i]);
B[i] = A[i];
}
sort(B, B + n);
int i, j, cnt = 0;
for (i = 1; i <= n; i++) {
for (j = 1; j <= n; j++) {
if(A[i-1] == B[j-1]) len[i][j] = 1 + len[i-1][j-1];
else len[i][j] = max(len[i-1][j], len[i][j-1]);
}
}
//输出任意一个最长公共子序列,倒叙遍历len数组
for (i = n, j = n; i > 0 && j > 0;) {
if (len[i][j] == len[i-1][j]) {
i--;
}
else if(len[i][j] == len[i][j-1]) {
j--;
}
else {
res[cnt++] = A[i-1];
i--;
j--;
}
}
printf("%d\n%d", cnt, res[cnt-1]);//输出这个最长公共子序列。
for (i = cnt - 2; i >= 0; i--) printf(" %d", res[i]);
printf("\n");
}
return 0;
}

注意:这种求法在输入序列中元素各不相同是可行的,一旦A中有重复元素,单纯的将A sort一下是不可以的,这时候要先去重再排序得到B序列。

算法二:

因为自己正在学DP,所以一开始是尝试使用DP的思想做的,时间复杂度也是O(n^2)。以dp[i]表示以i位结尾的最长递增子序列的长度。那么dp[i] = max(dp[i], dp[j]+1), j = 1, 2, 3,...,i-1。对于每个j<i遍历求出最大的dp[i],并用res[i] = j 来记录以i位结尾的最长递增子序列的前一位是谁,方便之后遍历输出子序列。

#include<iostream>
#include<stdio.h>
#include<cmath>
#include<string>
#include<string.h>
#include<set>
#include<map>
#include <algorithm>
using namespace std;

/*输出最长递增子序列*/
/*方法2:n^2复杂度,dp*/
int main() {
int n;
int num[100], res[100],dp[100],ans[100];
while (scanf("%d", &n) == 1) {
memset(res, 0, sizeof(res));
memset(dp, 0, sizeof(dp));
memset(ans, 0, sizeof(ans));
for (int i = 0; i < n; i++) {
scanf("%d", &num[i]);
}
dp[0] = 1;
res[0] = 0;
for (int i = 1; i < n; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if(num[i] > num[j]) {
dp[i] = max(dp[i], dp[j] + 1);
if(dp[i] == (dp[j] + 1)) res[i] = j;
}
}
if(dp[i] == 1) res[i] = i;
}
//遍历得到最长的递增子序列的长度以及结尾的位数
int maxlen = dp[0], maxloc = 0;
for(int i = 1; i < n; i++) {
if(dp[i] > maxlen) {
maxlen = dp[i];
maxloc = i;
}
}
//printf("%d\n",maxlen);
//遍历res数组,将这个最长递增子序列保存并输出
int cnt = 0;
ans[cnt++] = num[maxloc];
while (res[maxloc] != maxloc) {
maxloc = res[maxloc];
ans[cnt++] = num[maxloc];
}
for (int i = cnt - 1; i > 0; i--) {
printf("%d ",ans[i]);
}
printf("%d\n", ans[0]);
}
return 0;
}

算法三:
这个算法是效率比较高的dp求解。时间复杂度是O(nlgn)。关键是要抓住一个长度位i的候选子序列的尾元素至少不比一个长度为i-1的候选子序列的尾元素小。这是因为长度为i的候选单调递增子序列一定是从某个长度为i-1的候选单调递增子序列再加一个尾元素得到的,以L[i]表示长度为i的候选子序列的尾元素,则有L[1]<=L[2]<=L[3]<=...<=L[m],m为最长候选子序列长度。

#include<iostream>
#include<stdio.h>
#include<cmath>
#include<string>
#include<string.h>
#include<set>
#include<map>
#include <algorithm>
using namespace std;
/*方法3:nlgn复杂度。一个长度为i的尾元素至少不比一个长度为i-1的尾元素小。*/
int main() {
int n;
int num[100], L[100], prel[100], M[100], res[100];
while (scanf("%d", &n) == 1) {
memset(L, 0, sizeof(L));//L[i]记录的是在所有长度为i的子序列中尾元素最小的那个数值
memset(prel, 0, sizeof(prel));//记录递增序列尾元素的前一位元素在原序列中的位置。
memset(M, 0, sizeof(M));//记录尾元素在原序列中的位置。
int len = 0, i, pos;
for (i = 0; i < n; i++) {
scanf("%d", &num[i]);
}
L[0] = num[0];
M[0] = 0;
prel[0] = -1;
len = 1;
for (i = 1; i < n; i++) {
pos = lower_bound(L, L + len, num[i]) - L;
L[pos] = num[i];
M[pos] = i;
if(pos > 0) prel[i] = M[pos-1];
else prel[i] = -1;
if (pos == len) len++;
}
//for (i = 0; i < len; i++) printf("%d ", L[i]);
pos = M[len-1];
for (i = len - 1; i >= 0 && pos != -1; i--) {
res[i] = num[pos];
pos = prel[pos];
}
//输出
for (i = 0; i < len - 1; i++) {
printf("%d ",res[i]);
}
printf("%d\n", res[len-1]);
}
return 0;
}

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐