最长递增子序列(Longest Increasing Subsequence)-C语言实现

这篇具有很好参考价值的文章主要介绍了最长递增子序列(Longest Increasing Subsequence)-C语言实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最长递增子序列(Longest Increasing Subsequence)

  1. 前言

最长递增子序列属于经典的动态规划问题,属于约束条件下求最大子集的问题。这里的约束条件意思是,子序列需要严格按照递增条件产生,在这个前提之下,我们要求到最长的子序列。这类问题的衍生问题有很多,其本质都是穷举最大化子集。

  1. 问题描述

问题其实非常简单,给你一个整数数组 arr ,找到其中最长严格递增子序列的长度。我们任意提取数组中的元素,元素下标有:
i k < i j < i l < i m i_k<i_j<i_l<i_m ik<ij<il<im
如果对应的值严格递增(不包括等于),
a r r [ i k ] < a r r [ i j ] < a r r [ i l ] < a r r [ i m ] arr[i_k]<arr[i_j]<arr[i_l]<arr[i_m] arr[ik]<arr[ij]<arr[il]<arr[im]
那么此子序列就是递增的子序列,其长度为4。

如果考虑最长递增子序列,那么就要穷举数组中的所有子序列,然后找到最大值。

  1. 求解过程

为了深入学习动态规划的基本思维,我们坚持采用《算法导论》中的 CRCC方法对其进行分析,以便后续不断提升动态规划算法思维。求解动态规划框架分为四步。

a.) 表征优化问题的结构(Characterized the structure of the optimal solution)

首先给出例子,帮助理解整体的过程,

最长上升子序列c语言,算法,数据结构,动态规划,c语言

如果求出以index=6结尾的数组的最长递增子序列,那么我们可以遍历index=6之前所有元素,如果此元素小于6号元素的值,那么仅需把此元素处的LIS的值递增1即可,最后比较所有的可能。如果用图的术语解释,就是需要找到元素单调递增的最长路径(权值为1)。

最长上升子序列c语言,算法,数据结构,动态规划,c语言

如果需要计算以index=6为尾部的LIS值,可以用公式求解:
L I S [ 6 ] = m a x { L I S [ 2 ] , L I S [ 3 ] + 1 , L I S [ 4 ] + 1 } = m a x { 1 , 3 , 3 } ; LIS[6]=max\{LIS[2],LIS[3]+1,LIS[4]+1\}=max\{1,3,3\}; LIS[6]=max{LIS[2],LIS[3]+1,LIS[4]+1}=max{1,3,3};
所以观察到,以index=6结尾的LIS有两个选择:

最长上升子序列c语言,算法,数据结构,动态规划,c语言

这两种路径的长度都为3,均满足LIS的要求。

如果采用递归,那么返回的结果是LIS[i], 其中最长的LIS元素中一定包含index[i],这就要求需要多次调用递归,确保每个以i结尾的数组的LIS[i]都可以计算获得。

b.) 递归定义最优化问题的值(Recursively define the value of the optimal solution)

通过上面分析,我们可以清楚理解子问题的结构,而且每个子问题具有相互的独立性。接下来就需要用抽象的归纳法定义最优化的值,这些值得定义一定是递归结构,否则就无法产生子问题和求解子问题。如果用函数表示f(i)表示以arr[i]结尾(包含arr[i])的数组的最长递增子序列,那么就有:
f ( i ) = m a x { f ( j ) + 1 }   a s   l o n g   a s   j < i   a n d   a r r [ j ] < a r r [ i ] f(i)=max\{f(j)+1\}\ as\ long\ as\ j<i\ and\ arr[j]<arr[i] f(i)=max{f(j)+1} as long as j<i and arr[j]<arr[i]
表达式的意思是,扫描所有i之前的元素,如果某个位置j的取值小于f(i),那么f(i)=f(j)+1; 值得一提的是f(j)也是以元素j结尾(包含j处的元素)的f(j),这样子f(i)和f(j)就具有相同的解结构,只是问题规模大小不同,f(j)的规模比f(i)问题规模要小。

如果要对此问题进行递归求解,其中重要的举措之一便是,对以不同index结尾的数组进行多次递归,细心的读者可能会有疑问,是否需要对所有不同的index结尾的数组进行全部递归呢? 答案显而易见,不需要。我们不需要对已经在LIS路径上结尾的元素进行递归,只需要对未遍历的元素进行递归即可。

以下列数组进行说明:

Index 0 1 2 3 4 5 6 7
arr 10 9 2 5 3 7 114 101
visited true true true true true true false true
LIS 1 1 1 2 2 3 -1 4

以index=7结尾的元素的LIS[7]=4, 但在遍历过程中arr[6]>arr[7],导致在递归流程中,index=6演变成遍历的死角,所以需要继续以index=6结尾的元素在进行递归遍历,完成后本数组的每个元素的LIS的数值都完整求出。

Index 0 1 2 3 4 5 6 7
arr 10 9 2 5 3 7 114 101
visited true true true true true true true true
LIS 1 1 1 2 2 3 4 4

c) 计算最优化问题的值(Compute the value of the optimal solution)

