多種排序演算法的效率觀察

zYzYzYzYz發表於2024-04-28

注:時間的單位為毫秒,每個資料均觀測三次取平均值(排除異常資料)。

時間複雜度較大的排序演算法

隨機資料耗時

資料規模 選擇排序 氣泡排序 插入排序 猴子排序
\(10\) 0 0 0 178
\(100\) 0 0 0 -
\(10^3\) 0 0 0 -
\(10^4\) 130 124 24 -

分析

選擇排序、氣泡排序、插入排序時間複雜度均為 \(O(n^2)\)​,但是插入排序的時間卻比其餘兩者小。原因是如果插入的數字不太極端的話,每一個數插到中間就break了,上限跑不滿。

順序資料耗時

資料規模 選擇排序 氣泡排序 插入排序
\(10^4\) 35 0 0

由於氣泡排序和插入排序當檢測到序列已經升序時,可以直接停止演算法,使得二者在第一次迴圈結束時就結束了。選擇排序僅僅是比較了 \(\frac{n\times(n-1)}{2}\) 次大小,省去了交換兩數的過程,時間得到較大減少。

逆序資料耗時

資料規模 選擇排序 氣泡排序 插入排序
\(10^4\) 40 79 52

選擇排序竟然反殺了冒泡和插入。也許 c++ 會這點程式碼給最佳化了,於是用C語言重新寫了一遍,發現有:

資料規模 選擇排序 氣泡排序 插入排序
\(10^4\) 163 165 96

這符合之前的規律。但是為什麼逆序時c++會出現反常還有待討論。

時間複雜度較小的排序演算法

耗時

資料規模 歸併排序 堆排序 快速排序 快速排序(std)
\(10^3\) 0 0 0 0
\(10^4\) 0 1 0 0
\(10^5\) 12 12 8 5
\(10^6\) 136 176 121 84
\(10^7\) 1703 2926 1397 904

這些演算法複雜度均為 \(O(n\log n)\) ,相對於之前的 \(O(n^2)\) 演算法快了不少。手寫堆排序常數較大,用stl的priority_queue可以最佳化一下常數;stl的快速排序效率非常之高,其中一個最佳化是當資料範圍較小的時候直接進行插入排序,而非遞迴進行快速排序。

順序資料耗時

資料規模 歸併排序 堆排序 快速排序 快速排序(std)
\(10^5\) 4 10 3 1
\(10^6\) 53 116 40 12
\(10^7\) 634 1297 491 176

逆序資料耗時

資料規模 歸併排序 堆排序 快速排序 快速排序(std)
\(10^5\) 4 7 3 1
\(10^6\) 47 98 40 10
\(10^7\) 583 1048 501 129

我們可以發現,當資料有序時排序的速度比亂序時快。但是逆序時部分演算法比順序時快一點(目前並沒有搞明白是為什麼)。

複雜度與值域相關的排序演算法

耗時

資料規模 值域 桶排序 基數排序
\(10^4\) \(10^4\) 0 0
\(10^4\) \(10^7/10^9\) 54 0
\(10^5\) \(10^4\) 0 2
\(10^5\) \(10^7/10^9\) 57 4
\(10^6\) \(10^4\) 2 19
\(10^6\) \(10^7/10^9\) 86 41
\(10^7\) \(10^4\) 23 254
\(10^7\) \(10^7/10^9\) 344 483

由於開 \(10^9\) 個變數需要一個超大的記憶體,故桶排序的較大值域設為 \(10^7\)

相關程式碼

選擇排序

#include<bits/stdc++.h>
using namespace std;
int n;
int a[100010];
mt19937 mt(123456);
void xzsort()
{
	for(int i=1;i<=n;i++)
	for(int j=i+1;j<=n;j++)
		if(a[i]>a[j])
			swap(a[i],a[j]);
}
int main()
{
	n=10000;
	for(int i=1;i<=n;++i)
		a[i]=mt()%1000000000ll;
	// sort(a+1,a+1+n);
	// reverse(a+1,a+1+n);
	int abc=clock();
	xzsort();
	cout<<clock()-abc<<'\n';
}

氣泡排序

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n;
int a[10010];
mt19937 mt(123456);
void mpsort()
{
	for(int i=1;i<=n;i++){
		bool flag=0;
		for(int j=1;j<=n-i;j++)
		if(a[j]>a[j+1])
			swap(a[j+1],a[j]),flag=1;
		if(!flag)break;
	}
}
int main()
{
	n=10000;
	for(int i=1;i<=n;++i)
		a[i]=mt()%1000000000ll;
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	int abc=clock();
	mpsort();
	cout<<clock()-abc<<'\n';
}

插入排序

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n;
LL a[10010],b[10010];
mt19937 mt(123456);
void mpsort()
{
	for(int i=1;i<=n;i++){
		b[i]=a[i];
		for(int j=i;j>1;j--)
		if(a[j]<a[j-1])
			swap(a[j],a[j-1]);
		else break;
	}
	memcpy(a,b,sizeof(LL)*(n+3));
}
int main()
{
	n=10000;
	for(int i=1;i<=n;++i)
		a[i]=mt()%1000000000ll;
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	int abc=clock();
	mpsort();
	cout<<clock()-abc<<'\n';
}

