求数组中最长递增子序列

来源:互联网 发布:微云同步盘mac版 编辑:程序博客网 时间:2024/05/23 01:14
问题:给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续),如序列  1,-1,2,-3,4,-5,6,-7的最长递增子序列长度为4(1,2,4,6)

方法一:
最笨算法,复杂度为O(n*n),设一个辅助数组用来记录以对应元素结尾的最大递增子序列的长度(即lis[i]表示
以array[i]结尾的最大递增子序列长度为lis[i]),从头到尾扫一遍原数组,对于每个元素,以其结尾的最大递增子序列长度要么为1,要么为这个元素前面的元素中小于该元素且最大递增子序列长度最大的那个元素对应的最大递增子序列长度+1。
int LIS(int *array,int arraySize)
{
    int lis[arraySize],i,j;
    for(i=0;i<arraySize;i++)
    {
        lis[i]=1;
        for(j=0;j<i;j++)
        {
            if(array[i]>array[j] && lis[j]+1>lis[i])
                lis[i] = lis[j] + 1;
        }
    }
    int max=0;
    for(i=0;i
        if(lis[i]>max)
            max = lis[i];
    return max;
}

方法二:
复杂度为O(nlogn)的算法,其思想是设一个辅助数组temp,temp[i]表示长度为i+1(也就是程序中的len)的最长递增子序列的最小末尾,则显然temp是有序的。遍历原数组中的每一个元素,对于每个元素key,若比temp数组中当前最后一个元素大,则直接插入temp数组,表示最长递增子序列长度可以增加一,新的LIS以这个元素结尾;否则要么temp中已包含key这个元素,则不做任何操作,要么temp中的数全部大于这个元素,则key取代temp[0]成为长度为1的LIS的结尾元素,要么必然存在temp[j]<key<temp[j+1],这时key应取代temp[j+1]成为长度为j+2的LIS的结尾元素,对于后两种情况(temp中的数全部大于这个元素或temp[j]<key<temp[j+1]),key实际是取代了temp中比key大的和key最接近的元素。最后temp数组的长度(len)即为所求。
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。
0 0
原创粉丝点击