前言
OJ测试中最烦人的结果莫过于TLE(Time Limit Exceed 超时)和MLE(Mempry Limit Exceed超内存)了,在递归和搜索题里面看见这两货就烦。
目录
前言
时间复杂度
时间复杂度概念
时间复杂度的表示法
时间复杂度OJ测试要求
时间复杂度例举
剪枝
1.可行性剪枝
2.最优性剪枝
3.记忆化搜索
4.搜索顺序剪枝
时间复杂度
时间复杂度概念
C++时间复杂度是指算法在处理数据时所需要的计算时间。在C++中,常见的时间复杂度包括常数级别、对数级别、线性级别、平方级别、立方级别等。其中,常数级别是最快的,而立方级别是最慢的。具体来说:
常数级别:只需要一次计算即可完成,如赋值操作等。
对数级别:需要不断缩小问题规模,通常用于二分查找等算法。
线性级别:随着问题规模增加,所需计算时间也呈线性增长,如遍历一个数组等。
平方级别:通常是嵌套循环的形式,所需计算时间随着问题规模的增加呈平方级别增长。
立方级别:通常是三层嵌套循环的形式,所需计算时间随着问题规模的增加呈立方级别增长。
当然,在实际应用中,我们需要选择合适的算法来降低时间复杂度。例如,可以使用哈希表来加速查找操作,或者使用动态规划来减少重复计算等。在竞赛中,一般算机一秒能执行x次汁算。
时间复杂度的表示法
对于表示时间复杂度,我们可以使用O()表示法来表示时间复杂度,O(n)表示程序运行n次汁算。判断时,只关注最高次项。但执行的数量不是不确定的都填1
比如下面的例子:
O(c) = O(1) (c表示常数)
O(2n+1) = O(n)
O(n²+n+1) = O(n²)
O(3n³+1) = O(n³)
注意:符号 O(c)表示算法或函数具有恒定的时间复杂度,这意味着无论输入大小如何,它都需要固定的时间来执行。
时间复杂度OJ测试要求
为了避免TLE,OJ中要减少时间复杂度,一般来讲(1000ms) n的数据范围大致如下
时间复杂度 | n算法数据范围 |
| |
| |
| |
时间复杂度例举
O(1) 一般的输入输出,变量的定义 ,时间复杂度都为O(1)
printf("Hello,Wold");
int a=3,b=20;
return 0; //时间复杂度为O(1)
O(n) 一般的循环 ,时间复杂度都为O(n)
O(n^2) 一般的二重循环 ,时间复杂度都为O(n^2)
int n=123456;
for(int i=0;i<=n;i++){
for(int j=0;j<=i;j++)
cout<<"# ";
cout<<endl;
}
return 0; //时间复杂度为O(n^2)
O(n^3) 一般的三重循环 ,时间复杂度都为O(n^3)
O(2^n) 指数阶,如递归实现的斐波那契数列 ,时间复杂度都为O(2^n)
O(log n) 对数阶,如二分查找 ,时间复杂度都为O(log n)
O(n log n) 线性对数阶,例如快速排序,时间复杂度都为O(log n)
剪枝
搜索是OI之路上,人人必会的强大算法。自古便有名言:“暴力进省队”(实际上,很多考试你打好所有暴力就可以拿到不错的分数)。在考场上,搜索常常是与正解的对拍板子(当然有时搜索就是正解),且一般搜索都会有20~30分。而想要写好搜索,剪枝必不可少(有时出题人不会给纯暴力分)。
what's 剪枝?
常用的搜索有Dfs和Bfs。Bfs的剪枝通常就是判重,因为一般Bfs寻找的是步数最少,重复的话必定不会在之前的情况前产生最优解。深搜,它的进程近似一颗树(通常叫Dfs树)。而剪枝就是一种生动的比喻:把不会产生答案的,或不必要的枝条“剪掉”。剪枝的关键就在于剪枝的判断:什么枝该剪,什么枝不该剪,在什么地方减。常用的剪枝有:可行性剪枝、最优性剪枝、记忆化搜索、搜索顺序剪枝。
1.可行性剪枝
就是将搜索树里面不能走的或者走了会走到死胡同的枝条进行减去。
在很多情况下,并不是搜索树中的所有枝条都能通向我们需要的结果,很多的枝条实际上只是一些死胡同。如果我们能够在刚刚进入这样的死胡同的时候,就能够判断出来并立即剪枝,程序的效率往往会得到提高。
在程序上就是:如果当前条件不合法就不再继续搜索,直接return。这是非常好理解的剪枝,搜索初学者都能轻松地掌握,而且也很好想。一般的搜索都会加上。
2.最优性剪枝
一般要自己构造一个估值函数,由该估值函数计算上界和下界,最优性剪枝又是如何进行的呢?当我们处在搜索树的枝条上时,可以通过某种方法估算出该枝条上的所有解的评价函数的上界,即所谓估价函数。显然,大于当前保存的优度的下界,是该枝条上存在最优解的必要条件,否则就一定可以剪枝。所以,最优性剪枝也可以称为“上下界剪枝”。同时,我们也可以看到,最优性剪枝的核心问题就是估价函数的建立。一般实现:在搜索取和最大值时,如果后面的全部取最大仍然不比当前答案大就可以返回。在搜和最小时同理,可以预处理后缀最大/最小和进行快速查询。
3.记忆化搜索
记忆化搜索其实很像动态规划(DP)。
它的关键是:如果对于相同情况下必定答案相同,就可以把这个情况的答案值存储下来,以后再次搜索到这种情况时就可以直接调用。还有就是不能搜出环来,不能互相依赖。
4.搜索顺序剪枝
在一些迷宫题,网格题,或者其他搜索中可以贪心的题,搜索顺序显得十分重要。我经常听见有人说:“从左边搜会TLE,从右边搜就AC了”之类的语句。其实在迷宫、网格类的题目中,以左上->右下为例,右下左上就明显比左上右下优秀。在一些推断搜索题中,从已知信息最多的地方开始搜索显然更加优秀。在一些题中,先搜某个值大的,再搜某个值小的(比如树的度数,产生答案的预计),速度明显会比乱搜更快。
搜索的复杂度明显讲不清,这种剪枝自然是能加就加。