算法學習筆記——常用十個排序算法彙總

常用排序算法彙總

1、插入排序

簡介:作爲算法導論上的第一個排序算法,插入排序理解起來不難。其基本原理如圖所示
在這裏插入圖片描述
排序機理:從左向右掃描,每遇到一個數字temp就將其從右向左,與位於temp-1的數進行大小比較,如果滿足大小在兩數之間,就執行插入,所謂的插入,本質是通過改變元素在數組中的位置,每比較一個數字,若不滿足,則temp向左一位,temp-1的數字右移一位。

時間複雜度:O(n^2)

穩定性:穩是穩,就會慢了點

代碼

void insertion_sort (int arr[],int length)
{//插入排序 
	int i,j;
	for(i=1;i<length;i++)
	{
		int temp=arr[i]; 
		for(j=i;j>0&&arr[j-1]>temp;j--)
		{
			arr[j]=arr[j-1];
		}	
		arr[j]=temp;
	}
}

2、希爾排序

簡介:希爾排序是在插入排序基礎上改進而來,其本質也是插入排序,改進後的算法時間複雜度降到了O(n log n)。

在這裏插入圖片描述
在這裏插入圖片描述
排序機理:根據步長由長到短分組,比如步長爲5,那就把間隔爲5的元素取出來,算爲一組進行插入排序,這組排完之後再以步長爲2進行插入排序,直到步長爲1爲止。本質上是在插入排序上加入了間隔機制。

時間複雜度:O(n log n)

穩定性:不穩定

代碼

void shellsort(int arr[],int n)
{//希爾排序 
	for(int gap=n/2;gap>0;gap/=2)
	{
		for(int i=gap;i<n;i++)
		{
			int temp=arr[i];
			int j;
			for(j=i;j>=gap&&arr[j-gap]>temp;j-=gap)
				arr[j]=arr[j-gap];
			arr[j]=temp;
		} 
	}
}

3、基數排序

簡介:基數排序是分別先對數字的個位進行排序,然後十位,百位,千位,萬位等等,最後一步一次性排好,其中數位不足的就要用0補全。排序過程的話,有點像這種感覺(如圖)

圖侵刪

排序機理

圖侵刪

時間複雜度:O(n k)

穩定性:又穩又快

代碼

int getMax(int arr[],int n)//尋找數組中最大元素 
{
	int mx=arr[0];
	for(int i=1;i<n;i++)
		if(arr[i]>mx)
			mx=arr[i];
	return mx;
}
void countsort(int arr[],int n,int exp)
{
	int output[n];
	int i,count[10]={0};
	for(i=0;i<n;i++)
		count[(arr[i]/exp)%10]++;//取出某一位,進行10個數字的分配
	for(i=1;i<10;i++)
		count[i]+=count[i-1];//count數組從1到9逐次累加前一項
	for(i=n-1;i>0;i--)
	{
		output[count[(arr[i]/exp)%10]-1]=arr[i];//將arr中的元素按照取餘的梯度直方圖,依次放到合適的位置
		count[(arr[i]/exp)%10]--;//該基數的剩餘空位置相應減少一個 
	} 
	for(i=0;i<n;i++)
		arr[i]=output[i];
}
void radixsort(int arr[],int n)
{
	int m=getMax(arr,n);
	for(int exp=1;m/exp>0;exp*=10)
		countsort(arr,n,exp);
}

4、冒泡排序

簡介:大學中學習 C++ 的第二個排序算法,很經典。

在這裏插入圖片描述
排序機理:冒泡排序顧名思義,如果把這個數組以左邊爲底,右邊爲頂,逆時針旋轉90度,就像一個水中的氣泡,氣泡有什麼特點?在水中基本就是向上勻速浮動的,而且氣泡的體積在不考慮壓強變化的情況下體積不變。在冒泡排序中,氣泡裏面有兩個數字,僅將在氣泡裏面的兩個數字進行大小比較,小的靠左,大的靠右。排完之後,氣泡像上冒一格,也就是向右右移一格。當若n(n爲總元素的個數)個泡泡冒完之後,也就排好序了。

時間複雜度:O(n^2)

穩定性:穩是穩,就是慢了點

代碼

void bubblesort(int a[],int n)//void bubblesort(vector<int>& a)
{
	bool swapp=true;
	while(swapp)
	{
		swapp=false;
		for(int i=0;i<n;i++)//for(size_t i=0;i<a.size()-1;i++) 
		{
			if(a[i]>a[i+1])
			{
				a[i] += a[i+1];
				a[i+1] = a[i]-a[i+1];
				a[i] -= a[i+1];
				swapp=true;
			}
		}
	}
}

