首页 > 代码库 > 利用动态规划找出最长公共单调递增子序列

利用动态规划找出最长公共单调递增子序列

1.设计一个O(n2)时间的算法。

方法如下:

①利用快速排序先将原序列排序。
②然后再计算原序列和已排序序列两者公共子序列。
③打印公共子序列。

代码如下:

/************************************************************************/  
/* 算法导论15.4-5 
 * 找出n个数的序列中最长的单调递增子序列 
 * 时间复杂度为O(n^2)*/  
/************************************************************************/  
#include <iostream>
#include <time.h>
using namespace std;
#define N 11
char *b[N+1][N+1]={NULL};
int c[N+1][N+1]={0};
void Lcs_Length(int *x,int *y) 
{
	for (int i1=0;i1<=N;i1++)
	{
		b[i1][0]="NULL";
	}
	for (int j1=0;j1<=N;j1++)
	{
		b[0][j1]="NULL";
	}
	for (int i=1;i<=N;i++)
	{
		for (int j=1;j<=N;j++)
		{
			if (x[i]==y[j])
			{
				c[i][j]=c[i-1][j-1]+1;
				b[i][j]="↖";
			}
			else
			{
				if (c[i-1][j]>=c[i][j-1])
				{
					c[i][j]=c[i-1][j];
					b[i][j]="↑";
				} 
				else
				{
					c[i][j]=c[i][j-1];
					b[i][j]="←";
				}
			}
		}
        
	}
}
void PRINT_LCS(int*x,int i,int j)
{
	if (i==0||j==0)
	{
		return;
	}
	if (b[i][j]=="↖")
	{
		PRINT_LCS(x,i-1,j-1);
		cout<<x[i]<<" ";
	}
	else
	{
		if (b[i][j]=="↑")
		{
			PRINT_LCS(x,i-1,j);
		} 
		else
		{
			PRINT_LCS(x,i,j-1);
		}
	}
}
int PARTITION(int A[],int p,int r)
{
    int x=A[r];
    int i=p-1;
    for (int j=p;j<=r-1;j++)//O(n)
    {
        if (A[j]<=x)
        {
            i++;
            swap(A[i],A[j]);
        }
    }
    swap(A[i+1],A[r]);
    return i+1;
}
void QUICKSORT(int A[],int p,int r)
{
    if (p<r)
    {
        int q=PARTITION(A,p,r);
        QUICKSORT(A,p,q-1);
        QUICKSORT(A,q+1,r);
    }
}
void printSequence(int *b,int* nums,int last);
void main()
{//为了和书中代码吻合,数组第一个位置存放空字符。 
	srand( (unsigned)time( NULL ) );//
	int x[N+1] ={0};
	int y[N+1] ={0};
	for (int i=1;i<=N;i++)
	{
		y[i]=x[i]=rand()%10+1;
		cout<<x[i]<<" ";
	}
	cout<<endl;
	QUICKSORT(y,0,N);//①利用快速排序先将原序列排序。
	Lcs_Length(x, y);//②然后再计算原序列和已排序序列两者公共子序列。
	PRINT_LCS(x,N,N);//③打印公共子序列。
	cout<<endl;
	cout<<"书中图15-8中的表格:"<<endl;
	for ( i=0;i<=N;i++)
	{
		for (int j=0;j<=N;j++)
		{
			cout<<b[i][j]<<" ";
		}
		cout<<endl;
		for (j=0;j<=N;j++)
		{
			cout<<c[i][j]<<" ";
		}
		cout<<endl;
	}  
}

2.设计一个O(nlgn)时间的算法。

①如果当前元素比当前子序列中所有元素都大则最长递增子序列的长度加一。

②否则使用二分法在当前子序列中查找到大于等于当前元素的最小元素,用当前元素替换这个最小元素。

参考代码(我精简并更改了参考代码):点击打开链接

代码如下:

/************************************************************************/  
/* 算法导论15.4-6 
 * 找出n个数的序列中最长的单调递增子序列 
 * 时间复杂度为O(nlgn)*/  
/************************************************************************/  
#include <iostream>
#include <vector>
#include <time.h>
using namespace std;
#define N 11
void printSequence(int *b,int* nums,int last);  
void main()
{
	srand( (unsigned)time( NULL ) );
    int nums[N+1]={0};  
	cout<<"原序列长度为"<<N+1<<",如下:"<<endl;  
    for (int i=0;i<=N;i++)
	{
		nums[i]=rand()%1000+1;
		cout<<nums[i]<<" ";
	}
    //数组b存储在递增子序列中当前元素的前一个元素序号   
    int b[N+1]={0}; //数组b存储在递增子序列中当前元素的前一个元素序号,根据数组b我们能回溯整个子序列     
	int last[N+1]={0},L=0;  //last中存储各不同长度的递增子序列(同一长度的子序列,只考虑尾元素最小的序列)中最后一个元素的序号,根据last数组我们能找到最长子序列的最后一个元素序号。
    for ( i=1;i<N+1;i++)//所以我们用last数组找到MAX子序列最后一个元素下标后,利用数组b回溯整个MAX子序列。  
    {  
        int low=0,high=L-1; 
        if(nums[i]>nums[last[L==0?0:high]])  
        {//如果当前元素比last中所有元素都大则最长递增子序列的长度加一,其尾元素为当前元素nums[i]  
			last[L++]=i;
            b[i]=last[high];  
        }
		else
		{//否则使用二分法在last中查找到大于等于当前元素nums[i]的最小元素  
            while(low<=high)  
            {     
                int middle=(high+low)/2;  				
                if(nums[i]>nums[last[middle]])  
                    low=middle+1;  
                else  
                    high=middle-1;  
            }  
            //更新last中的元素值和b中的下标值。 
            last[low]=i;  
            b[i]=last[low==0 ? 0 : low-1]; 
        }  
    }  
	int len=L;
	cout<<endl; 
	cout<<"公共子序列如下:"<<endl;
    printSequence(b,nums,last[len-1]);  
    cout<<endl; 
}
void printSequence(int *b,int* nums,int last)  
{ //回溯法 
    if(b[last]!=last)  
        printSequence(b,nums,b[last]);  
    cout<<nums[last]<<" ";  
}