所有排序算法汇总,时间空间效率

在这里插入图片描述

排序方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(n^2)O(n)O(n^2)O(1)稳定
简单选择排序O(n^2)O(n^2)O(n^2)O(1)稳定
直接插入排序O(n^2)O(n)O(n^2)O(1)稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
快速排序O(nlogn)O(nlogn)O(n^2)O(logn)~O(n)不稳定
希尔排序O(nlogn)~O(n^2)O(n^1.3)O(n^2)O(1)不稳定

插入排序
分为直接插入排序、折半插入排序和希尔排序
直接插入排序:

最好情况:已有序,没插入一个元素只需要比较一次,时间复杂度O(n)
最坏情况:刚好逆序,比较次数最大2+3+…+n,移动次数(2+1)+(3+1)+…+ (n+1)
(加一是从哨兵位置移动到插入位置)
平均情况:比较次数与移动次数都是(n^2)/4
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定的排序方法
适用:顺序存储和链式存储

折半插入排序:

比较次数约为O(nlog2(n)),比较次数与待排序表的初始状态无关,仅取决于元素个数n。但移动次数未变。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定的排序方法

希尔排序:

时间复杂度:依赖于增量序列的函数,当n在某个特定范围时,时间复杂度为O(n^1.3), 最坏情况下为O(n^2)
空间复杂度:O(1)
不稳定的排序方法
适用:仅适用于线性表为顺序存储

冒泡排序

时间效率:可以设置flag来决定跳出循环,最好情况下时间复杂度为O(n)。最坏情况是初始为逆序,需要进行 n-1 趟排序,第i趟排序需要进行 n-i 次比较,而且每次比较后,必须移动三次来交换元素位置。这种比较次数n(n-1)/2,移动次数3n(n-1)/2,最坏时间复杂度为O(n^2)
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定的排序方法

快速排序:
采用的是分治的思想,首先将待排序的数组中选择一个基准,经过一次快排将数组的元素分为两部分,以基准数为中心,左边元素小于基准数,右边的元素大于基准数。递归直到排序完成。
快排的平均时间复杂度是nlogn级,当数组元素为随机的非常多时,快排在这些排序方法中是最快的,当数组的元素重复数比较多,最极限就是所有元素相等的时候,触发快排的最坏情况,时间复杂度为n^2级。
对快排有多个优化情况:
首先就是对基准数的选取,最基础的快排一般选取第一个数或者最后一个数作为基准数。
第一种优化是随机化基准数,若数组本来就是顺序的,但快排就会退化成冒泡,也会触发快排的最坏情况。从数组中随机选取一个数作为基准数,之后将这三个数中的中间大的数交换到数列首位的位置,之后将这个数作为基准点,这种使数组基准数左右元素个数更加平均。
第二种优化是三数取中法:指的是选取基准点之前我们可以拿出数列中间位置元素的值,将它和首尾的元素进行比较,之后将这三个数中的中间大的数交换到数列首位的位置,之后将这个数作为基准点,尽量减小之后的分区后左右两边的区间长度之差。
第三种优化是当待排序序列长度分割到一定大小后,使用插入排序。对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排。

if (high - low + 1 < 10)  
{  
    InsertSort(arr,low,high);  
    return;  
}//else时,正常执行快排  

第四种优化是聚拢重复元素,是针对重复的元素,极限情况,当数组的元素全是想等的数时,触发最坏情况,对于这种可以先将与基准数相等元素移到两端,结束后,再将元素移回基准元素左右。思想比较容易理解,但是代码中有很多细节注意。
随机化基准数的快排代码:

#include <iostream>
#include<stdlib.h>
#include<time.h>
using namespace std;
int SelectPivotRandom(int a[],int low,int high) {
	srand((unsigned)time(NULL));
	int pivotPos = rand()%(high - low) + low;
	swap(a[pivotPos],a[low]);
	return a[low];
}
void quicksort(int low,int high,int a[]) {
	if(low>=high)
		return;
	int i=low,j=high;
	int w=SelectPivotRandom(a,i,j);
	while(i<j) {
		while(i<j&&a[j]>=w)
			j--;
		if(i<j) {
			a[i]=a[j];
			i++;
		}
		while(i<j&&a[i]<=w)
			i++;
		if(i<j) {
			a[j]=a[i];
			j--;
		}
	}
	a[i]=w;
	quicksort(low,i-1,a);
	quicksort(i+1,high,a);
}
int main() {
	int N;
	cin>>N;
	int a[100000];
	for(int i=0; i<N; i++)
		cin>>a[i];
	quicksort(0,N-1,a);
	for(int i=0; i<N; i++)
		cout<<a[i]<<" ";
}

空间效率:由于快排是递归的,需要栈的空间容量与最大深度是一致的,最好的情况下是O(logn);最坏需要进行n-1次递归调用,深度为O(n);
平均空间复杂度:O(logn)
时间效率:快排运行时间与划分的对称有关,最坏情况下最大程度不对称即初始排序表基本有序或者基本逆序,此时为O(n^2)。最好情况平衡划分,此时为O(nlogn)。
平均时间复杂度:O(nlogn)
快排是所有内部排序算法中平均性能最优的。
不稳定的排序

