對于快速排序和快速選擇我之前的文章已經有詳細的說明,需要了解的同學可以移步
傳送門:快速排序|快速選擇(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;
}