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

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

传送门:快速排序|快速选择(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=1nxi(T(i1)+T(ni)+Θ(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=1nE(xi(T(i1)+T(ni)+Θ(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=1nE(xi)E((T(i1)+T(ni)+Θ(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=1nE(T(i1))+i=1nE(T(ni))+i=1nΘ(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))=n2i=0n1E(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))=n2ci=2n1ilogi+Θ(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=2n1ilogi<21n2logn81n2

算法导论中这里提示说可以将式子分成两半
在这里插入图片描述
可是愚昧的我并没有想到怎么计算(哪位大佬知道烦请告知)。但是我尝试了一下积分,发现了一个更加紧凑的上界。

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

然后将式子带入得到:
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=1nxi(T(max(i1,ni))+Θ(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=1nE(xi(T(max(i1,ni))+Θ(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=1nE(xi)E((T(max(i1,ni))+Θ(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))=n1i=1nE(T(max(i1,ni)))+Θ(n)
然后我们去掉maxmaxmax,即从┌n/2┐\ulcorner n/2 \urcornern/2nnn计算两边。
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))=n2i=n/2nE(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))=n2i=n/2nci+Θ(n)
求和式是一个简单的等差数列,因此
E(T(n))=3c4n+Θ(n)<=cnE(T(n))=\frac{3c}{4}n+\Theta(n)<=cn E(T(n))=43cn+Θ(n)<=cn
ccc足够大的时候上式成立。
对于基本情况,当ccc足够大的时候成立,证毕。

测试

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

快速排序

数据规模1e51e61e7
三者取中0.0202470.2325562.641669
随机化0.1318581.344317ten thousand yearslater

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

快速选择

1e51e61e7
模拟随机化0.0056810.0587960.560251
随机化0.0013480.0164570.159499
BFPTR0.0141200.1470331.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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383664.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux基础】Linux的5种IO模型详解

引入 为了更好的理解5种IO模型的区别&#xff0c;在介绍IO模型之前&#xff0c;我先介绍几个概念 1.进程的切换 &#xff08;1&#xff09;定义 为了控制进程的执行&#xff0c;内核必须有能力挂起正在CPU上运行的进程&#xff0c;并恢复以前挂起的某个进程的执行。即从用户…

计算机网络【五】广播通信+以太网

局域网的拓扑 广域网使用点到点通信 局域网使用广播通信 可以随意向网络中添加设备。 总线网星形网&#xff0c;使用集线器。现在多使用星形网络。环状网树形网 其中匹配电阻用来吸收总线上传播的信号。 共享通信媒体 静态划分信道 频分复用、时分复用、波分复用、码分复用…

聊聊Linux 五种IO模型

一篇《聊聊同步、异步、阻塞与非阻塞》已经通俗的讲解了&#xff0c;要理解同步、异步、阻塞与非阻塞重要的两个概念点了&#xff0c;没有看过的&#xff0c;建议先看这篇博文理解这两个概念点。在认知上&#xff0c;建立统一的模型。这样&#xff0c;大家在继续看本篇时&#…

操作系统【四】分页存储管理

连续分配方式的缺点&#xff1a; 固定分区分配&#xff1a;缺乏灵活性&#xff0c;产生大量的内部碎片&#xff0c;内存的利用率较低 动态分区分配&#xff1a;会产生许多外部碎片&#xff0c;虽然可以用紧凑技术处理&#xff0c;但是紧凑技术的时间代价较高 基本分页存储管理…

操作系统【五】分段内存管理+段页式内存管理

基本分段存储管理 与分页最大的区别&#xff1a;离散分配时所分配地址空间的基本单位不同 进程的地址空间&#xff1a;按照程序自身的逻辑关系划分为若干个段&#xff0c;每个段都有一个段名&#xff0c;每段从0开始编址 内存分配规则&#xff1a;以段位单位进行分配&#xff…

计算机网络【六】网络层协议

网络层负责在不同网络之间尽力转发数据包&#xff08;基于数据包的IP地址转发&#xff09;。不负责丢失重传&#xff0c;也不负责顺序&#xff08;每一个数据包都是单独选择路径&#xff09;。 可靠传输是由传输层实现。 网络设备和OSI参考模型 通过分层&#xff0c;屏蔽了…

计算机网络【3】网络层

主要任务时把分组从源端发送到目的端&#xff0c;为分组交换网上的不同主机提供服务。网络层传输单位是数据报 功能&#xff1a; 路由选择与分组转发&#xff08;最佳路径 &#xff09;异构网络互联拥塞控制 数据交换方式 电路交换&#xff1a;通信时延小、有序传输、没有冲…

Linux探秘之用户态与内核态

https://www.cnblogs.com/bakari/p/5520860.html 一、 Unix/Linux的体系架构 如上图所示&#xff0c;从宏观上来看&#xff0c;Linux操作系统的体系架构分为用户态和内核态&#xff08;或者用户空间和内核&#xff09;。内核从本质上看是一种软件——控制计算机的硬件资源&…

哈夫曼算法证明+哈夫曼编码译码程序实现

哈夫曼算法证明 哈夫曼算法是一种贪心算法&#xff0c;我们考虑证明其最优子结构和贪心选择性质&#xff1a; 最优子结构&#xff1a;假设一个树是哈夫曼树&#xff0c;则以其任意节点为根节点的最大子树也是哈夫曼树。 证明&#xff1a;子树的根节点的值是其所有叶子节点出现…

Python3小知识

对于迭代器对象&#xff0c;Python默认赋值是将引用赋值&#xff0c;即指向同一片内存空间。为了实现对内存空间的赋值&#xff0c;我们可以使用分片进行深复制。例如&#xff1a; 当定义元组的时候&#xff0c;我们一般使用小括号将元素包围起来&#xff0c;也可以不使用括号…

汇编:实现日历星期数查询工具

编制一个简单日历查询工具&#xff0c;输入年、月、日&#xff0c;能够判断当日的星期数&#xff0c;并进行输出&#xff0c;数据的输入和结果的输出要有必要的提示&#xff0c;且提示独占一行。 查阅资料 ​ 经过查阅资料&#xff0c;发现有两个相关的算法可以解决这个问题&…

Linux C 实现一个简单的线程池

https://www.cnblogs.com/GyForever1004/p/9185240.html 线程池的定义 线程池是一种多线程处理形式&#xff0c;处理过程中将任务添加到队列&#xff0c;然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小&#xff0c;以默认的优先级…

斐波那契数列求解+尾递归

1.普通递归 这里观察f[4]的递归树代替f[10]的递归树&#xff08;后者比较大&#xff0c;画不下&#xff09;。 使用递归求解的时候复杂度为T(n)T(n−1)T(n−2)T(n)T(n-1)T(n-2)T(n)T(n−1)T(n−2)&#xff0c;观察递归树&#xff0c;发现降速最快的是最右边每次减2&#xff0c…

LCS最长公共子串

问题介绍 LCS问题(longest common subsequence problem)指的是求解两个字符串最长公共子序列问题。这里的子序列是可以不连续的。LCS问题广泛地出现在计算生物学中&#xff08;DNA序列、系统生成树等等&#xff09;。这里介绍如何解决LCS问题&#xff0c;以及算法的正确性证明…

操作系统【六】虚拟内存

传统存储管理方式的不足 一次性&#xff1a;作业必须一次性全部装入内存后才能开始运行。这会造成&#xff1a;当作也很大时不能全部装入内存&#xff1b;当大量作业要求运行时&#xff0c;由于内存无法容纳所有作业&#xff0c;因此只有少量作业能够运行&#xff0c;导致多道…

SQL Server【一】简介和基本概念和命令

数据结构和数据库的区别 数据库是应用软件级别研究数据的存储和操作&#xff08;主要针对磁盘上的数据&#xff09; 数据结构是在系统软件级别研究数据的存储和操作&#xff08;主要是针对内存中的数据&#xff09; 对硬盘数操作是数据库的强项&#xff0c;是数据库研究的核心…

Linux下网络socket编程——实现服务器(select)与多个客户端通信

一、关于socket通信 服务器端工作流程&#xff1a; 调用 socket() 函数创建套接字 用 bind() 函数将创建的套接字与服务端IP地址绑定调用listen()函数监听socket() 函数创建的套接字&#xff0c;等待客户端连接 当客户端请求到来之后调用 accept()函数接受连接请求&#xff0c…

SQL Server【四】

identity 主键自动增长&#xff0c;用户不需要为identity修饰的主键赋值 create table student (std_id int primary key identity(10,5),--(10,5)可以省略&#xff0c;默认为(1,1)std_name nvarchar(200) not null ) select * from student insert into student values (张三…

计算机网络【4】传输层

概述 传输层是只有主机才有的层次 传输层的功能&#xff1a; 传输层提供进程和进程之间的逻辑通信&#xff08;网络层提供主机与主机之间的逻辑通信&#xff09;复用和分用传输层对收到的报文进行差错检测 传输层有两个协议&#xff1a; 面向连接的传输层控制协议TCP&…

计算机网络【0】概述

计算机网络概念和功能 概念 是一个将分散的、具有独立功能的计算机系统&#xff0c;通过通信设备与线路连接起来&#xff0c;由功能完善的软件实现资源共享和信息传递的系统。 计算机网络是互连的、自治&#xff08;无主从关系&#xff09;的计算机集合。 功能 数据通信&am…