上机实践报告
一、目的
1.熟悉算法设计的基本思想
2.最长上升子序列
二、内容与设计思想
解决OJ上的练习题,实现输入n个数字,输出最长上升子序列长度的算法。
画图比较不同规模下的算法效率。
(加分项)设计程序,在练习题的基础上,输出所有最长上升子序列的方案。(自行设计数据进行测试与分析)
(大加分项)设计程序,求把任意数组,分离成上升(非降序)子序列子集的最少个数。
- 比如
[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)<<" ";
}
}
数据
ID | N | 第1次实验时间 | 2 | 3 | 4 | 5 | 平均时间/ms |
---|---|---|---|---|---|---|---|
1 | 1000 | 0.3198 | 0.3462 | 0.3528 | 0.3511 | 0.3198 | 0.33794 |
2 | 2000 | 0.9745 | 0.75 | 0.6698 | 0.791 | 0.7734 | 0.79174 |
3 | 3000 | 1.3744 | 1.3607 | 1.3845 | 1.3744 | 1.3744 | 1.37368 |
4 | 4000 | 1.8381 | 1.8373 | 1.8214 | 1.8286 | 1.838 | 1.83268 |
5 | 5000 | 2.2927 | 2.2847 | 2.2897 | 2.2976 | 2.2927 | 2.29148 |
6 | 6000 | 2.7881 | 2.7523 | 2.7548 | 2.7679 | 2.7881 | 2.77024 |
7 | 7000 | 3.2222 | 3.2194 | 3.2181 | 3.2 | 3.2222 | 3.21638 |
8 | 8000 | 3.6825 | 3.681 | 3.6841 | 3.7603 | 3.6825 | 3.69808 |
9 | 9000 | 4.1446 | 4.2036 | 4.1446 | 4.1589 | 4.1335 | 4.15704 |
10 | 10000 | 4.625 | 4.6349 | 4.5972 | 4.621 | 4.635 | 4.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倒不难,但一开始的下标分类我想了很久,最后还是问的同学,其实与第一问相似,要是该数大于储存数组最后一个数,则它单独分为一组,若是小于,则二分查找到比它大的第一个数,将它的下标与这个数分到同一组。
文档信息
- 本文作者:Liu Jiafan
- 本文链接:https://jiapang777777.github.io//2023/04/01/%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)