5、快速排序

簡介:簡稱快排,時間複雜度不固定,在最壞情況下(元素剛好是反向的)速度比較慢,達到 O(n^2),但如果在比較理想的情況下時間複雜度 O(nlogn)。快排本質也用到分治思想,快排算法每次選擇一個元素並且將整個數組以那個元素分爲兩部分,根據實現算法的不同,元素的選擇一般有如下幾種:

  • 永遠選擇第一個元素
  • 永遠選擇最後一個元素
  • 隨機選擇元素
  • 取中間值

代碼以永遠選擇第一個元素爲例

算法機理

在這裏插入圖片描述
(PS:圖片來源圖解快速排序 - MOBIN - 博客園)

時間複雜度:O(n log n)

穩定性:不穩定,速度一般

代碼

//快速排序
void exchange(int *p,int *q)
{
	int temp=*p;
	*p=*q;
	*q=temp;
}     
void quicksort(int arr[],int left,int right)
{
	if(left>=right)	return ;
	int i=left,j=right,temp=arr[left];
	while(i<j)
	{
		while(i<j&&arr[j]>=temp)	j--;
		while(i<j&&arr[i]<=temp)	i++;
		if(i<j)	exchange(&arr[i],&arr[j]);
	}
	arr[left]=arr[i];//更換下一個參考數 
	arr[i]=temp;//參考數回到i,j之間 
	quicksort(arr,i+1,right);//帶入遞歸 
	quicksort(arr,left,i-1);
}

6、堆排序

簡介:搞清楚堆排序,首先要搞明白幾個概念:最大堆,最小堆,堆源自完全二叉樹,完全二叉樹與滿二叉樹的關係,滿二叉樹與普通二叉樹區別,普通二叉樹與多叉樹的特點,樹的基本屬性,樹與無向不閉合圖的關係。開玩笑的啦,其實這裏面只要理解最大堆就好了,別的就當串聯複習吧。

排序機理:堆排序,顧名思義是在堆的基礎上進行排序。所以首先要創建一個堆,並且把初始堆調整爲最大堆,這樣堆中的最大數就排到了最頂端,然後,只需將最頂端的數與數組中(也是堆中)最後一位調換位置,把調換過位置的最大值稱之爲有序區,接着再繼續把調換之後無序區的堆,調整爲最大堆即可,最後連續進行n-1個循環即可實現堆排序。過程就像下圖一樣(圖來自公衆號:五分鐘學算法)
在這裏插入圖片描述實現效果就像下圖一樣:
在這裏插入圖片描述
時間複雜度:O(n log n)

穩定性:不太穩,速度也還行

代碼

void heapify(int arr[],int n,int i)//不斷調整以保證是最大堆 
{
	int largest=i;
	int l=2*i+1;
	int r=2*i+2;
	if(l<n&&arr[l]>arr[largest])//最大元素向上排 
		largest=l;//本質是數組的下標largest在移動 
	if(r<n&&arr[r]>arr[largest])
		largest=r;
	if(largest!=i)//遞歸處理子堆 
	{
		swap(arr[i],arr[largest]);
		heapify(arr,n,largest); 
	}
}
void heapsort(int arr[],int n)
{
	for(int i=n/2-1;i>=0;i--)//建立堆 
		heapify(arr,n,i);
	for(int i=n-1;i>=0;i--)//n-1次將堆頂最大值與有序區交換 
	{
		swap(arr[0],arr[i]);
		heapify(arr,i,0);
	}
} 

7 、歸併排序

簡介:歸併排序是在前面的排序的基礎上加上了分治思想,分而治之。分到什麼程度?分到直到只剩1,不能再分爲止。然後呢?然後再兩兩相比較來合併成有序的數組,將若干個這樣的數組中的元素兩兩相比,按照大小順序合併。

在這裏插入圖片描述
算法機理:下面用一張動態的直方圖來表示這個過程,直方圖中不同高低代表不同數值大小的元素。先分後後合一目瞭然。另外根據這個圖也說明了,遞歸的本質就是樹。

在這裏插入圖片描述
時間複雜度:O(n log n)

穩定性:穩定,速度一般

代碼

