本篇博文为追忆曾经写过的算法系列第三篇
温故知新
题目重述
已知一个序列,由随机数构成,求其最长单调子序列。
要求:单调分严格和不严格两种情况,并分别求解并输出一个最长单调子序列和所有符合要求的子序列。
问题分析
本题是求解有约束条件的子序列问题,可用动态规划求解。由于本题是求解最长单调子序列的,包括求一个最长单调子序列和求解所有符合要求的序列,下面将按照这两种情况讨论算法复杂度。
求解一个最长单调子序列的算法复杂度
本题假设单调为递增的情况,序列长度为N(任意大小),即序列S[N]。若采用直接搜索的方法,同时定义数组LP[N]记录对应序列元素所在最长单调子序列中的位置。其算法思想如下:序列从S[1]开始(S[0])已经初始化),每递增一个,判断与之前的每个数值的大小,若S[i]>
S[j](j<i) (注:若非严格则是“>=”)且LP[i]<=LP
[j]+1,则更新LP[i]为LP[j]+1。这样保证了每个元素归类到自身所满足的最长子序列当中,但此算法的复杂度为O(n^2)。
通过对第二层循环,即确定新加进的元素其所在最长子序列的位置,可改进搜索策略,将复杂度降低为O(nlogn)。基本思想是:定义数组Len[N+1],第0个元素空闲,第j(j>0)个位置存储所有长度为j的子序列中最后元素的最小值,这样可以保证当前序列为最长序列且保持局部最优。当对新添加的元素S[i]进行判别时,采用二分搜索法在Len数组中搜索元素的位置,由于Len一直保持升序排列,且搜索到其所在的位置后,取代比他大的元素,从而成为那个长度的序列最后元素最小,其搜索复杂度为O(logn)。在算上第一层循环的O(n),所以复杂度为O(nlogn)。通过LP[N]和S[N]可循环求解出一个最长单调子序列,复杂度为O(n)。所以,总复杂度为O(nlogn)。
求解所有最长单调子序列的复杂度
求解所有最长单调子序列,其和2.1中的唯一不同在于求解输出所有最长单调子序列,而求解LP[N]和MaxLen的复杂度同2.1中的分析,最好方法的复杂度为O(nlogn)。输出所有最长单调子序列(设其复杂度为O(T))和上述过程是并列而非嵌套,所以总的复杂度为。
而T的求解很容易,通过分析可以发现,其中第一个n指循环LP[N]得到最长子序列的起始位置,而k指所有最长子序列的个数,取小于号是因为最长序列的起始点总是小于n的。
综上可以得到,求解所有最长单调子序列的复杂度为,其中k的取值为[0,n]。即极端情况下为O(n^2),这里之所以讨论是想强调无限长序列下有限个最长单调子序列的思想。
算法思想与实现
a.定义S[N],LP[N],Len[N+1]三个数组,其分别是序列本身,序列元素对应的最长子序列位置记录,长度为i的子序列最末元素的最小值。其中S[N]随机生成,Len[0]闲置;
b.初始化LP[0]为1,S[N]从i=1开始循环至N-1;K用于记录Len的最大值,即目前搜索到的最长子序列长度,其初始值为K=1;若S[i]>Len[K]
(注:若非严格则是“>=”),即下一个元素的值大于最长子序列最后元素的值,则直接将S[i]放在Len[++K]的位置;若S[i]<=Len[K],则进入步骤c;
c.调用函数k
= fn_InsertPos(),并执行如下操作: Len[k]=S[i]; LP[i] = k; LP[i]仍记录了S[i]所在的最长子序列位置,有利于序列的输出;fn_InsertPos()采用二分查找法,但和常规的查找条件不同,其算法复杂度为O(logn);
d.完成S[i]的搜索后,Len所记录的k值即为最长单调子序列的长度,此时可通过LP[N
]数组的记录求出一个最长子序列,即从后向前遍历LP[N],用辅助数组P[N]记录子序列,k记录当前需要存入的子序列长度,当LP[i]==k且S[i]<P[k]时(注:若非严格则是“>=”),将S[i]存入P[--k],直至k==0,具体过程可参见函数void
fn_OutPutLMS(int Pos ),其算法复杂度为;
e.要输出所有最长单调子序列,则需要确定所有最长子序列的末尾元素所在的位置,这个容易实现,即定义数组C[N],遍历LP[N]并记录值为MaxLen的元素的位置。然后一次调用voidfn_OutPutLMS(int
Pos ),复杂度为。
程序实现
/*----------------------------------------------------------------- * 最长单调子序列问题 * ----------------------------------------------------------------- * By Gu Jinjin SEU * 求解最长单调子序列,分严格和不严格两种情况 * 这里以单调递增为例 * Time : 2012/11/28-29 Weather:rainy */ #include <iostream> #include <cstdlib> #include <ctime> using std::cout; using std::endl; // define #define N 5000 // S[N]-序列,LP[N]-序列元素在最长子序列中的位置 // Len[N+1]-用于记录i长度的所有单调子序列末尾元素最小值 int S[N],LP[N],Len[N+1]; // 记录最长单调子序列长度 int MaxLen; // 函数声明 void fn_RandNum(); int fn_InsertPos(int Si, int K); int fn_GetLMS_Len(); void fn_OutPutInitList(); void fn_OutPutLMS(int Pos); void fn_GetAllLMSes(); /*----------------------------------------------------------------- * void main( void ) * ----------------------------------------------------------------- * 主函数 */ void main() { clock_t t_start,t_end; fn_RandNum(); t_start=clock(); MaxLen = fn_GetLMS_Len(); t_end=clock(); cout<<"Inital List:"<<endl; cout<<"=============================="<<endl; fn_OutPutInitList(); cout<<"All LMSes:"<<endl; cout<<"=============================="<<endl; fn_GetAllLMSes(); cout<<"=============================="<<endl; cout<<"The needed time:"<<difftime(t_end,t_start)<<"ms"<<endl; } /*----------------------------------------------------------------- * void fn_RandNum( ... ) * ----------------------------------------------------------------- * 生成随机数 */ void fn_RandNum() { // 用于保证是随机生成的数 // 不同的种子可以生成不同的随机数 //srand((unsigned)time(NULL)); for(int i=0; i<N; i++) { S[i] = rand()%N; LP[i] = 1; // 数组初始化 Len[i] = 0; } } /*----------------------------------------------------------------- * void fn_InsertPos( ... ) * ----------------------------------------------------------------- * 计算元素所在的最长子序列中位置,并返回 */ int fn_InsertPos(int Si, int K) { int low=1, high=K, mid; //定义上下界和中间值 mid=(low+high)/2; // 若low>high,则说明搜索到 while(low <= high) { if(low > high)break; else if(Len[mid]<Si)low = mid+1; else high = mid -1; mid=(low+high)/2; } //返回插入的位置,即S[i]元素所对应的最长子序列的长度 return(high+1); } /*----------------------------------------------------------------- * void fn_GetLMS_Len( ... ) * ----------------------------------------------------------------- * 计算LMS的长度 */ int fn_GetLMS_Len() { int lmn=1,k=1; Len[k]=S[0]; for(int i=1; i<N; i++) { if(S[i]>Len[lmn]) { Len[++lmn]=S[i]; LP[i]=lmn; } else { k = fn_InsertPos(S[i],lmn); Len[k] = S[i]; LP[i] = k; } } return(lmn); } /*----------------------------------------------------------------- * void fn_OutPutInitSeq( ... ) * ----------------------------------------------------------------- * 输出原始数列 */ void fn_OutPutInitList() { cout<<"S"<<'\t'<<"LP"<<'\t'<<"Len"<<endl; cout<<"------------------------------"<<endl; for(int i=0; i<N; i++) { cout<<S[i]<<'\t'<<LP[i]<<'\t'<<Len[i]<<endl; } } /*----------------------------------------------------------------- * void fn_OutPutLMS( ... ) * ----------------------------------------------------------------- * 输出一个LMS函数 */ void fn_OutPutLMS(int Pos) { int P[N],k=MaxLen-1; P[k]=S[Pos]; for(int i=Pos-1; i>=0; i--) { if(LP[i] == k && S[i]< P[k])P[--k]=S[i]; } // OutPut LMS if(k==0) { for(int i=0; i<MaxLen; i++)cout<<P[i]<<'\t'; cout<<endl; cout<<"------------------------------"<<endl; } } /*----------------------------------------------------------------- * void fn_GetAllLMSes( ... ) * ----------------------------------------------------------------- * 获取所有LMSes */ void fn_GetAllLMSes() { // C[N]用于记录长度为MaxLen序列的元素在LP[N]中位置 int C[N], k=0; for(int i=N-1; i>=MaxLen-1; i--) { if(LP[i]==MaxLen){ C[k]=i; k++;} } for(int i=0;i<k;i++) { fn_OutPutLMS(C[i]); } }
结果(N取20的时候,其中数组为随机函数生成)
最长单调子序列求解