选择排序:

简单选择排序:


#include<stdio.h>
void SelectionSort(int *num,int n) {
	int i = 0;
	int min = 0;
	int j = 0;
	int tmp = 0;
	for(i = 0; i < n-1; i++) {
		min = i;//每次将min置成无序组起始位置元素下标
		for(j = i; j < n; j++) { //遍历无序组,找到最小元素。
			if(num[min]>num[j]) {
				min = j;
			}
		}
		if(min != i) { //如果最小元素不是无序组起始位置元素,则与起始元素交换位置
			tmp = num[min];
			num[min] = num[i];
			num[i] = tmp;
		}
	}
}
int main() {
	int num[6] = {5,4,3,2,9,1};
	int i = 0;
	SelectionSort(num,6);//这里需要将数列元素个数传入。可用sizeof在函数内求得元素个数。
	for(i = 0; i < 6; i++) {
		printf("%d ",num[i]);
	}
	return 0;
}

空间效率:O(1)
时间效率:元素移动次数最少,不会超过3(n-1)次,最好情况是0次,此时对应表有序。元素间的比较次数与序列的初始状态无关,始终是n(n-1)/2。
时间复杂度:O(n^2)
不稳定的排序

堆排序:
这种排序采用构造大顶堆的方式进行排序,对于一个数组,首先将它们构造成一个大顶堆,然后将根元素与底部最后一个元素相交换即将第一个元素与最后一个元素交换,然后输出最后一个元素,并删除。然后继续构造剩下元素成为大顶堆,重复以上活动,直到所有元素输出。
堆排序每次取一个最大值和堆底部的数据交换,重新筛选堆,把堆顶的X调整到位,有很大可能是依旧调整到堆的底部(堆的底部X显然是比较小的数,才会在底部),然后再次和堆顶最大值交换,再调整下来,可以说堆排序做了许多无用功。

//堆排序 
//索引为i的左孩子的索引是 (2*i+1);
//索引为i的右孩子的索引是 (2*i+2);
//索引为i的父结点的索引是 ((i-1)/2);
#include<iostream>
using namespace std;
void p(int a[],int i,int N){
	int child;
	int temp=a[i];
	for(;2*i+1<N;i=child){
		child=2*i+1;//数组下标是从0开始的,所以左孩子的不是2*i
		if(2*i+1!=N-1&&a[child+1]>a[child]) //找到最大的儿子节点
			child++;
		if(temp<a[child])
			a[i]=a[child];
		else break;
	}
	a[i]=temp;
}
void heapsort(int a[],int N){
	for(int i=N/2;i>=0;i--)
		p(a,i,N);//构造堆
	for(int i=N-1;i>0;i--){ //将最大元素(根)与数组末尾元素交换,从而删除最大元素,重新构造堆
		int temp=a[0];
		a[0]=a[i];
		a[i]=temp;
		p(a,0,i);
	}
}
int main()
{
	int N;
	cin>>N;
	int a[100000];
	for(int i=0;i<N;i++)
		cin>>a[i];
	heapsort(a,N);
	for(int i=0;i<N;i++)
		cout<<a[i]<<" ";
}

空间复杂度:O(1)
时间效率:建堆时间为O(n),之后有n-1次想下调整的操作,每次调整的时间复杂度为O(h),最坏最好平均情况下,时间复杂度是O(nlogn)
不稳定的排序

归并排序:
2路归并排序
采用分治思想,先分后合,与快速排序还是不同的,归并排序将数组元素等分,直到无法分割为止,然后再比较同一等级的相邻两组(即由同一组分成的两组数据)数据,根据大小将它们合并成一组直到所有组全部合并成功,完成排序。归并排序也是一个效率较高的排序方法。

#include<iostream>
using namespace std;
void merge(int a[],int left,int right,int temp[]) {
	if(left<right) {
		int mid=(left+right)/2;
		merge(a,left,mid,temp);//分割
		merge(a,mid+1,right,temp);//分割
		int i=left,j=mid+1;
		int p=0;
		//合并
		while(i<=mid&&j<=right){
			if(a[i]>a[j]){
				temp[p]=a[j];
				p++;j++;				
			}
			else {
				temp[p]=a[i];
				p++;i++;
			}
		}
		while(i<=mid){
			temp[p]=a[i];
			p++;i++;
		}
		while(j<=right){
			temp[p]=a[j];
			p++;j++;
		}
		for(int z=0;z<=right-left;z++){
			a[left+z]=temp[z];			
		}
	}else return;
}
int main() {
	int N;
	cin>>N;
	int a[100000];
	int temp[100000];
	for(int i=0; i<N; i++)
		cin>>a[i];
	merge(a,0,N-1,temp); 
	for(int i=0; i<N; i++)
		cout<<a[i]<<" ";
}

空间复杂度:O(n)
时间复杂度:每趟归并的时间复杂度是O(n),共需要进行[logn]次,时间复杂度是O(nlogn)
稳定的排序方法

基数排序

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值