三种算法求一个数字序列的最长递增子序列
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--;
}
}
注意:这种求法在输入序列中元素各不相同是可行的,一旦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;
}
算法一:
将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;
}
相关文章推荐
- 三种算法获取一个整数的每个数字
- 最长递增子序列的三种算法
- 算法--判断一个数字序列是否为二叉排序树的后续遍历
- 一个将阿拉伯数字转换成中文大写的好算法【原创】(C#)
- 已知一棵二叉树的中序序列和后序序列,写一个建立该二叉树的二叉链表存储结构的算法
- 最长递增子序列 O(NlogN)算法
- 每天学习一算法系列(14) (输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字)
- 三种算法求解一个数组的子数组最大和
- 一个将阿拉伯数字转换成中文大写地好算法
- 问题描述如下: 有2.5亿个整数(这2.5亿个整数存储在一个数组里面,至于数组是放在外存还是内存,没有进一步具体说明); 要求找出这2.5亿个数字里面,不重复的数字的个数; 另外,可用的内存限定为600M; 要求算法尽量高效,最优;
- 大学时候想的一个算法——计算数组中最大和序列
- 每天学习一算法系列(18)(n 个数字(0,1,…,n-1)形成一个圆圈,从数字0开始,每次从这个圆圈中删除第m 个数字)
- 数字序列随机产生的算法
- 一个无聊男人的疯狂《数据结构与算法分析-C++描述》学习笔记 用C++/lua/python/bash的四重实现(7)习题2.8 随机数组的三种生成算法
- 大学时候想的一个算法——计算数组中最大和序列
- 求一个序列的所有组合的算法
- 探讨一个好算法——找出一百万个数字中十个最大数字的算法
- 有一个队列 ABCDEFG, 现有一个堆栈, 有三种操作, 出队, 入栈,出栈. 输出可以是出队操作也可以是出栈操作.试问如何判断某一序列不是无法输出的?
- 一个无聊男人的疯狂《数据结构与算法分析-C++描述》学习笔记 习题2.8 随机数组的三种生成算法(补) 将bash的实现翻译成比较纯正的bash风格
- 判断一个整数数组中是否有重复数字出现的O(n)时间复杂度算法