对于快速排序和快速选择我之前的文章已经有详细的说明,需要了解的同学可以移步

传送门:快速排序|快速选择(BFPTR)

所谓随机化其实就是选择枢纽的时候使用随机数选择而已,实现起来很简单。但是我们使用随机数如何保证复杂度呢?

首先,我们假定随机数的确是随机的,即我们选取任何一个元素作为枢纽都是有可能的。

我们使用指示器随机变量xix_ixi​,如果选择第iii个元素作为枢纽则xi=1x_i=1xi​=1,否则等于0。如果还不了解什么是指示器随机变量可以看一下这篇文章,觉得讲的很好:传送门

简单来讲,利用期望的线性性质,巧妙利用指示器随机变量可以将一个复杂的问题分解为许多容易处理的简单的问题。而且它有一个很好的性质就是他的期望就是他的概率。

因为是随机算法,所以我们求解的是复杂度的期望。

随机快速排序

由快速排序算法,我们可以得到递归式为:
E(T(n))=E∑i=1nxi(T(i−1)+T(n−i)+Θ(n))E(T(n))=E\sum_{i=1}^n{x_i(T(i-1)+T(n-i)+\Theta(n))} E(T(n))=Ei=1∑n​xi​(T(i−1)+T(n−i)+Θ(n))
由期望的线性性质:
E(T(n))=∑i=1nE(xi(T(i−1)+T(n−i)+Θ(n)))E(T(n))=\sum_{i=1}^nE({x_i(T(i-1)+T(n-i)+\Theta(n)))} E(T(n))=i=1∑n​E(xi​(T(i−1)+T(n−i)+Θ(n)))
对于一个确定的xix_ixi​,就确定了当前的划分,但是对于进一步的递归和xix_ixi​没有关系,因此两者是独立的,由期望的独立性性质:
E(T(n))=∑i=1nE(xi)∗E((T(i−1)+T(n−i)+Θ(n)))E(T(n))=\sum_{i=1}^nE(x_i)*E((T(i-1)+T(n-i)+\Theta(n))) E(T(n))=i=1∑n​E(xi​)∗E((T(i−1)+T(n−i)+Θ(n)))
因为我们假定是完全随机的,所以E(xi)=1nE(x_i)=\frac{1}{n}E(xi​)=n1​,是一个常量,然后再根据期望的线性性质:
E(T(n))=E(xi)∗(∑i=1nE(T(i−1))+∑i=1nE(T(n−i))+∑i=1nΘ(n))E(T(n))=E(x_i)*(\sum_{i=1}^nE(T(i-1))+\sum_{i=1}^nE(T(n-i))+\sum_{i=1}^n\Theta(n)) E(T(n))=E(xi​)∗(i=1∑n​E(T(i−1))+i=1∑n​E(T(n−i))+i=1∑n​Θ(n))
仔细观察发现两个求和式是一样的,因此我们可以合并
E(T(n))=2n∗∑i=0n−1E(T(i))+Θ(n)E(T(n))=\frac{2}{n}*\sum_{i=0}^{n-1}E(T(i))+\Theta(n) E(T(n))=n2​∗i=0∑n−1​E(T(i))+Θ(n)
我们预期的时间复杂度为O(nlogn)O(nlogn)O(nlogn),因此我们用代入法证明:

我们假设E(T(n))<=cnlognE(T(n))<=cnlognE(T(n))<=cnlogn

因为当i=0和i=1的时候复杂度都是常数,所以我们将他们从式子中移去加入常数项(这样才可以将log带入)

E(T(n))=2cn∗∑i=2n−1ilogi+Θ(n)E(T(n))=\frac{2c}{n}*\sum_{i=2}^{n-1}ilogi+\Theta(n) E(T(n))=n2c​∗i=2∑n−1​ilogi+Θ(n)

因为我们想要证明的是E(T(n))<anlognE(T(n))<anlognE(T(n))<anlogn,我们需要想办法消去后面的Θ(n)\Theta(n)Θ(n),所以我们必须在求和式上动些手脚。

下面证明:
∑i=2n−1ilogi<12n2logn−18n2\sum_{i=2}^{n-1}ilogi<\frac{1}{2}n^2logn-\frac{1}{8}n^2 i=2∑n−1​ilogi<21​n2logn−81​n2

算法导论中这里提示说可以将式子分成两半

可是愚昧的我并没有想到怎么计算(哪位大佬知道烦请告知)。但是我尝试了一下积分,发现了一个更加紧凑的上界。