歸併排序

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n;
LL a[10000010],b[10000010];
mt19937 mt(123456);
void mergesort(int L,int R)
{
	if(L==R)return;
	int M=(L+R)>>1;
	mergesort(L,M);
	mergesort(M+1,R);
	int Lp=L,Rp=M+1,pos=L;
	while(Lp<=M||Rp<=R){
		if((a[Lp]<=a[Rp]&&Lp<=M)||Rp>R)
			b[pos++]=a[Lp++];
		else b[pos++]=a[Rp++];
	}
	memcpy(a+L,b+L,sizeof(LL)*(R-L+1));
}
int main()
{
	n=10000000;
	for(int i=1;i<=n;++i)
		a[i]=mt()%1000000000ll;
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	int abc=clock();
	mergesort(1,n);
	cout<<clock()-abc<<'\n';
}

堆排序

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n;
LL a[10000010],DUI[10000010],NUM;
mt19937 mt(123456);
void UP(int x)
{
	while(x>1){
		if(DUI[x]<=DUI[x>>1])return;
		DUI[x]^=DUI[x>>1]^=DUI[x]^=DUI[x>>1];
		x>>=1;
	}
}
void DOWN(int x)
{
	int y=x<<1;
	while(y<=NUM){
		if(y<NUM&&DUI[y+1]>DUI[y])
			y++;
		if(DUI[x]>=DUI[y])return;
		DUI[x]^=DUI[y]^=DUI[x]^=DUI[y];
		x=y;y<<=1;
	}
}
void ADD(LL x)
{
	DUI[++NUM]=x;
	UP(NUM);
}
void DEL()
{
	DUI[1]=DUI[NUM--];
	DOWN(1);
}
void duisort(LL A[],int Len)
{
	NUM=0;
	for(int i=1;i<=Len;i++)
		ADD(A[i]);
	for(int i=Len;i;i--)
		A[i]=DUI[1],DEL();
}
int main()
{
	n=10000000;
	for(int i=1;i<=n;++i)
		a[i]=mt()%1000000000ll;
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	int abc=clock();
	duisort(a,n);
	cout<<clock()-abc<<'\n';
}

快速排序

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int bit[4]={0,16,32,48},U=65535;
LL n,a[10000010],Maxa[10000010],Mina[10000010];
int st[66000];
mt19937 mt(123456);
void qsort(int L,int R)
{
	if(L>=R)return;
	int M=(L+R)>>1,Lp=0,Rp=0;
	LL Max=max(a[M],max(a[L],a[R])),Min=min(a[M],min(a[L],a[R]));
	LL temp=a[M]^a[L]^a[R]^Max^Min;
	for(int i=L;i<=R;i++)
	if(a[i]<temp)Mina[++Lp]=a[i];
	else if(a[i]>temp)Maxa[++Rp]=a[i];
	memcpy(a+L,Mina+1,sizeof(LL)*Lp);
	fill(a+L+Lp,a+R-Rp,temp);
	memcpy(a+R-Rp+1,Maxa+1,sizeof(LL)*Rp);
	qsort(L,L+Lp-1);
	qsort(R-Rp+1,R);
}
int main()
{
	n=10000000;
	for(int i=1;i<=n;++i)
		a[i]=mt()%1000000000ll;
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	int abc=clock();
	qsort(1,n);
	cout<<clock()-abc<<'\n';
}

快速排序std

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n;
LL a[10000010];
mt19937 mt(123456);
int main()
{
	n=10000000;
	for(int i=1;i<=n;++i)
		a[i]=mt()%1000000000ll;
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	int abc=clock();
	sort(a+1,a+1+n);
	cout<<clock()-abc<<'\n';
}

基數排序

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int bit[4]={0,16,32,48},U=65535;
LL n,a[10000010],b[10000010];
int st[66000];
mt19937 mt(123456);
void radixsort()
{
	for(int d=0;d<2;++d){
		memset(st,0,sizeof st);
		for(int i=1;i<=n;++st[(a[i++]>>bit[d])&U]);
		for(int i=1;i<=U;++i)st[i]+=st[i-1];
		for(int i=n;i>0;--i)b[st[(a[i]>>bit[d])&U]--]=a[i];
		memcpy(a,b,sizeof(LL)*(n+5));
	}
}
int main()
{
	n=1000000;
	for(int i=1;i<=n;++i)
		a[i]=mt()%10000ll;
	int abc=clock();
	radixsort();
	cout<<clock()-abc<<'\n';
}

桶排序

#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL n,a[10000010],c[10000010];
LL b[10000010];
int st[66000];
mt19937 mt(123456);
void tongsort()
{
	for(int i=1;i<=n;++b[a[i++]]);
	for(int i=n=0;i<=10000;i++)
	while(b[i]--)a[++n]=i;
}
int main()
{
	n=100000;
	for(int i=1;i<=n;++i)
		a[i]=mt()%10000;
	int abc=clock();
	tongsort();
	cout<<clock()-abc<<'\n';
}

相關文章