排序算法总结(选择、冒泡、插入、希尔、堆、归并、快速排序)

根据浙大数据结构慕课和《算法设计与分析基础》整理

规定按照从小到大顺序排序。

前提:

void X_Sort(int A[ ], int N)

主函数:

int main(){
	int N;
	cin>>N;
	int a[N];
	for(int i = 0;i<N;i++){
		cin>>a[i];
	}
   X_Sort(a,N);//排序函数
	cout<<a[0];
	for(int i = 1;i<N;i++){
		cout<<' '<<a[i];
	}
}

一、选择排序

扫描整个列表,找到它的最小元素,和第一个元素交换,将最小元素放到它在有序表的最终位置。之后从第二个元素开始扫描,找到第二小的元素,和第二个元素交换位置……在进行(n-1)遍扫描后,列表排好序。
代码如下:

void Selection_Sort(int A[],int N)
{
	int i,j;
	int minDex;//最小元素的索引 
	for(i = 0;i<N-1;i++){
		minDex = i;
		for(j = i+1;j<N;j++){
			if(A[j]<A[minDex]){
				minDex = j;
			}
		}
		int t = A[i];
		A[i] = A[minDex];
		A[minDex] = t;
	}
}

对于任何输入,选择排序都要进行一样的双重循环,其时间复杂度为O(n2
但是,其键的交换次数少,仅交换(n-1)次便能完成排序。
选择排序并不稳定。

二、冒泡排序

比较表中相邻元素,如果它们是逆序就交换它们的位置,重复多次后,最大的元素就沉到列表的最后一个位置。第二篇将列表的第二大元素沉下去,一直这样做,到第n-1遍后,列表排好序。
代码如下:

void Bubble_Sort(int A[],int N)
{
	for(int i = N-1;i > 0;i--){//i是一趟冒泡排序最后比到的那个数 
		int flag = 0;
		for(int j = 0;j<i;j++){
			if(A[j]>A[j+1]){
				int t = A[j];
				A[j] = A[j+1];
				A[j+1] = t;
				flag = 1;
			}
		}
		if(flag==0){
			return;//如果一趟冒泡排序全程无交换,则已经有序了 
		}
	}
}

分析可知,设置flag后的最好情况:序列一开始即是有序的,时间复杂度为O(N);
最坏情况:逆序,时间复杂度O(N2)
平均时间复杂度:O(N2)

冒泡排序是稳定的。

三、插入排序

类似于玩家在玩牌时将抽到的牌插入到之前抽到的牌中,插入排序从A[1]开始到A[n-1]为止,A[i]被插入到数组的前i个有序元素的适当位置上(此位置一般并不是它们的最终位置)。
代码如下:

void Insertion_Sort(int A[],int N)
{
	int i,j;
	for(i = 1;i<N;i++){
			int t = A[i];
		for(j = i-1;j>=0;j--){
			if(t<A[j]){
				A[j+1] = A[j];
			}else{
				break;
			}
		}
		A[j+1] = t;
	}
}

插入排序是稳定的。
对算法平均效率的精确分析主要基于对逆序对的研究。
冒泡排序和插入排序每次交换2个元素都正好消除一个逆序对,若数列中有N个逆序对,则交换N次。
I 为逆序对的个数,插入排序的时间复杂度T(N,I)= O(N+I),所以插入排序在遇到基本有序数组时表现出优越的性能(如果逆序对个数和N同一数量级,则时间复杂度为线性),使得插入排序领先于冒泡排序和选择排序。
但是定理:任意N个不同元素组成的序列平均具有
N(N-1)/4个逆序对。所以任何仅以交换相邻两元素来排序的算法,其平均时间复杂度为Ω(N2

所以,插入排序:
最坏情况:逆序,O(N2);
最好情况:顺序:O(N)
平均情况:O(N2)

四、希尔排序

相较于插入排序,为了提高算法效率,每次交换相隔较远的两个元素。
在这里插入图片描述
希尔排序中要注意选取增量元素。若增量元素不互质,则小增量可能根本不起作用。
复杂度问题很复杂,不好讨论。
希尔排序不稳定。

void Shell_Sort(int A[],int N)
{
	int S_array[] = {121,40,13,4,1,0};
	int i,j,k,Tmp;
	int D;//D是间隔 
	
	//初始增量S_array[k]的长度不能超过待排序列的长度 
	for(k=0;S_array[k]>=N;k++);

	for(D = S_array[k];D>0;D = S_array[k++]){//首先规定好此次排序的间隔
	      //这里是k++不是k+1。。。k变量要自增,k+1没有改变k!除非写成K = K+1 
		for(i = D;i<N;i++){//注意是i++ ,插入排序(间隔为D) 
			Tmp = A[i];
			for(j = i-D;j>=0;j = j-D){
				if(Tmp<A[j]){
					A[j+D] = A[j];
				}else{
					break;
				}
			}
			A[j+D] = Tmp;
		} 	
	}
}

五、堆排序

选择排序的改进。选择排序中键的交换次数少,但是选择最小值的过程复杂,所以利用最小堆去选择最小值可以改进选择排序。
堆排序过程:首先建立一个最大堆,之后一个个删除堆顶的最小值元素(将堆顶元素移到最后面,即排好序的数组的最终位置),之后重新调整剩下的堆为最大堆。
时间复杂度无论最好最坏都是O(N*logN)
堆排序不稳定。
代码如下:

void Heap_Sort(int A[],int N)
{
	int i;
	for(i = N/2-1;i>=0;i--){
		PercDown(A,i,N);//调整成最大堆 
	}
	for(i=N-1;i>0;i--){//i即删除前最后一个元素的下标 ,删除后的堆元素的个数 
		int tmp = A[0];
		A[0] = A[i];
		A[i] = tmp;//交换最大值(即A[0])和数组最后一个元素 
		PercDown(A,0,i);//把剩下的元素调整成最大堆 
	}
}

void PercDown(int A[],int p,int N)
{//将N个元素数组中以A[p]为跟的堆调整成最大堆 
	int Parent,Child;
	int X = A[p];
	
	for(Parent = p;Parent*2+1<N;Parent = Child){
		Child = Parent*2+1;
		if((Child!=(N-1))&&A[Child]<A[Child+1]){
			Child++;
		}
		if(X<A[Child]){
			A[Parent] = A[Child];	
		}else{
			break;
		}
	}
		A[Parent] = X;
 } 

六、归并排序

对于一个需要排序的数组A,归并排序将其一分为二,并对每个子数组归并排序,然后把两个排好序的子数组合并为一个有序数组存在临时数组TmpA里面,然后把TmpA导回A当中,一步步向上递归。(空间上此算法多开辟了一个数组长度的空间)
归并排序的难点在于合并成一个有序数组,其时间复杂度为O(N)。所以归并排序的时间复杂度公式为T(N) = 2*T(N/2) + O(N),计算得平均时间复杂度为O(NlogN)。最坏情况:最小元素轮流来自于不同的数组。但即使是最坏情况也是O(NlogN) (无论最好最坏都是O(NlogN))
归并排序具有稳定性。

void Merge_Sort(int A[],int N){
	int *TmpA = new int(N);
	M_Sort(A,TmpA,0,N-1);

} 
void M_Sort(int A[],int TmpA[],int L,int RightEnd)
{
	int Center;
	if(L<RightEnd){
		Center = (L+RightEnd)/2;
		M_Sort(A,TmpA,L,Center);
		M_Sort(A,TmpA,Center+1,RightEnd);
		Merge(A,TmpA,L,Center+1,RightEnd);
	}	
}

void Merge(int A[],int TmpA[],int L,int R,int RightEnd)
{
	int LeftEnd = R-1;
	int i = L;
	int Num = RightEnd - L +1; 
	while((L<=LeftEnd)&&(R<=RightEnd)){
		if(A[L]<=A[R]){
			TmpA[i] = A[L]; 
			L++;
			i++;
		}else{
			TmpA[i] = A[R];
			R++;
			i++;
		}
	}
	
		while(R<=RightEnd){
			TmpA[i] = A[R];
			R++;i++;
		}
	
		while(L<=LeftEnd){
			TmpA[i] = A[L];
			L++;i++;
		}
	
	for(i = 0;i<Num;i++,RightEnd--){
		A[RightEnd] = TmpA[RightEnd];
	}//将TmpA中数值导入A中 
	
}

七、快速排序

和归并排序一样,也是分治法。但是它不是按照元素位置划分,而是按照元素的值进行划分。归并排序的重点在于归并,快速排序的重点在于划分。即找到一个元素,通过划分令该元素左边的值都小于此元素,元素右边的值都大于此元素,这时就确定了此元素的最终位置(因此快速排序的交换次数也少,所以比较快)。
快速排序最好的情况:所有的分裂点都位于相应子数组的中点。
快速排序最坏的情况:所有的分裂点都趋于极端(顺序或逆序),此时的时间复杂度:O(N2
当问题规模较小(N<100)时,快排可能不如插入排序快,因此可以在规模较小时将快排转化成插入排序。
快速排序不稳定。

void Quick_Sort(int A[],int N){//接口 
	Q_Sort(A,0,N-1);
}

void Q_Sort(int A[],int L,int R)
{
	int Cutoff = 50;//阈值 
	int Pivot;
	if((R-L)>Cutoff){
		Pivot = Median3(A,L,R);
		int i = L;int j = R-1;
		for(;;){
			//Median3的好处不仅令主元选取更科学,
			//还在边界设置了哨兵,不用担心i,j越界 
			while(A[++i]<Pivot){
			
			}
			while(A[--j]>Pivot){
			
			}
			if(i<j){
				Swap(A[i],A[j]);
			}else{
				break;
			}	
		}
		Swap(A[i],A[R-1]);
		Q_Sort(A,L,i-1);
		Q_Sort(A,i+1,R);
	}
	else{
		Insertion_Sort(A+L,R-L+1);
	}
}
void Insertion_Sort(int A[],int N){
	 int i;int j;
	 int tmp;
	 for(i = 1;i<N;i++){
	 	tmp = A[i];
	 	for(j = i-1;j>=0;j--){
	 		if(tmp<A[j]){
	 			A[j+1] = A[j];
			 }else{
			 	break;
			 }
		 }
		 A[j+1] = tmp;
	 }
}
int Median3(int A[],int L,int R)
{
	int tmp;
	int Center = (L+R)/2;
	if(A[L]>A[Center]){
		Swap(A[L],A[Center]);
	}
	if(A[L]>A[R]){
		Swap(A[L],A[R]);
	}
	if(A[Center]>A[R]){
		Swap(A[Center],A[R]);
	}
	//此时左边数最小,右边数最大 
	Swap(A[Center],A[R-1]);
	//只需考虑A[l+1]与A[R-1]之间的数
	return A[R-1]; 
	
} 

void Swap(int &a,int&b){
	int tmp = a;
	a = b;
	b = tmp;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值