接下来我们就需要计算最优化问题的值,本问题可以选择递归或者迭代两种方式之一,其中递归计算稍显复杂,没有迭代看起来简洁与直观。

让我们先从递归计算开始,深入理解LIS的基本算法。

在递归过程中,首先需要定义递归的截止条件,递归的截止条件实际上有两类:

  • f(i)当中的i==0的时候,LIS=1;

  • f(i)当前置比前置的任何值都要小,那么LIS=1,表格中的index=2的便是这类情况,遇到这类情况,也就是递归中判断条件没有任何一个成立,循环完成后,直接返回1这个具体的值

当递归完成后,需要对没有遍历的元素,设定其为最后一个元素(包括此元素),然后再进行递归遍历,直至所有元素都完成遍历为止,也即所有的visited[i]的值都等于1为止。所以需要对单个递归进行循环。

I. 头文件 (LIS_recursive.h)

/**
 * @file LIS_recursive.h
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-02-28
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#ifndef LIS_RECURSIVE_H
#define LIS_RECURSIVE_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
 * @brief Use recursive method to get longest increasing subsequence
 * 
 * @param arr Array list
 * @param n  Number of array list
 * @param s  Store the number of each elmenent
 * @return int Return longest increasing subsequence
 */
int lis_recursive(int *arr, int n, int *s, int *visited);

/**
 * @brief Use recursive method to get longest increasing subsequence
 *
 * @param arr Array list
 * @param n  Number of array list
 * @param s  Store the number of each elmenent
 * @return int Return longest increasing subsequence
 */
int lis_recursive_aux(int *arr, int n, int *s, int *visited);

int find_longest_increasing_subsequence(int *arr, int n, int *s,int *visited);
/**
 * @brief Find maximum number between m and n
 * 
 * @param m 
 * @param n 
 * @return int 
 */
int max(int m,int n);


#endif

II. 函数的实现

/**
 * @file LIS_recursive.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-02-28
 * 
 * @copyright Copyright (c) 2023
 * 
 */

#ifndef LIS_RECURSIVE_C
#define LIS_RECURSIVE_C
#include "LIS_recursive.h"

int lis_recursive(int *arr, int n, int *s, int *visited)
{
    int i;

    for(i=0;i<=n;i++)
    {
        *(s+i)=-1;
        *(visited+i)=0;
    }
    return find_longest_increasing_subsequence(arr,n,s,visited);
}

int lis_recursive_aux(int *arr, int n, int *s, int *visited)
{
    int i;
    if(s[n]!=-1)
    {
        return s[n];
    }

    if((n)==0)
    {
        s[n]=1;
        visited[n]=1;
    }
    else
    {
        s[n] = 1;
        visited[n] = 1;

        for (i = 0; i < n; i++) //int arr[] = {10, 9, 2, 5, 3, 7, 101, 18};
        {
            if (arr[i] < arr[n])
            {
                s[n] = max(s[n], lis_recursive_aux(arr, i, s,visited) + 1);
            }            
        }
    }

    return s[n];
}

int find_longest_increasing_subsequence(int *arr, int n, int *s, int *visited)
{
    int i;
    int max_value=1;

    for(i=n;i>=0;i--)
    {
        if(!visited[i])
        {
            s[i] = lis_recursive_aux(arr, i, s, visited); 
        }
        max_value=max(max_value,s[i]);
    }

    return max_value;
}

int max(int m, int n)
{
    return (m>n?m:n);
}

#endif

III. 测试函数

/**
 * @file LIS_recursive_main.h
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-02-28
 * 
 * @copyright Copyright (c) 2023
 * 
 */

#ifndef LIS_RECURSIVE_MAIN_C
#define LIS_RECURSIVE_MAIN_C
#include "LIS_recursive.c"
#define N 8
int main(void)
{
    int arr[] = {10, 9, 2, 5, 3, 7, 114, 101};
    int n;
    int s[N];
    int visited[N];
    int lis;
    n = N-1;
    lis=lis_recursive(arr,n,s,visited);
    printf("The longest increasing susbequence number is %d\n",lis);
    getchar();
    return EXIT_SUCCESS;
}


#endif

接下来继续讨论迭代形式的实现,有了递归作为基础,迭代本就是水到渠成的事情,递归过程中实际变化参数只有数组的下标, 很自然地就以dp[N]作为迭代的保存数组。

关键的是状态转移方程的确认,要确认dp[i]的状态,必须对前面i-1元素和当前的第i元素进行比较,那么就有状态转移方程:
d p [ i ] = m a x { d p [ i ] , d p [ j ] + 1 } ,   j < i   a n d   a r r [ i ] > a r r [ j ] dp[i]=max\{dp[i],dp[j]+1\},\ j<i\ and \ arr[i]>arr[j] dp[i]=max{dp[i],dp[j]+1}, j<i and arr[i]>arr[j]
状态转移方程的基准也很明确,对于单个元素,其LIS的值都等于1,也即,其自身是最长的LIS。基于上述理解,就很容易书写出来。
d p [ i ] = 1 , 0 < = i < n ; dp[i]=1, 0<=i<n; dp[i]=10<=i<n;
其核心代码实现:

/**
 * @file LIS_recursive.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-02-28
 * 
 * @copyright Copyright (c) 2023
 * 
 */

#ifndef LIS_BOTTOMUP_C
#define LIS_BOTTOMUP_C
#include "LIS_bottomup.h"

int lis_bottomup(int *arr, int n, int *dp)
{
    int i;
    int j;
    int max_value;

    //when there is only one element in the array, 
    //the longest increasing subsequence is 1
    for(i=0;i<=n;i++)
    {
        *(dp+i)=1;
    }

    //If the array is not empty, the max_value will be initialized as 1
    max_value=1;

    for(i=0;i<=n;i++)
    {
        for(j=0;j<i;j++)
        {
            if(arr[j]<arr[i])
            {
                dp[i]=max(dp[i],dp[j]+1);
            }
        }

        max_value=max(max_value,dp[i]);
    }

    return max_value;
}

int max(int m, int n)
{
    return (m>n?m:n);
}

#endif

在此不再赘述dp的运算的详细过程。

4. 小结

对于LIS问题,可以清晰的采用迭代实现,但是如果采用递归实现就需要多余的函数和判断,所以很多时候,如果能清晰的判断迭代的边界条件并能给出初始化的base,迭代也是一种最佳选择。如果涉及到很多子问题的剪枝,那么递归就比迭代效率更高,比如背包问题采用迭代,可以节省很多dp[N+1][Total_Weight+1]空间。

参考资料

  1. 《Introduction to algorithm 4ed, 4th edition》

  2. 300. 最长递增子序列 - 力扣(Leetcode), LeetCode 习题文章来源地址https://www.toymoban.com/news/detail-785907.html

到了这里,关于最长递增子序列(Longest Increasing Subsequence)-C语言实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 算法刷题Day 52 最长递增子序列+最长连续递增子序列+最长重复子数组

    我自己想出来的方法,时间复杂度应该是 O(n2) 滑动窗口 连续的话,可以考虑用滑动窗口 动态规划 贪心算法

    2024年02月14日
    浏览(40)
  • LeetCode | C++ 动态规划——300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

    300题目链接 dp 数组定义 dp[i] 表示 i 之前包括 i 的以 nums[i]结尾 的最长递增子序列的长度 需要包含nums[i]结尾,不然在做递增比较的时候,就没有意义了。 递推公式 位置 i 的最长递增子序列 等于 j 从 0 到 i - 1各个位置的最长递增子序列 + 1 的 最大值 if (nums[i] nums[j]) dp[i] = ma

    2024年02月16日
    浏览(38)
  • 最长递增子序列——力扣300

    2024年02月12日
    浏览(25)
  • 力扣300. 最长递增子序列

    思路: 假设 dp[i] 为前 i 个元素构成的最长递增子序列的个数,包含 nums[i]; 则 dp[i] 构成序列上一个元素 nums[j] 构成最长递增子序列 dp[j],则 dp[i] = dp[j] + 1; 如果动态取 j ∈ [0, i - 1],则选取其中最长递增子序列值中最大的,其值 + 1 来更新 dp[i] 的值;

    2024年02月04日
    浏览(31)
  • 57 最长递增子序列

    给你一个整数数组 nums ,找到其中 最长严格递增子序列的长度 。 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 示例 1: 输入: nums = [10,9,2,5,3,7,101,18] 输出:4 解释:最长递增子序列

    2024年02月07日
    浏览(28)
  • 300. 最长递增子序列

    给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如, [3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 示例 1: 示例 2: 示例 3: 提示: 1 = nums.length = 2500 -104 = nums[i] = 104

    2024年02月09日
    浏览(30)
  • 【Leecode】674. 最长连续递增序列

    Given an unsorted array of integers nums , return the length of the longest continuous increasing subsequence (i.e. subarray). The subsequence must be strictly increasing. A continuous increasing subsequence is defined by two indices l and r (l r) such that it is [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] and for each l = i r , nums[i] nums[i + 1] . E

    2024年02月07日
    浏览(34)
  • 动态规划算法 | 最长递增子序列

    通过查阅相关资料 发现动态规划问题一般就是求解最值问题 。这种方法在解决一些问题时应用比较多,比如求最长递增子序列等。 有部分人认为动态规划的核心就是:穷举。因为要求最值,肯定要把所有可行的答案穷举出来,然后在其中找最值。 首先,笔者认为动态规划中

    2024年02月06日
    浏览(41)
  • 673. 最长递增子序列的个数

    673. 最长递增子序列的个数 https://leetcode.cn/problems/number-of-longest-increasing-subsequence/description/

    2024年02月11日
    浏览(26)
  • 动态规划之最长递增子序列

    leetcode 300 最长递增子序列 1.定义dp数组:dp[i]表示以nums[i]结尾的最长递增子序列的长度。 2.定义递推公式 dp[i] = max(dp[j] + 1, dp[i]) 因为dp[j] + 1中的dp[j]并非是在前一个已经加1的dp[j]的基础之上再加上1。若从初始状态加1,而dp[i]永远保持的是最大的状态,则dp[j] + 1肯定要小一些。

    2024年01月23日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包