隨機化快速排序+快速選擇 復雜度證明+運行測試

對于快速排序和快速選擇我之前的文章已經有詳細的說明,需要了解的同學可以移步

傳送門:快速排序|快速選擇(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=1n?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=1n?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=1n?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=1n?E(T(i?1))+i=1n?E(T(n?i))+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))=n2??i=0n?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=2n?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=2n?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=2n?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=2n?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=1n?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=1n?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=1n?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=1n?E(T(max(i?1,n?i)))+Θ(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))=n2?i=n/2n?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/2n?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足夠大的時候成立,證畢。

測試

既然復雜度差不多,那么是否隨機化的性能差別大嗎?懷著這個疑問,我自己手動進行了測試。

快速排序

數據規模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.pswp.cn/news/383664.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383664.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383664.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++子類父類成員函數的覆蓋和隱藏實例詳解

https://www.jb51.net/article/117380.htm函數的覆蓋覆蓋發生的條件&#xff1a; &#xff08;1&#xff09; 基類必須是虛函數&#xff08;使用virtual 關鍵字來進行聲明&#xff09; &#xff08;2&#xff09;發生覆蓋的兩個函數分別位于派生類和基類 &#xff08;3&#xf…

【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;但是緊湊技術的時間代價較高 基本分頁存儲管理…

聊聊同步、異步、阻塞與非阻塞

近來遇到了一些常見的概念&#xff0c;尤其是網絡編程方面的概念&#xff0c;如&#xff1a;阻塞、非阻塞、異步I/O等等&#xff0c;對于這些概念自己也沒有太清晰的認識&#xff0c;只是很模糊的概念&#xff0c;說了解吧也了解&#xff0c;但是要讓自己準確的描述概念方面的具…

操作系統【五】分段內存管理+段頁式內存管理

基本分段存儲管理 與分頁最大的區別&#xff1a;離散分配時所分配地址空間的基本單位不同 進程的地址空間&#xff1a;按照程序自身的邏輯關系劃分為若干個段&#xff0c;每個段都有一個段名&#xff0c;每段從0開始編址 內存分配規則&#xff1a;以段位單位進行分配&#xff…

計算機網絡【六】網絡層協議

網絡層負責在不同網絡之間盡力轉發數據包&#xff08;基于數據包的IP地址轉發&#xff09;。不負責丟失重傳&#xff0c;也不負責順序&#xff08;每一個數據包都是單獨選擇路徑&#xff09;。 可靠傳輸是由傳輸層實現。 網絡設備和OSI參考模型 通過分層&#xff0c;屏蔽了…

epoll 水平觸發與邊緣觸發

https://blog.csdn.net/lihao21/article/details/67631516?refmyread epoll也是實現I/O多路復用的一種方法&#xff0c;為了深入了解epoll的原理&#xff0c;我們先來看下epoll水平觸發&#xff08;level trigger&#xff0c;LT&#xff0c;LT為epoll的默認工作模式&#xff…

計算機網絡【3】網絡層

主要任務時把分組從源端發送到目的端&#xff0c;為分組交換網上的不同主機提供服務。網絡層傳輸單位是數據報 功能&#xff1a; 路由選擇與分組轉發&#xff08;最佳路徑 &#xff09;異構網絡互聯擁塞控制 數據交換方式 電路交換&#xff1a;通信時延小、有序傳輸、沒有沖…

C++空類的大小

https://blog.csdn.net/lihao21/article/details/47973609 本文中所說是C的空類是指這個類不帶任何數據&#xff0c;即類中沒有非靜態(non-static)數據成員變量&#xff0c;沒有虛函數(virtual function)&#xff0c;也沒有虛基類(virtual base class)。 直觀地看&#xff0c…

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;發現有兩個相關的算法可以解決這個問題&…

一個通用純C隊列的實現

https://blog.csdn.net/kxcfzyk/article/details/31728179 隊列并不是很復雜的數據結構&#xff0c;但是非常實用&#xff0c;這里實現一個隊列是因為在我的另一篇博客非常精簡的Linux線程池實現中要用到。 隊列API定義如下&#xff1a; //queue.h #ifndef QUEUE_H_INCLUDED…

Dijkstra算法介紹+正確性證明+性能分析

算法介紹 源點s,數組d[u]表示s到u的最短距離&#xff0c;空集S&#xff0c;點集Q初始化&#xff1a;將源點s從點集中去掉&#xff0c;加入S&#xff0c;d[s]0&#xff0c;?v∈Q,d[v]w[s][v]\forall v\in Q ,d[v]w[s][v]?v∈Q,d[v]w[s][v]將Q中d[v]最小的點去掉加入S,并對u∈…

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…

循環服務器,并發服務器模型以及I/O多路轉接模型

https://blog.csdn.net/xinianbuxiu/article/details/53455784 一、基于TCP/IP協議的基本循環服務器 tcp_server.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #incl…