void merge(int arr[],int l,int m,int r)
{
	int i,j,k;
	int n1=m-l+1;//切分 
	int n2=r-m;
	int L[n1],R[n2];
	for(i=0;i<n1;i++)
		L[i]=arr[l+i];
	for(j=0;j<n2;j++)
		R[j]=arr[m+1+j];
	i=0;
	j=0;
	k=l;
	while(i<n1&&j<n2)//合併操作 
	{
		if(L[i]<=R[j])
		{
			arr[k]=L[i];
			i++;
		}
		else
		{
			arr[k]=R[j];
			j++;
		} 
		k++;
	}	
	while(i<n1)//解決兩個數組比較後大小不同問題 
	{
		arr[k]=L[i];
		i++;
		k++;
	} 
	while(j<n2)
	{
		arr[k]=R[j];
		j++;
		k++;
	}
}  
void mergesort(int arr[],int l,int r)
{
	if(l<r)
	{
		int m=l+(r-l)/2;
		mergesort(arr,l,m);
		mergesort(arr,m+1,r);
		merge(arr,l,m,r);
	}	
}

8、選擇排序

簡介:選擇排序是一種很簡單的排序方法,直觀明瞭。也是通過比較並通過循環調換位置來進行排序的。

算法機理
在這裏插入圖片描述

時間複雜度:O(n^2)

穩定性:不穩定,速度慢

代碼

//選擇排序
void selectionsort(int arr[],int length)
{
	int temp,minindex=0;
	for(int i=0;i<length;i++)
	{
		for(int j=i+1;j<length;j++)
			if(arr[j]<arr[minindex])	minindex=j;
		temp=arr[i];
		arr[i]=arr[minindex];
		arr[minindex]=temp;
	}
} 

9、計數排序

簡介:計數排序作爲線性時間複雜度的排序,必須具有明確的數據範圍。計數排序不是基於比較的方法,而是將元素數值按儲存到另外開闢數組空間內。

算法機理

在這裏插入圖片描述

時間複雜度:O(n+k)

穩定性:穩定,速度快

代碼

void countingsort(int arr[],int maxvalue,int length)//計數排序需要提供數據範圍 
{
	int* counting=new int[maxvalue+1]();
	int a=0;
	for(int i=0;i<=length;i++)
		counting[arr[i]]++; 
	for(int i=1;i<=maxvalue;i++)
	{
		int temp=counting[i];
		counting[i]+=counting[i-1]; 
		while(temp--)
			arr[a++]=i;
	}
}

10、桶排序

簡介:桶排序其實就是加強版的計數排序。桶排序的思路就是將一組數據先按照一定的規則放到不同的桶中,桶的分配一般有簡單分桶和規化分桶,

  • 簡單分桶:桶號=max / 10 – min / 10 + 1
  • 規約化分桶:桶號=(array[i] - min) / (max - min) * array.length
    分完桶後,先對桶號排序,再對每個桶內的元素分別排序(這裏有可能使用到別的排序方法或者是以遞歸方式繼續用桶排序)。

排序機理

在這裏插入圖片描述
在這裏插入圖片描述
至於每個桶裏面的排序就不贅述了,直接嵌套別的排序方法即可。

時間複雜度:O(n+k)

穩定性:穩定,速度很快

代碼

int getmax(int arr[],int n)//尋找數組中最大元素 
{
	int mx=arr[0];
	for(int i=1;i<n;i++)
		if(arr[i]>mx)
			mx=arr[i];
	return mx;
}
int getmin(int arr[],int n)//尋找數組中最大元素 
{
	int mi=arr[0];
	for(int i=1;i<n;i++)
		if(arr[i]<mi)
			mi=arr[i];
	return mi;
}
void bucketsort(int arr[],int n)
{
	vector<int> b[n];	//向量b的大小應當≥桶序號bn可能出現的最大值  
	int max=getmax(arr,n);
	int min=getmin(arr,n); 
	for(int i=0;i<n;i++)
	{
		int bn=max/10-min/10+1;
	//	int bn=(arr[i]-min)/(max-min)*10;//簡單分桶 
	//	int bn=arr[i]/(N/100);//歸約化分桶 
	//	cout<<i<<"bn="<<bn<<endl;
		b[bn].push_back(arr[i]);//在序號爲bn的桶中(序列最後)插入目標元素 		
	}
	for(int i=0;i<n;i++)//sort需要include <algorithm> 
		sort(b[i].begin(),b[i].end());
	int index=0;//桶合併 
	for(int i=0;i<n;i++)
		for(int j=0;j<b[i].size();j++)
			arr[index++]=b[i][j]; 
} 

時間複雜度對比

在這裏插入圖片描述

附:調試程序

