插入排序的优化非希尔【不靠谱地讲可以优化到O(nlogn)】 USACO 丑数
首先我们先介绍一下普通的插排,就是我们现在一般写的那种,效率是O(n^2)的。
普通的插排基于的思想就是找位置,然后插入进去,其他在它后面的元素全部后移,下面是普通插排的代码:
#include<iostream>
#include<fstream>
#include<stdio.h>
using namespace std;
int a[];
int p[]; int main(){
ios::sync_with_stdio(false);
int n;
cin>>n;
for(int i=;i<=n;i++){
cin>>a[i];
}
int len = ;
p[len] = a[];
for(int i=;i<=n;i++){ //插入第i个元素
int k = a[i];
int j;
for(j=len;j>=;j--){
if(k < p[j]){
p[j+] = p[j];
}else
break;
}
p[j+] = k;
len++;
}
for(int i=;i<=n;i++)
cout<<p[i]<<" ";
return ;
}
可以看出,这个代码的复杂度应该是T((1+n)*n/2)=O(n^2)的,让我们仔细分析到底时间费在哪里。
1.查找过程太费时间,仔细观察我们就可以发现,查找它应当插入元素的位置的时间是O(n)的,我们可以想办法优化成O(log2n),没错,二分查找,于是我们写出了下面这份代码。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<algorithm>
using namespace std;
int a[];
vector <int> vec; int main(){
ios::sync_with_stdio(false);
int n;
cin >> n;
for(int i =;i<=n;i++)
cin>>a[i];
vec.push_back(a[]);
for(int i=;i<=n;i++){
int k=a[i];
int j=lower_bound(vec.begin(),vec.end(),k)-vec.begin();
vec.insert(vec.begin()+j,k); //这是O(n)的
}
for(int i=;i<n;i++){
cout<<vec[i]<<" ";
}
return ;
}
显然,这份代码的复杂度,应该是T(n*(log2n+n))=O(n^2)的,但是有一点好的,就是不会被某些专门卡插排的数据卡。
我们再分析一下另一个耗时间的地方。
2.将所有元素前移的时间的上界是O(n)的,我们也要想办法优化到O(logn)。若我们只针对这一点优化,那么我们可以想到一种比O(logn)更快的数据结构来优化这一点,链表。
如果我们用链表来储存,我们完全没必要将元素前移,只要连接起来,是O(1)的。不难写出下面这份代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
struct node{
int data;
node* next;
};
node *start=new node,*end = new node;
int a[]; int main(){
end->data = -;
ios::sync_with_stdio(false);
int n;
cin >> n;
for(int i=;i<=n;i++)
cin>>a[i];
start->next = end;
for(int i=;i<=n;i++){
int k=a[i];
node *p = start;
while(p->next!=end&&p->next->data < k)
p = p->next;
node *q = p->next;
node *now = new node;
now->data = k;
now->next = q;
p->next = now;
}
node *p =start->next;
while(p!=end){
cout<<p->data<<" ";
p = p->next;
}
return ;
}
显然,这份代码也是O(n^2)的,慢在哪了?又是查找。
所以我们现在要做的事,就是把二分融合在链表里面,这就设下了一个大难关,但是,对数级的优化又启发了我们,我们必须在有限的次数(可以预知)内筛掉一半以上的数。
我们不妨考虑一个简化版的问题:给定n个有序的元素,现在要插入1个元素,用链表实现,效率是O(logn)怎么搞。
对这个问题,我有两个方法:
法1:在读入的时候预处理每个点到另一个点的中点的位置,空间复杂度高达O(n^2),铁定MLE。我们不得不另寻他法
法2:我们不妨分层存储,比如对于一个8个元素的链表,我们可以设计出以下数据结构:
显然,层数是log2n层的,每一层的元素个数都是上一层的1/2,那么我们的查找显然是O(logn)的,我们从最顶上一层开始找,如果下一个不是我想要的,那么就排掉了一半,往下面走,以此类推。
这样的空间复杂度是O(n)的,显然第一层是有n个元素,此外每一层的元素个数都是上一层的1/2,回忆二叉树的知识,若我们在最上面再补一层,那么就有n+n-1=2n-1,那么再减去刚才补上的一层,就是2n-2个元素,所以空间复杂度是O(n)的。
推广这个问题,我们可以发现,这样子只能对某个固定的链表使用,而不能动态地更改,于是我们就想到了随机数。对每个元素,我们有1/2的几率让它成为上面一层的元素,这样的话每层元素的期望也就跟这个差不多,可以证明,这样做插入的复杂度是O(logn)<-递归log2n层,查找的复杂度也是O(logn),那么插入排序的复杂度就变成了O(nlogn),下面是代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cstring>
using namespace std;
struct node{
int data;
int level; //所在层数
node* under; //下一层的相同结点
node* next;
};
struct llist{ //level of list
int level;
node* start;
llist(){
start = new node;
start->data = -;
}
};
int siz[]; //定义为一个天文数字
int a[];
int log2(int);
node* end = new node;
int top = ; //表示层数
llist le[];
int want,n;
int insert(int now,int lev,node* place,node *last = NULL){
node *f = place;
while(f->next->data < now && f->next!=end)
f = f->next;
if(lev != ){
int pd = insert(now,lev-,f->under,f);
if(pd == true){
int trid = rand()%;
if(trid == &&lev<want){
node* p = f->next;
node *q = new node;
q->data = p->data;
q->level = p->level+;
q->under = p;
if(last!=NULL){
q->next = last->next;
last->next = q;
}else{
q->next = end;
le[lev+].start->next = q;
}
if(siz[lev+] == )
top = lev+;
siz[lev+] = ;
return true;
}
}else
return false;
}else{
siz[]++;
node *p = new node;
p->level = ;
p->data = now;
p->next = f->next;
f->next = p; //移花接木
int trid = rand()%;
if(trid == &&lev<want){
node *q = new node;
q->data = p->data;
q->level = p->level+;
q->under = p;
if(last!=NULL){
q->next = last->next;
last->next = q;
}else{
q->next = end;
le[lev+].start->next = q;
}
if(siz[lev+] == )
top = lev+;
siz[lev+]++;
return ;
}else
return ;
}
} int main(){
// freopen("sort.in","r",stdin);
// freopen("sort.out","w",stdout);
srand(time(NULL));
end->data = ;
end->level = -; //确认身份
ios::sync_with_stdio(false);
cin >> n;
want = log2(n);
for(int i=;i<=want;i++){
le[i].level = i;
le[i].start->level = i;
le[i].start->next = end;
if(i!=){
le[i].start->under = le[i-].start;
}
}
for(int i=;i<=n;i++)
cin>>a[i];
node* q = new node;
q->data = a[];
q->next = end;
le[].start->next = q; //为第一层加上一个结点
siz[]++;
for(int i=;i<=n;i++){
insert(a[i],top,le[top].start); //插 ♂入 a[i]
}
node *p = le[].start;
p = p->next;
while(p!=end){
cout<<p->data<<" ";
p = p->next;
}
return ;
} int log2(int n){
int val = ,k = ;
while(val* < n){
k++;
val*=;
}
return k+;
}
(前面的那份代码是错误的,现在是更正后的!!)
多美妙的代码啊。
下面是它与其它几种排序方法在时间上的比较:
首先是对于测试点的说明:
对于测试点1:n=100000,专门卡插入排序的测试点,因为是从小到大排序所以自然是从大到小的数据喽。
对于测试点2:n=1000,数据随机。基本上都能过
对于测试点3:n=10000,数据随机。卡卡常还是能过
对于测试点4:n=50000,数据随机。理论上分块能过,人懒就没写分块。
对于测试点5:n=100000,数据随机。只有O(nlogn)能过
STL都跑得快,最慢的跑了0.09秒。heap_sort最慢的0.24秒。插排优化最慢的0.43秒(常数略大)。
普通插排最慢的32.18秒,被第一个点卡了。链表插排没过最后一个点(常数太大)。
还有一个我删了的是二分优化的普通插排(数组实现O(n^2)),最慢的跑了3秒。
下面是一道实战题,丑数(USACOtraining 第三章)
对于一给定的素数集合 S = {p1, p2, ..., pK},
来考虑那些质因数全部属于 S 的数的集合.这个集合包括,p1, p1p2, p1p1, 和 p1p2p3 (还有其它).
这是个对于一个输入的 S 的丑数集合.
注意:我们不认为 1 是一个丑数.
你的工作是对于输入的集合 S 去寻找集合中的第 N 个丑数.longint(signed 32-bit)对于程序是足
够的.
程序名: humble
读入说明:
第 1 行: 二个被空间分开的整数:K 和 N , 1<= K<=100 , 1<= N<=100,000.
第 2 行: K 个被空间分开的整数:集合 S 的元素
样例读入 (文件名 humble.in)
4 19
2 3 5 7
输出说明
单独的一行,写上对于输入的 S 的第 N 个丑数.
样例输出 (文件名 humble.out)
27
对于这道题,我们容易想到的一种做法是用堆来做,代码我就不贴了(其实就是我删了懒得重打了)。容易证明,这样做的复杂度是O(2^(k)*k*n)的,空间也是会爆掉的,所以这样做不行。
我们试着改变它,因为它是求第n小,所以假设一个由丑数构成的序列大于100000个元素,那么大于100,000的元素必然不可能是我想要的丑数(借用刘汝佳的一句话:想一想,为什么)。那么我们可以构建一个ans链表,来储存目前枚举出的每一个丑数,初始状态,ans数组仅有S集合中的元素,然后我们扫描一遍,求第n小,我们可以每次把当前第一小的数字出链表,同时加入它与它后面的数的乘积入链表(想一想为什么不加入它和前面的乘积,或者它和后面好几个的乘积),这个的复杂度显然是O(k*logn)的,如果这个链表大小超过了n,那么就砍尾巴喽。那么这个的复杂度就是O(n*k*logn),卡卡常还是能过的嘛。(毕竟是USACO,没像NOIP那样为难oier)。代码的话,我不会打!!!!毕竟这么晚了,改天有空了我会把代码补上。放心这次不会和前面那个三分,莫比乌斯一样烂尾的!
好了,代码写完了,其实这份代码有个可以优化的地方,大概可以优化O(nk)吧,就是对于某个数,从哪开始计算后面的这里,可以考虑用数组储存,而不是像我的代码一样每次都算出来。注意!这份代码因为写得烂(我这蒟蒻就这水平),会被卡常,所以要再优化优化,优化很简单,每次出链表,maxlen-1,可以优化到最后一个点不超时,请自行优化。
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cstring>
#include<cstring>
#include<vector>
using namespace std;
struct node{
long long data;
node *next,*last;
node* under;
node* on;
node(){
under =NULL;
on = NULL;
}
};
struct llist{
long long level;
node *start;
llist(){
start = new node;
}
}le[]; //1<<200是个天文数字
long long siz[];
long long p[],top=,want;
long long log2(long long);
long long insert(long long,long long,node*,node*);
node* end=new node;
node* point = end; //指向第一层最后一个
long long del(node*); int main(){
srand(time(NULL));
end->data = (1ll<<62ll); //初始化end为一个很大的数,表明它的身份
ios::sync_with_stdio(false);
long long n,k;
cin>>n>>k;
want=log2(k); //期望的层数,最大不能超过它(其实超过一点点可以加速,懒得那么写)
for(long long I=;I<=n;I++)
cin>>p[I];
make_heap(p+,p+n+);
sort_heap(p+,p+n+);
for(long long i=;i<=want;i++){
le[i].level = i;
le[i].start->next = end;
if(i!=){
le[i].start->under = le[i-].start;
}
if(i!=want)
le[i].start->on = le[i+].start;
}
for(long long i=;i<=n;i++) //插入初始结点
insert(p[i],top,le[top].start,NULL);
point =le[].start;
while(point->next!=end)
point = point->next;
long long maxlen = max(k,n);
for(long long i=;i<k;i++){ //O(n)
long long num=le[].start->next->data;
del(le[].start->next);
long long Table[];
memset(Table,,sizeof(Table));
long long relation;
for(long long j=;j<=n;j++) //O(k)
if(num%p[j]==)
relation = j;
for(long long j=relation;j<=n;j++){ //O(k)
Table[j-relation+] = num*p[j];
}
for(long long j=;j<=(n-relation+);j++){ //O(k)
if(Table[j]>point->data&&siz[]>=maxlen)
break;
insert(Table[j],top,le[top].start,NULL);//O(logn)
if(siz[]>maxlen){ //删除明显不可能是答案的结点
node* key = point->last;
long long level_now = ;
while(point!=NULL){
point->last->next = end;
node *p=point->on;
delete point;
point=p;
siz[level_now++]--;
}
point = key;
}
}
}
cout<<le[].start->next->data;
return ;
}
long long del(node* place){ //删去元素。
long long levelnow = ;
while(place!=NULL){
place->last->next = place->next;
place->next->last = place->last;
node *p=place->on;
place=p;
siz[levelnow++]--;
}
} long long insert(long long now,long long lev,node* place,node *last){
node *f = place;
while(f->next->data < now && f->next!=end)
f = f->next;
if(lev != ){ //如果不是第一行就继续往下走
long long pd = insert(now,lev-,f->under,f);
if(pd == true){ //这个链表的性质,必须下一层有上面一层才会有
long long trid = rand()%;
if(trid == &&lev<want){ //如果随机中了,就给上层加结点
node* p = f->next;
node *q = new node;
q->data = p->data;
q->under = p;
p->on = q;
if(last!=NULL){
q->next = last->next;
q->next->last = q;
last->next = q;
q->last = last;
}else{
q->next = end;
le[lev+].start->next = q;
q->last = le[lev+].start;
}
if(siz[lev+] == )
top = lev+;
siz[lev+]++;
return true;
}else
return false;
}else
return false;
}else{
siz[]++;
node *p = new node; //创建p结点连接起来
p->data = now;
p->next = f->next;
p->last=f;
f->next->last = p;
f->next = p;if(p->next==end)point=p;
long long trid = rand()%;
if(trid == &&lev<want){
node *q = new node; //为上一层创造q结点
q->data = p->data;
q->under = p;
p->on = q;
if(last!=NULL){
q->next = last->next;
q->next->last = q;
last->next = q;
q->last = last;
}else{
q->next = end;
le[lev+].start->next = q;
q->last = le[lev+].start;
}
if(siz[lev+] == )//其实siz数组有没有都无所谓啦,只要有个bool的siz数组就行
top = lev+;
siz[lev+]++;
return ;
}else
return ;
}
} long long log2(long long n){ //如代码名,计算log2n
long long Value = ,k = ;
while(Value* < n){
k++;
Value*=;
}
return k+;
}
插入排序的优化非希尔【不靠谱地讲可以优化到O(nlogn)】 USACO 丑数的更多相关文章
- UC何小鹏:移动互联网创业需警惕五大“不靠谱
http://tech.qq.com/a/20140121/012443.htm 腾讯科技 启言 1月21日报道 移动互联网创业很容易犯错误,一不小心就陷入“坑”中.UC也是如此.近日,UC创始人何小 ...
- 不靠谱的Paypal及海外网站
昨天因为报名参加某个比赛,需要用到Paypal付款,整整折腾了我一个小时.我先是使用自己的银行储蓄卡,到最后一步需要银行确认,提示我需要安装个插件才能输入密码.安装完插件后需要重启浏览器,整个流程又要 ...
- DateTimePicker.Text不靠谱
DateTimePicker.Text不靠谱 获取时:在DateTimePicker.ValueChanged事件中,获取到的Text有可能是string.Empty!!!,特别当ValueChang ...
- 参加Java培训到底靠不靠谱?
导读 科技越发展,社会越进步,人们越便利,便衍生出更多的人从事程序员这个高大上的职业,可哈尔滨Java培训学校这么多,到底靠不靠谱,会不会处处是陷阱,爱尚实训帮你擦亮眼 随着时代的发展,越来越多的人对 ...
- mysql进阶(十)不靠谱的FLOAT数据类型
今天在设计数据表时,突然发现原来FLOAT原来是很不靠谱的,所以在这里建议大家换成DOUBLE类型, 原因是: 在mysql手册中讲到,在MySQL中的所有计算都是使用双精度完成的,使用float(单 ...
- Shell脚本中字符串判空:使用-z 字符串长度为0时,为真,-n字符串长度不为0,为真。这两个都不靠谱【转】
最近发现使用 -z 和 -n 来判断字符串判空,或不空时,很不靠谱. 使用下面的方法最可靠: if [ "x${value}" == "x" ] ...
- 出租WiFi到底靠不靠谱?
创业是一种心态,也是不断的探索,他融入我们的生活,从日常中积累,从小微处启航. 一.背景交代 最近在换工作,本周搬到新租的单身公寓,空间不大,倒是干净整洁.委托租房中介帮忙开通宽带,告知是电信网最低开 ...
- SimpleDateFormat之后为何多了一年,难道Java API也这么不靠谱?
这一切的背后到底是机器故障,还是程序的bug? 难道Java API也不靠谱 朋友在我博客上发现一时间明显错误,操作时间怎么会是2016年?在同一个for循环输出到页面的时间,唯独这一个时间不对,整整 ...
- tp5.1 phpspreadsheet- 工具类 导入导出(整合优化,非原创,抄一抄,加了一些自己的东西,)
phpspreadsheet-工具类 导入导出(整合优化,非原创,抄一抄,加了一些自己的东西)1. composer require phpoffice/phpspreadsheet2. 看最下面的两 ...
随机推荐
- 安卓的UI界面开发(初学者推荐)
一 随便扯扯 用户界面设计是程序开发的一项重要内容.在界面设计的过程中,需要考虑如何制作出UI界面,怎么样控制UI界面两大块. 任何有编程常识的人都知道:软件界面是开发者添加控件,编写控件控制逻辑, ...
- TCP/IP协议(零)TCP/IP参考模型
我们先浏览一下TCP/IP的参考模型,对网络模型有一个大致的了解,后续着重学习OSI参考模型. TCP/IP参考模型是计算机网络的祖父ARPANET和其后继的因特网使用的参考模型. 1.结构 TCP/ ...
- 【Zookeeper】源码分析之服务器(四)
一.前言 前面分析了LeaderZooKeeperServer,接着分析FollowerZooKeeperServer. 二.FollowerZooKeeperServer源码分析 2.1 类的继承关 ...
- 1339 / 1163: [Baltic2008]Mafia
1163: [Baltic2008]Mafia Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 96 Solved: 60[Submit][Statu ...
- java开发之阿里云对象存储OSS和云数据库Memcache的使用
web开发中标配:aliyun ECS(阿里云服务器),aliyun RDS(阿里云数据库),aliyun OSS(阿里云对象存储),aliyun Memcache(阿里云缓存数据库). 今天就介绍下 ...
- Java Stream API进阶篇
本文github地址 上一节介绍了部分Stream常见接口方法,理解起来并不困难,但Stream的用法不止于此,本节我们将仍然以Stream为例,介绍流的规约操作. 规约操作(reduction op ...
- SQL_Join 小总结
原文出自 :http://www.nowamagic.net/librarys/veda/detail/936 对于SQL的Join,在学习起来可能是比较乱的.我们知道,SQL的Join语法有很多in ...
- cordova原理与插件制作
---恢复内容开始--- cordova插件打包教程: http://www.jianshu.com/p/96855e009e95 www.itnose.net/detail/6245407.html ...
- Swift3.0 函数闭包与OC Block
刚接触Swift,如有不对的点,欢迎指正.转载请说明出处 定义一个基本函数 //定义一个函数,接收一个字符串,返回一个String类型的值 func test(name:String) -> S ...
- C++枚举类型详解
原创作品,转载请注明来源:http://www.cnblogs.com/shrimp-can/p/5171110.html 一.枚举类型的定义 enum 类型名 {枚举值表}: 类型名是变量名,指定 ...