Advertisement

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

阅读量:

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

算法一:

将n个数的原序列A[n]排序后得到递增序列B[n],则把求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;
    
 }
    
    
    
    
    AI写代码

注意:这种求法在输入序列中元素各不相同是可行的,一旦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;
    
 }
    
    
    
    
    AI写代码

算法三:

这个算法是效率比较高的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;
    
 }
    
    
    
    
    AI写代码

全部评论 (0)

还没有任何评论哟~