#include<iostream>
#include<stdlib.h>
#include<time.h>
#include<vector>
#include<cstddef>
#include<algorithm>
#define N 10000
using namespace std;
void insertion_sort (int arr[],int length)
{//插入排序 
	int i,j;
	for(i=1;i<length;i++)
	{
		int temp=arr[i]; 
		for(j=i;j>0&&arr[j-1]>temp;j--)
		{
			arr[j]=arr[j-1];
		}	
		arr[j]=temp;
	}
}
//////////////////////////////////////////////////////////////////
void shellsort(int arr[],int n)
{//希爾排序 
	for(int gap=n/2;gap>0;gap/=2)
	{
		for(int i=gap;i<n;i++)
		{
			int temp=arr[i];
			int j;
			for(j=i;j>=gap&&arr[j-gap]>temp;j-=gap)
				arr[j]=arr[j-gap];
			arr[j]=temp;
		} 
	}
}
////////////////////////////////////////////////////
//基數排序
int getMax(int arr[],int n)//尋找數組中最大元素 
{
	int mx=arr[0];
	for(int i=1;i<n;i++)
		if(arr[i]>mx)
			mx=arr[i];
	return mx;
}
void countsort(int arr[],int n,int exp)
{
	int output[n];
	int i,count[10]={0};
	for(i=0;i<n;i++)
		count[(arr[i]/exp)%10]++;//取出某一位,進行10個數字的分配
	for(i=1;i<10;i++)
		count[i]+=count[i-1];//count數組從1到9逐次累加前一項
	for(i=n-1;i>0;i--)
	{
		output[count[(arr[i]/exp)%10]-1]=arr[i];//將arr中的元素按照取餘的梯度直方圖,依次放到合適的位置
		count[(arr[i]/exp)%10]--;//該基數的剩餘空位置相應減少一個 
	} 
	for(i=0;i<n;i++)
		arr[i]=output[i];
}
void radixsort(int arr[],int n)
{
	int m=getMax(arr,n);
	for(int exp=1;m/exp>0;exp*=10)
		countsort(arr,n,exp);
}
////////////////////////////////////////////////////////////////////////// 

void bubblesort(int a[],int n)//void bubblesort(vector<int>& a)
{
	bool swapp=true;
	while(swapp)
	{
		swapp=false;
		for(int i=0;i<n;i++)//for(size_t i=0;i<a.size()-1;i++) 
		{
			if(a[i]>a[i+1])
			{
				a[i] += a[i+1];
				a[i+1] = a[i]-a[i+1];
				a[i] -= a[i+1];
				swapp=true;
			}
		}
	}
}
////////////////////////////////////////////////////////////////////////////
//歸併排序
void merge(int arr[],int l,int m,int r)
{
	int i,j,k;
	int n1=m-l+1;//切分 
	int n2=r-m;
	int L[n1],R[n2];
	for(i=0;i<n1;i++)
		L[i]=arr[l+i];
	for(j=0;j<n2;j++)
		R[j]=arr[m+1+j];
	i=0;
	j=0;
	k=l;
	while(i<n1&&j<n2)//合併操作 
	{
		if(L[i]<=R[j])
		{
			arr[k]=L[i];
			i++;
		}
		else
		{
			arr[k]=R[j];
			j++;
		} 
		k++;
	}	
	while(i<n1)//解決兩個數組比較後大小不同問題 
	{
		arr[k]=L[i];
		i++;
		k++;
	} 
	while(j<n2)
	{
		arr[k]=R[j];
		j++;
		k++;
	}
}  
void mergesort(int arr[],int l,int r)
{
	if(l<r)
	{
		int m=l+(r-l)/2;
		mergesort(arr,l,m);
		mergesort(arr,m+1,r);
		merge(arr,l,m,r);
	}	
}
/////////////////////////////////////////////////////////////////
//堆排序
void heapify(int arr[],int n,int i)//不斷調整以保證是最大堆 
{
	int largest=i;
	int l=2*i+1;
	int r=2*i+2;
	if(l<n&&arr[l]>arr[largest])//最大元素向上排 
		largest=l;//本質是數組的下標largest在移動 
	if(r<n&&arr[r]>arr[largest])
		largest=r;
	if(largest!=i)//遞歸處理子堆 
	{
		swap(arr[i],arr[largest]);
		heapify(arr,n,largest); 
	}
}
void heapsort(int arr[],int n)
{
	for(int i=n/2-1;i>=0;i--)//建立堆 
		heapify(arr,n,i);
	for(int i=n-1;i>=0;i--)//n-1次將堆頂最大值與有序區交換 
	{
		swap(arr[0],arr[i]);
		heapify(arr,i,0);
	}
} 
/////////////////////////////////////////////////////////////
//桶排序
int getmax(int arr[],int n)//尋找數組中最大元素 
{
	int mx=arr[0];
	for(int i=1;i<n;i++)
		if(arr[i]>mx)
			mx=arr[i];
	return mx;
}
int getmin(int arr[],int n)//尋找數組中最大元素 
{
	int mi=arr[0];
	for(int i=1;i<n;i++)
		if(arr[i]<mi)
			mi=arr[i];
	return mi;
}
void bucketsort(int arr[],int n)
{
	vector<int> b[n];	//向量b的大小應當≥桶序號bn可能出現的最大值  
	int max=getmax(arr,n);
	int min=getmin(arr,n); 
	for(int i=0;i<n;i++)
	{
		int bn=max/10-min/10+1;
	//	int bn=(arr[i]-min)/(max-min)*10;//簡單分桶 
	//	int bn=arr[i]/(N/100);//歸約化分桶 
	//	cout<<i<<"bn="<<bn<<endl;
		b[bn].push_back(arr[i]);//在序號爲bn的桶中(序列最後)插入目標元素 		
	}
	for(int i=0;i<n;i++)//sort需要include <algorithm> 
		sort(b[i].begin(),b[i].end());
	int index=0;//桶合併 
	for(int i=0;i<n;i++)
		for(int j=0;j<b[i].size();j++)
			arr[index++]=b[i][j]; 
} 
///////////////////////////////////////////////////////
 //計數排序