我们可以将式子变成
∑k=2n−1klogk<∫2n−1klogkdk\sum_{k=2}^{n-1}klogk<\int_{2}^{n-1}klogk\,{\rm d}k k=2∑n−1​klogk<∫2n−1​klogkdk
然后我掏出了多年没有用过的高数课本,对这个式子积分,然后会得到
∑k=2n−1klogk<12n2logn−14ln2n2\sum_{k=2}^{n-1}klogk<\frac{1}{2}n^2logn-\frac{1}{4ln2}n^2 k=2∑n−1​klogk<21​n2logn−4ln21​n2
而这个式子是比上面小的,所以算是证明了吧。。。

然后将式子带入得到:
E(T(n))<cnlogn+Θ(n)−cn4E(T(n))<cnlogn+\Theta(n)-\frac{cn}{4} E(T(n))<cnlogn+Θ(n)−4cn​
对于足够大的ccc,后面的为负,即
E(T(n))<cnlognE(T(n))<cnlogn E(T(n))<cnlogn
对于基本情况,如果ccc足够大,上式也满足,证毕。

随机快速选择

这个复杂度的证明和上面的类似,而且比上面的简答。

由快速选择算法,有递归式:
E(T(n))=E∑i=1nxi(T(max(i−1,n−i))+Θ(n))E(T(n))=E\sum_{i=1}^n{x_i(T(max(i-1,n-i))+\Theta(n))} E(T(n))=Ei=1∑n​xi​(T(max(i−1,n−i))+Θ(n))
之所以有maxmaxmax是因为我们求取的是上界,所以我们总选取大区间

然后我们同上进行化简:
E(T(n))=∑i=1nE(xi(T(max(i−1,n−i))+Θ(n)))E(T(n))=\sum_{i=1}^nE({x_i(T(max(i-1,n-i))+\Theta(n)))} E(T(n))=i=1∑n​E(xi​(T(max(i−1,n−i))+Θ(n)))
E(T(n))=∑i=1nE(xi)∗E((T(max(i−1,n−i))+Θ(n)))E(T(n))=\sum_{i=1}^nE(x_i)*E((T(max(i-1,n-i))+\Theta(n))) E(T(n))=i=1∑n​E(xi​)∗E((T(max(i−1,n−i))+Θ(n)))
E(T(n))=1n∑i=1nE(T(max(i−1,n−i)))+Θ(n)E(T(n))=\frac{1}{n}\sum_{i=1}^nE(T(max(i-1,n-i)))+\Theta(n) E(T(n))=n1​i=1∑n​E(T(max(i−1,n−i)))+Θ(n)
然后我们去掉maxmaxmax,即从┌n/2┐\ulcorner n/2 \urcorner┌n/2┐到nnn计算两边。
E(T(n))=2n∑i=┌n/2┐nE(T(i))+Θ(n)E(T(n))=\frac{2}{n}\sum_{i=\ulcorner n/2 \urcorner}^nE(T(i))+\Theta(n) E(T(n))=n2​i=┌n/2┐∑n​E(T(i))+Θ(n)

我们假设复杂度为O(n)O(n)O(n),即E(T(k))<=ckE(T(k))<=ckE(T(k))<=ck,再带入:
E(T(n))=2n∑i=┌n/2┐nci+Θ(n)E(T(n))=\frac{2}{n}\sum_{i=\ulcorner n/2 \urcorner}^nci+\Theta(n) E(T(n))=n2​i=┌n/2┐∑n​ci+Θ(n)
求和式是一个简单的等差数列,因此
E(T(n))=3c4n+Θ(n)<=cnE(T(n))=\frac{3c}{4}n+\Theta(n)<=cn E(T(n))=43c​n+Θ(n)<=cn
当ccc足够大的时候上式成立。
对于基本情况,当ccc足够大的时候成立,证毕。

测试

既然复杂度差不多,那么是否随机化的性能差别大吗?怀着这个疑问,我自己手动进行了测试。

快速排序

数据规模 1e5 1e6 1e7
三者取中 0.020247 0.232556 2.641669
随机化 0.131858 1.344317 ten thousand yearslater

可以看出,快速选择排序我们使用三者取中的方法是比随机化快很多的。

快速选择

1e5 1e6 1e7
模拟随机化 0.005681 0.058796 0.560251
随机化 0.001348 0.016457 0.159499
BFPTR 0.014120 0.147033 1.438184

可以看出,当我们进行快速选择的时候随机化的选择枢纽是最快的。也验证了我在专门介绍BFPTR算法的文章中的分析。

测试代码

因为快速排序算法的测试代码我在专门介绍快速排序的时候已经写过了,这里就不再贴了,如果需要的话加单修改一下就可以。这里贴一下快速选择算法的测试代码:

#include <iostream>
#include <ctime>
#include <cstdio>
#include <fstream>
#include <cstdlib>using namespace std;typedef double T;
typedef int (*FP)(T*,int,int,int);  //定义函数指针数组类型void CreatData()
{int n=10;FILE* file=fopen("TestFile","w");fprintf(file,"%d\n",n);int t;srand(t);for(int i=0;i<n;++i){t=rand();fprintf(file,"%d ",rand()%10);}fclose(file);return ;
}T* CreatList(int &n)
{//printf("n=");//CreatData();ifstream in("TestFile");in >> n;T* ret = new T[n];for(int i=0;i<n;++i){in>>ret[i];}in.close();return ret;
}void Init(T* a,int l,int r)
{srand((int)time(NULL));int idx = rand()%(l-r)+l;swap(a[idx],a[l]);return;
}void InsertSort(T* a,int l,int r)
{//插入排序int mid=(l+r)>>1;   //获得中位数就足够了for(int i=l+1;i<=mid;++i){T x=a[i]; int j=i-1;while(j>=l && a[j]>x){a[j+1]=a[j]; --j;}a[j+1]=x;}
}void InsertSort1(T* a,int l,int r)
{//插入排序for(int i=l+1;i<r;++i){T x=a[i]; int j=i-1;while(j>=l && a[j]>x){a[j+1]=a[j]; --j;}a[j+1]=x;}
}void GetPovit1(T* a,int l,int r)
{int x;    //将区间分割为[x,x+5)int cnt=0;  //有多少个中位数for(x=l; x+5<r; x+=5){InsertSort1(a,x,x+5);swap(a[l+cnt],a[x+2]);  //将当前区间的中位数放在最前面++cnt;}if(x<r){InsertSort1(a,x,r);swap(a[l+cnt],a[(x+r)>>1]);++cnt;}if(1 == cnt) return;GetPovit1(a,l,l+cnt);
}int BFPTR1(T* a,int l,int r,int k)
{if(r-l == 1) return l;   //返回找到的数字GetPovit1(a,l,r);            //五个一组递归求取中位数T povit=a[l];int i=l-1,j=r;while(i<j){do ++i; while(a[i]<povit);do --j; while(a[j]>povit);if(i<j) swap(a[i],a[j]);}if(j-l+1>=k) return BFPTR1(a,l,j+1,k);else return BFPTR1(a,j+1,r,k-j+l-1);
}int BFPTR2(T* a,int l,int r,int k)
{if(r-l == 1) return l;   //返回找到的数字Init(a,l,r);T povit=a[l];int i=l,j=r;while(i<j){do ++i; while(i+1 < r && a[i]<povit);do --j; while(a[j]>povit);if(i<j) swap(a[i],a[j]);}swap(a[l],a[j]);int num=j-l+1;  //povit在当前序列中排第几if(k == num) return j;else if(num > k) return BFPTR2(a,l,j,k);else return BFPTR2(a,j+1,r,k-num);
}int BFPTR3(T* a,int l,int r,int k);T GetPovit(T* a,int l,int r)
{int x;    //将区间分割为[x,x+5)int cnt=0;  //有多少个中位数for(x=l; x+5<r; x+=5){InsertSort(a,x,x+5);swap(a[l+cnt],a[x+2]);  //将当前区间的中位数放在最前面++cnt;}if(x<r){InsertSort(a,x,r);swap(a[l+cnt],a[(x+r)>>1]);++cnt;}if(1 == cnt) return l;return BFPTR3(a,l,l+cnt,cnt/2);
}int BFPTR3(T* a,int l,int r,int k)
{if(r-l == 1) return l;   //返回找到的数字//五个一组递归求取中位数int idx = GetPovit(a,l,r);T povit = a[idx];swap(a[l],a[idx]);int i=l,j=r;while(i<j){do ++i; while(i+1 < r && a[i]<povit);do --j; while(a[j]>povit);if(i<j) swap(a[i],a[j]);}swap(a[l],a[j]);int num=j-l+1;  //povit在当前序列中排第几if(k == num) return j;else if(num > k) return BFPTR3(a,l,j,k);else return BFPTR3(a,j+1,r,k-num);
}void Show(T* a,int n)
{for(int i=0;i<n;++i){cout<<a[i]<<" ";}cout<<endl;
}void Test(FP fp[])
{for(int i=0;i<3;++i){clock_t S,E;int Time = 10;double sum=0;for(int j=0;j<Time;++j){int n;T* a=CreatList(n);S=clock();int x=fp[i](a,0,n,9);//0 0 1 4 4 4 6 6 8 9E=clock();//cout<<x<<":"<<a[x]<<" ";sum+=(double)(E-S)/CLOCKS_PER_SEC;//cout<<"经过排序之后:"<<endl;//Show(a,n);delete[] a;}cout<<endl;printf("BFPTR%d's times=%f\n",i+1,sum/Time);}
}int main()
{FP fp[3] = {BFPTR1,BFPTR2,BFPTR3};Test(fp);return 0;
}

随机化快速排序+快速选择 复杂度证明+运行测试相关推荐

  1. java 随机化快速排序,JS实现随机化快速排序的实例代码

    算法的平均时间复杂度为O(nlogn).但是当输入是已经排序的数组或几乎排好序的输入,时间复杂度却为O(n^2).为解决这一问题并保证平均时间复杂度为O(nlogn)的方法是引入预处理步骤,它惟一的目 ...

  2. BFPTR算法详解+实现+复杂度证明

    BFPTR算法是由Blum.Floyed.Pratt.Tarjan.Rivest这五位牛人一起提出来的,其特点在于可以以最坏复杂度为O(n)O(n)O(n)地求解top−ktop-ktop−k问题.所 ...

  3. 线段树合并复杂度证明

    前言 近期对线段树合并有了更深的了解,所以在这里写一下一些自己的想法 适用问题 线段树合并有一类经典的模板,现在对于一棵有n个叶子节点的树(Tip:对于一棵nnn个叶子节点的树,其节点数量小于等于2n ...

  4. 斜堆学习笔记+复杂度证明

    和左偏树几乎一模一样,唯一的区别是左偏树合并后判断如果左儿子深度小于右儿子则交换左右儿子,而斜堆直接无脑交换. 复杂度是均摊的 O(nlog⁡n)O(n\log n)O(nlogn) 证明: 定义重结 ...

  5. 【CF1311E】Construct the Binary Tree【增量构造】【复杂度证明】

    题意:给定nnn和ddd,构造或判断无法构造一棵二叉树使得所有点的深度(定义为到根距离)之和为ddd. n,d≤5000n,d\leq 5000n,d≤5000 显然可以算出有解的ddd的下界和上界, ...

  6. 关于 min_25 筛的入门以及复杂度证明

    min_25 筛是由 min_25 大佬使用后普遍推广的一种新型算法,这个算法能在 \(O({n^{3\over 4}\over log~ n})\) 的复杂度内解决所有的积性函数前缀和求解问题(个人 ...

  7. python语言实现快速排序以及复杂度的分析总结

    一.快速排序 思想:对一组数据进行排序,找到一个位置放置这组数据的第一个数,该位置左边的数比该数小,右边比该数大.然后分别对左边和右边的数据再次使用同样的方法,最后直到左右两边只有一个数据,结束. 复 ...

  8. 【杭电多校2020】Lead of Wisdom【搜索】【复杂度证明】

    题意:nnn件物品,每种物品有一个种类tit_iti​,四个属性ai,bi,ci,dia_i,b_i,c_i,d_iai​,bi​,ci​,di​,每个种类最多选一件物品,求 (100+∑ai)(10 ...

  9. 快速排序时间复杂度为O(n×log(n))的证明

    快速排序时间复杂度为O(n×log(n))的证明 2014年05月22日 11:17:52 oohaha_123 阅读数:2789 标签: 快速排序算法导论复杂度证明 更多 个人分类: 资料收集整理数 ...

最新文章

  1. SQL——以面向集合的思维方式来思考
  2. 性能监控工具 NewRelic 简介
  3. 简述一下extern C的小作用
  4. XP系统下建立WIFI热点让手机、电脑能上网
  5. 03.进程和线程.md
  6. 循环队列及C语言实现一
  7. delphi中richedit中光标如何定位到最后_嵌入式开发之Linux系统中Vi编辑器的使用
  8. 引入对抗训练的对比学习
  9. 【Spring Boot 实战】数据库千万级分库分表和读写分离实战
  10. CentOS 6.5 x64 Redis 安装
  11. 写于公元2006年2月14日
  12. NGINX实现负载均衡,并利用PHP实现session入库
  13. js 日期操作 (转载:http://blog.sina.com.cn/s/blog_699d2e170101q6iz.html)
  14. android apk更新下载以及安装
  15. python的多行注释以什么开头_Python的多行注释
  16. java上传zip_JAVA 上传ZIP,RAR文件并解压
  17. 计算机无法加载操作系统,由于关键系统驱动程序丢失或包含错误,因此无法加载操作系统。解决方案...
  18. 一文看懂摄像头测距技术
  19. 龙门标局:注册地理标志证明商标有什么作用
  20. 2021计算机excel,excel2021版本

热门文章

  1. python笔记30-docstring注释添加变量
  2. JAVA 框架-Spring
  3. So easy Webservice 1.Socket建设web服务
  4. 华为摄像机搜索软件_别人的终点华为的起点!用普惠AI守护城市安全
  5. 计算机硬件系统一直延用,会计从业资格证考试:会计电算化计算机硬件系统
  6. php 四维数组_PHP 多维数组
  7. java 中io的删除文件_总结删除文件或文件夹的7种方法-JAVA IO基础总结第4篇
  8. java比较时间sql_如何正确比较日期 java.sql.Date
  9. php酷狗音乐json,用php来搜索酷狗音乐
  10. java多文件post请求_如何使用Java发出多部分/表单数据POST请求?