解决最长单调子



算法系列第三篇

温故知新


题目重述

已知一个序列。由随机数构成。求其最长单调子序列。

要求:单调分严格和不严格两种情况,并分别求解并输出一个最长单调子序列和全部符合要求的子序列。


问题分析

本题是求解有约束条件的子序列问题。可用动态规划求解。因为本题是求解最长单调子序列的,包含求一个最长单调子序列和求解全部符合要求的序列,以下将依照这两种情况讨论算法复杂度。


求解一个最长单调子序列的算法复杂度

本题如果单调为递增的情况,序列长度为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的求解非常easy,通过分析能够发现,当中第一个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]1S[N]i=1開始循环至N-1K用于记录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]==kS[i]<P[k](注:若非严格则是“>=”),将S[i]存入P[--k],直至k==0,详细过程可參见函数void fn_OutPutLMS(int Pos )其算法复杂度为O(n)


e.要输出全部最长单调子序列,则须要确定全部最长子序列的末尾元素所在的位置,这个easy实现,即定义数组C[N],遍历LP[N]并记录值为MaxLen的元素的位置。

然后一次调用voidfn_OutPutLMS(int Pos )复杂度为O(kn)


程序实现

/*-----------------------------------------------------------------
*                    最长单调子序列问题 
* -----------------------------------------------------------------
*  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的时候,当中数组为随机函数生成)



posted @ 2015-06-08 15:58  hrhguanli  阅读(230)  评论(0编辑  收藏  举报