int LIS1(int *array,int arraySize)
{
int temp[arraySize],i,pos;
temp[0]=array[0];
int len=1;//用于标示temp数组中的元素个数
for(i=1;i<arraySize;i++)
{
if(array[i]>temp[len-1])
//如果大于temp中最大的元素,则直接插入到temp数组末尾
{
temp[len] = array[i];
len++;
}
else
{
pos = BiSearch(temp,len,array[i]);
//二分查找需要插入的位置
temp[pos] = array[i];
}
}
return len;
}
LIS1()用到的修改后的二分查找,该函数在查找成功的时候返回key所在的位置,查找不成功的时候返回
比key大的和Key最接近的元素所在的位置,
即
key
应该插入的位置
int BiSearch(int *data,int len,int key)
{
int low=0,high=len-1,mid;
while(low<=high)
{
mid = (low+high)/2;
if(data[mid]>key)
high = mid-1;
else if(data[mid]
low = mid+1;
else
return mid;
}
return low;
//数组data中不存在该元素,则返回该元素应该插入的位置
}
主程序,输出应为两种方法计算的LIS,为4 4
int main()
{
int array[]={1,-1,2,-3,4,-5,6,-7};
int lis = LIS(array,sizeof(array)/sizeof(array[0]));
int lis1 = LIS1(array,sizeof(array)/sizeof(array[0]));
printf("%d %d\n",lis,lis1);
return 0;
}
LIS1()算法的一个示例:
假设存在一个序列d[1..9] ={ 2,1 ,5 ,3 ,6,4, 8 ,9, 7},可以看出来它的LIS长度为5。
下面一步一步试着找出它。
我们定义一个序列B,然后令 i = 1 to 9 逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了
首先,把d[1]有序地放到B里,令B[1] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1
然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1
接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1..2] = 1, 5,Len=2
再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1..2] = 1, 3,Len = 2
继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1..3] = 1, 3, 6,还是很容易理解吧? Len = 3 了噢。
第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1..3] = 1, 3, 4, Len继续等于3
第7个, d[7] = 8,它很大,比4大,嗯。于是B[4] = 8。Len变成4了
第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。
最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。
于是我们知道了LIS的长度为5。
《编程之美》里有个题目是要求数组中最长递增子序列,在CSDN上看到的题目是数组中的最长递减子序列。题目如下:
求一个数组的最长递减子序列
比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2}
求一个数组的最长递增子序列
比如{1,-1,2,-3,4,-5,6,-7}的最长递减子序列为{1,2,4,3,6}
最长递增序列和最长递减子序列的解法是一样的,最不济,也可以先revert,求完再revert一次。
首先我们得搞清一个范围问题,虽然我们要的是一个最大值,但是是不是他是可以递推的呢,就是我只保存一个最大值,然后不断增加这个值,直觉上看应该是不太现实的,比如:12345-101234,我们数到5时最大值是5,但往后看时,都比5小,不能增加最大值,但事实上是最大值是6,如果我们受限于前面求的的一个最大值,就会产生错误的结果,也许你可以说,我就是保存一个值,没到一个点我就重新算新的最大值,虽然牺牲一点计算,但是我就想只有一个变量,有思路吗?如果没有就换个思路吧。既然递推困难,我们就把各个备选值都列出来,然后找出那个最大的,那哪些是备选值呢?其实以任意元素为终点的递增序列都是合法的candidate。
解法1:
如同上面的分析,考虑一个序列x0, x1,x2,… xn-1, xn。我们想知道以每个元素为终点的最长递增子序列的长度,令temp[i]表示以xi为终点的递增序列的长度,假设已经知道了temp[0]-temp[n-1],那如何求temp[n]呢?如果xi<xn那,xi就有可能在xn为终点的序列上,那以xn为终点的递增序列就至少是xi的最长序列+1。下面是代码:
#include <iostream>
using namespace std;
int LIS(int arr[],int n)
{
int *temp = new int[n];//存放当前遍历位置最长序列
for(int i=0;i<n;++i)
{
temp[i]=1; //初始化默认长度
for(int j=0;j<i;++j) //找出前面最长的序列
{
// 当前值 array[i] 跟已经遍历的值比较,
//大于已经遍历的值且已知递增序列+1 大于当前值则 更新当前最长递增序列值
if(arr[i]>arr[j] && temp[j]+1 > temp[i] )
{
temp[i] = temp[j] + 1;
}
}
}
int max=temp[0];
for(int k=0;k<n;++k)//找出整个数组中最长的子序列
{
if(max<temp[k])
max=temp[k];
}
return max;
}
int main()
{
int arr[]={1,-1,2,-3,4,-5,6,-7};
int result=LIS(arr,8);
cout<<result<<endl;
}