最长上升子序列

2023/04/01 程序设计 共 4603 字,约 14 分钟

上机实践报告

一、目的

1.熟悉算法设计的基本思想

2.最长上升子序列

二、内容与设计思想

  1. 解决OJ上的练习题,实现输入n个数字,输出最长上升子序列长度的算法。

  2. 画图比较不同规模下的算法效率。

  3. (加分项)设计程序,在练习题的基础上,输出所有最长上升子序列的方案。(自行设计数据进行测试与分析)

  4. (大加分项)设计程序,求把任意数组,分离成上升(非降序)子序列子集的最少个数。

    • 比如[1,2,3,4,5,1],可以分离成{[2,3,4,5], [1, 1]} 或者 {[1,2,3,4,5], [1]}最小个数是2[2,2,1,1] => {[2, 2], [1, 1]} => 2

三、使用环境

推荐使用C/C++集成编译环境。

练习地址:[oj《算法设计与分析》第六次实验课](http://47.100.233.213/contest/92)(密码40088860)

截止时间:2021-11-14 23:33

提交地址:算法设计与分析-实践报告6 (jianguoyun.com)

不建议随意改变本文档以上部分的结构,以下部分可以按需扩充。但不要缺少“实验过程”和“总结”。

四、实验过程

1.最长上升子序列长度算法

源代码

#include <bits/stdc++.h>
using namespace std;
class solution
{
public:
    int binary_search(int num, vector<int> &a);
    int LIS_length(vector<int> &nums);
};
int solution::binary_search(int num, vector<int> &a) //二分查找
{
    int low = 0, high = a.size() - 1, mid;
    while (low <= high)
    {
        mid = low + (high - low) / 2;
        if (a[mid] == num)
            return mid;
        else if (a[mid] > num)
            high = mid - 1;
        else if (a[mid] < num)
            low = mid + 1;
    }
    return low;
}

int solution::LIS_length(vector<int> &nums)
{
    vector<int> st;
    if (nums.size() == 0)
        return 0;
    st.push_back(nums[0]);
    for (int i = 1; i < nums.size(); i++)
    {
        if (nums[i] > st.back()) //如果第i个元素是大于st中最后一个元素的则直接加进去
        {
            st.push_back(nums[i]);
        }
        else
        {
            int pos = binary_search(nums[i], st); //若第i个元素是小于栈顶的,直接进行替换
            st[pos] = nums[i];
        }
    }
    return st.size();
}

int main()
{
    solution a;
    int n;
    cin >> n;
    vector<int> s;
    for (int i = 0; i < n; i++)
    {
        s.clear();
        int j;
        cin >> j;
        for (int t = 0; t < j; t++)
        {
            int m;
            cin >> m;
            s.push_back(m);
        }
        cout << a.LIS_length(s)<<" ";
    }
}

数据

IDN第1次实验时间2345平均时间/ms
110000.31980.34620.35280.35110.31980.33794
220000.97450.750.66980.7910.77340.79174
330001.37441.36071.38451.37441.37441.37368
440001.83811.83731.82141.82861.8381.83268
550002.29272.28472.28972.29762.29272.29148
660002.78812.75232.75482.76792.78812.77024
770003.22223.21943.21813.23.22223.21638
880003.68253.6813.68413.76033.68253.69808
990004.14464.20364.14464.15894.13354.15704
10100004.6254.63494.59724.6214.6354.62262

2.输出所有最长上升子序列

源代码:

#include<bits/stdc++.h>
using namespace std;
class solution
{
    public:
    void  dfs(vector<int> &array, vector<vector<int> >  &s1,vector<vector<int> > &s2,vector<int> &find);
    int binary_search(int num, vector<int> &a);
    void  print_all(vector<int> &array,vector<vector<int> > &s2);
    void  find_allLIS(vector<int> &array);
    
};
int solution::binary_search(int num, vector<int> &a) //二分查找
{
    int low = 0, high = a.size() - 1, mid;
    while (low <= high)
    {
        mid = low + (high - low) / 2;
        if (a[mid] == num)
            return mid;
        else if (a[mid] > num)
            high = mid - 1;
        else if (a[mid] < num)
            low = mid + 1;
    }
    return low;
}
void  solution::dfs(vector<int> &array, vector<vector<int> >  &s1,vector<vector<int> > &s2,vector<int> &find) 
{
	//find即是每一次搜索最长路径的下标保存数组
  int lmax = find.size();
  //s1的长度即是最大长度
	if (s1.size() == lmax) { 
		//搜索数组长度等于最大序列长度时,添加该路径
		s2.push_back(find);
		return;
	}
	for (auto i : s1[lmax]) {
		//添加到搜索队列的条件
		if (find.empty() || find.back() < i && array[find.back()]<array[i]) 
    {
			find.push_back(i);
			dfs(array, s1, s2, find);
			find.pop_back();
		}
	}
}
void  solution::print_all(vector<int> &array,vector<vector<int> > &s2) 
{
	for (auto r : s2) 
  {
		cout << "LIS:";
		for (auto i : r) {
			cout << array[i] << " ";
		}
		cout << endl;
	}
}
void  solution::find_allLIS(vector<int> &array) 
{
	vector<int>  arr;
  vector<int> a;
	vector<vector<int> >  s1; //对该数组所有元素进行一个下标的分类
  int n = array.size();
	for (int i = 0; i < n;i++) 
  {
		int c = array[i];
		if (arr.empty() || arr.back() < c) 
    {
			arr.push_back(c);
      //该类下标传进s1中保存起来,记录新增加元素的下标
      a.push_back(i);
			s1.push_back(a); 
		}
		else //要是比arr中最后一个元素小,则这个新增加的元素下标分到与他同等级的那一类
   {
      int b = binary_search(c,arr);
      s1[b].push_back(i);
		}
    a.clear();
	}
	vector<vector<int> >  s2;//s2就是用来记录所有符合要求的路径
	vector<int>  find;//只是每一次过程当中会用到的数组
	dfs(array, s1, s2, find);
	print_all(array, s2);
}

int main()
{
    solution s;
    vector<int> array;
    int n;
    cin>>n;
    for(int i=0; i<n; i++)
    {
        int a;
        cin>>a;
        array.push_back(a);
    }
    s.find_allLIS(array);
}

五、总结

关于实验一——找最长上升子序列的长度

for(i=1;i<n;i++)  
10     {  
11         b[i]=1;//b[i]最小值为1
12         for(j=0;j<i;j++)  
13             if(a[i]>a[j]) b[i]=max(b[i],b[j]+1);
14     }  

一开始我是用的动态规划做,复杂度为O(n^2)。相当于用数组b记录下对于每一个元素该位置而言的最长子序列长度,然后再从中找最大的,两个循环。但是这样复杂度还是比较高的,于是我们对它进行优化,我们可以模拟栈来记录数据,每次取栈顶元素与读到的元素做比较,如果大于,则将它入栈;如果小于,则二分查找栈中比它大的第一个数,并替换它。最后得到的长度即为最长子序列的大小,为什么要替换是因为,虽然长度未改变,但是它的‘潜力’却变大了,因为我们是要找最长的。用这个方法做下来的复杂度为O(nlgn),这样就优化了算法

关于实验二——输出所有最长子序列上升的方案

看到寻找所有最长子序列时,第一想法就是用DFS,关于这道题的思路也和上学期老师让我们写过的迷宫问题很像,我用s1数组来记录下对原数组的下标进行分类后的数组,比如我的输入是:1 3 5 4 7 6,那么s1为:{0},{1},{2,3},{4,5} , 然后我从原数组的一开始开始dfs(0),然后从0开始dfs(1),然后从1开始dfs(2)或dfs(3),从2开始dfs(4,5)或者从3开始dfs(4,5);将所有满足要求的放入s2中,最后输出s2中的所有数组。dfs倒不难,但一开始的下标分类我想了很久,最后还是问的同学,其实与第一问相似,要是该数大于储存数组最后一个数,则它单独分为一组,若是小于,则二分查找到比它大的第一个数,将它的下标与这个数分到同一组。

文档信息

Search

    Table of Contents