void countingsort(int arr[],int maxvalue,int length)//計數排序需要提供數據範圍 
{
	int* counting=new int[maxvalue+1]();
	int a=0;
	for(int i=0;i<=length;i++)
		counting[arr[i]]++; 
	for(int i=1;i<=maxvalue;i++)
	{
		int temp=counting[i];
		counting[i]+=counting[i-1]; 
		cout<<i<<'\t'<<temp<<'\t'<<counting[i]<<'\t'<<'\t';
		while(temp--)
		{
			arr[a++]=i;
		}
	}
} 
////////////////////////////////////////////////////////////////////////
//選擇排序
void selectionsort(int arr[],int length)
{
	int temp,minindex=0;
	for(int i=0;i<length;i++)
	{
		for(int j=i+1;j<length;j++)
			if(arr[j]<arr[minindex])	minindex=j;
		temp=arr[i];
		arr[i]=arr[minindex];
		arr[minindex]=temp;
	}
} 
///////////////////////////////////////////////////////////////////////////////
//快速排序
void exchange(int *p,int *q)
{
	int temp=*p;
	*p=*q;
	*q=temp;
}     
void quicksort(int arr[],int left,int right)
{
	if(left>=right)	return ;
	int i=left,j=right,temp=arr[left];
	while(i<j)
	{
		while(i<j&&arr[j]>=temp)	j--;
		while(i<j&&arr[i]<=temp)	i++;
		if(i<j)	exchange(&arr[i],&arr[j]);
	}
	arr[left]=arr[i];//更換下一個參考數 
	arr[i]=temp;//參考數回到i,j之間 
	quicksort(arr,i+1,right);//帶入遞歸 
	quicksort(arr,left,i-1);
}
////////////////////////////////////////////////////////////////////////////////
int main()
{//以下注釋默認N=10000 
	int a[N];//幾乎不消耗時間 
	for(int i=0;i<N;i++)//消耗約200ms 
		a[i]=rand()%N;
	for(int i=0;i<N;i++)//消耗約5000ms
		{ 
			cout<<a[i]<<'\t';
			if((i+1)%15==0)	cout<<'\n';
		}
	cout<<endl<<endl;
	clock_t start=clock();//clock返回當前次數  
	//insertion_sort(a,N);// 插入排序消耗約112ms 
	//shellsort(a,N) ;//希爾排序約2ms 
	//radixsort(a,N);//基數排序 4ms
	//bubblesort(a,N);//冒泡排序1157ms
	//mergesort(a,0,N);//歸併排序5ms
	//heapsort(a,N); //堆排序3ms 
	//bucketsort(a,N);//桶排序5ms 
	//countingsort(a,N,N);//計數排序0ms 
	//selectionsort(a,N);//選擇排序204ms
	 quicksort(a,0,N); //快速排序 
	clock_t end =clock();
	for(int i=0;i<N;i++)//消耗約4700ms 
		{
			cout<<a[i]<<'\t';
			if((i+1)%15==0)	cout<<'\n';
		}
	cout<<endl<<"耗時:"<<(double)(end-start)/CLOCKS_PER_SEC<<"s"<<endl; 
	return 0;
 } 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章