作者:ke天天_809 | 来源:互联网 | 2024-11-07 19:49
在探讨P1923问题时,我们发现手写的快速排序在最后两个测试用例中出现了超时现象,这在意料之中,因为该题目实际上要求的是时间复杂度为O(n)的算法。进一步研究题解后,发现有选手使用STL中的`nth_element`函数成功通过了所有测试点。本文将详细分析这一现象,并提出相应的优化策略。
文章目录
- 一、手写快排最后两个测试数据超时了,预料之中的事情,因为这个题目考的应该就是`O(n)`的算法
- 二、看题解有人用`STL`的`nth_element`过掉本题,我直接略过这种解法
- 三、快读+快速排序(手写)+`-O2优化` (三者缺一不可)可以过掉此题,但`O(nlogn)`的算法不是预期的解法
- 四、`vector` + `O(n)`的算法有些时候会超时
- 五、换成数组 + `O(n)`的算法,通过了,而且时间很好看
- 六、后记,`O(n)`的算法还需要好好研究,~~我自己没有看懂,自己其实一开始尝试写`O(n)`的算法了,但是总是出错,我也不知道为什么。而且对于函数中的while循环和递归函数,我都理不清楚出口的样子。先背过吧。~~ 已经弄明白了,讲解如下:
- 七、教训
一、手写快排最后两个测试数据超时了,预料之中的事情,因为这个题目考的应该就是O(n)
的算法
二、看题解有人用STL
的nth_element
过掉本题,我直接略过这种解法
三、快读+快速排序(手写)+-O2优化
(三者缺一不可)可以过掉此题,但O(nlogn)
的算法不是预期的解法
代码如下:
#include
#include
#include
using namespace std;int nums[5000005];void qsort(int left, int right){int x &#61; left, y &#61; right, mid &#61; nums[(left&#43;right)>>1];while(x<&#61;y){while(nums[x]<mid)x&#43;&#43;;while(mid<nums[y])y--;if(x<&#61;y){int tmp &#61; nums[x];nums[x] &#61; nums[y];nums[y] &#61; tmp;x&#43;&#43;;y--;}}if(left<y)qsort(left, y);if(x<right)qsort(x, right);
}inline int read(){int x&#61;0,f&#61;1;char c&#61;getchar();while(c<&#39;0&#39;||c>&#39;9&#39;){if(c&#61;&#61;&#39;-&#39;)f&#61;-1;c&#61;getchar();}while(c>&#61;&#39;0&#39;&&c<&#61;&#39;9&#39;){x&#61;(x<<1)&#43;(x<<3)&#43;(c^48);c&#61;getchar();} return x*f;
}int main(){int cnt, k;cnt &#61; read();k &#61; read();if(k>cnt-1)return -1;for(int i&#61;0;i<cnt;i&#43;&#43;){nums[i] &#61; read();}qsort(0, cnt-1);cout<<nums[k]<<endl;return 0;
}
四、vector
&#43; O(n)
的算法有些时候会超时
说有些时候&#xff0c;是因为&#xff0c;运气好的时候&#xff0c;第五组测试数据就900多ms&#xff0c;刚刚好不会超时&#xff0c;但是有时候运气差&#xff08;我第二次提交这个代码就遇到了&#xff0c;由此看概率不低&#xff09;&#xff0c;就会超过1s&#xff0c;然后TLE
代码如下&#xff1a;
#include
#include
#include
using namespace std;void kthsmall(vector<int>& nums, int left, int right, int k, int* res){int x &#61; left, y &#61; right, mid &#61; nums[(x&#43;y)>>1];while(x<&#61;y){while(nums[x]<mid)x&#43;&#43;;while(mid<nums[y])y--;if(x<&#61;y){int tmp &#61; nums[x]; nums[x] &#61; nums[y]; nums[y] &#61; tmp;x&#43;&#43;;y--;}}if(k<&#61;y)kthsmall(nums, left, y, k, res);else if(x<&#61;k)kthsmall(nums, x, right, k, res);else{*res &#61; y&#43;1;return;}
}int main(){int cnt, k;cin>>cnt>>k;vector<int> nums;for(int i&#61;0;i<cnt;i&#43;&#43;){int tmp;scanf("%d", &tmp);nums.push_back(tmp);}int res &#61; 0;kthsmall(nums, 0, nums.size()-1, k, &res);cout<<nums[res]<<endl;return 0;
}
超时详情
五、换成数组 &#43; O(n)
的算法&#xff0c;通过了&#xff0c;而且时间很好看
代码如下&#xff1a;
#include
#include
#include
using namespace std;int nums[5000005];void kthsmall(int left, int right, int k, int* res){int x &#61; left, y &#61; right, mid &#61; nums[(x&#43;y)>>1];while(x<&#61;y){while(nums[x]<mid)x&#43;&#43;;while(mid<nums[y])y--;if(x<&#61;y){int tmp &#61; nums[x]; nums[x] &#61; nums[y]; nums[y] &#61; tmp;x&#43;&#43;;y--;}}if(k<&#61;y)kthsmall(left, y, k, res);else if(x<&#61;k)kthsmall(x, right, k, res);else{*res &#61; y&#43;1;return;}
}int main(){int cnt, k;cin>>cnt>>k;for(int i&#61;0;i<cnt;i&#43;&#43;){scanf("%d", &(nums[i]));}int res &#61; 0;kthsmall(0, cnt-1, k, &res);cout<<nums[res]<<endl;return 0;
}
好看的时间&#xff08;最多600多ms&#xff0c;十分nice&#xff09;
六、后记&#xff0c;O(n)
的算法还需要好好研究&#xff0c;我自己没有看懂&#xff0c;自己其实一开始尝试写O(n)
的算法了&#xff0c;但是总是出错&#xff0c;我也不知道为什么。而且对于函数中的while循环和递归函数&#xff0c;我都理不清楚出口的样子。先背过吧。 已经弄明白了&#xff0c;讲解如下&#xff1a;
2020年08月21日&#xff0c;通过循环不变量&#xff08;loop invariant&#xff09;想明白这个过程了。
while(x<&#61;y){while(nums[x]<mid)x&#43;&#43;;while(mid<nums[y])y--;if(x<&#61;y){int tmp &#61; nums[x]; nums[x] &#61; nums[y]; nums[y] &#61; tmp;x&#43;&#43;;y--;}}
这个while
循环&#xff0c;最终的跳出循环时的条件是(y&#xff0c;并且保证了nums[left]<&#61;nums[y]<&#61;nums[x]<&#61;nums[right]
&#xff0c;若y
和x
之间有空隙&#xff0c;则空隙中的值作为nums
数组索引下标所对应的所有nums
中的值都是相等的&#xff0c;这是while
循环能够保证的。
if(k<&#61;y)kthsmall(left, y, k, res);else if(x<&#61;k)kthsmall(x, right, k, res);else{*res &#61; y&#43;1;return;}
这段代码讨论了k
落在三个区间[left, y]、(y, x)、[x, right]
的情况。对于第一种和第三种情况不多赘述。第二种情况&#xff0c;由于(y, x)
中的值作为nums
数组索引下标所对应的nums
中的值都是相等的&#xff0c;因此*res &#61; y&#43;1;
与*res &#61; x-1;
都是合适的&#xff0c;我在OJ上分别提交了这两个写法的代码&#xff0c;均AC了。
七、教训
- 测试数据很猛的时候&#xff08;
5000000
的数据O(n)
的复杂度&#xff0c;时间要求1s
&#xff09;&#xff0c;尽量不要用vector
&#xff0c;不但耗时&#xff0c;而且占用存储空间极大&#xff08;5000000
的数据O(n)
的复杂度&#xff0c;大概30多M
的空间&#xff09; - 对于大数据的读取&#xff0c;万不得已用快读&#xff0c;其次用
scanf
&#xff0c;其实平常scanf
就够用了&#xff0c;最好不要用cin
int
数组开到5000000
好像没啥大问题&#xff0c;再大了就不清楚了。