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

旧街凉风 提交于 2019-11-29 08:59:29

对科研中常见的几种排序方法做个总结,方便日后查看与回顾。(对本人认为比较简单易懂的排序方法过程比较简略,对比较复杂的排序方法过程就比较详细)

编程语言:C++(经过本人验证,可以正常运行)。

一、直接插入排序(稳定):

思想:“一趟一个“地将待排序记录插入到已经排好序的部分记录的适当位置中,使其成为一个新的有序序列,直到所有待排序记录全部插入完毕。

分析:

空间:仅需使用一个辅助单元。故空间复杂度为O(1);

最好情况:待排序序列已经有序,每趟操作只需比较1次和移动0次,此时,总的比较次数为n-1,总移动次数为0,故最好情况算法复杂度为O(n);

最坏情况:待排序序列按逆序排序,这时在第j趟操作中,为插入元素需要同前面的j个元素比较,移动元素的次数为j+1.此时有:总的比较次数=1+2+...+(n-1)=n(n-1)/2;总移动次数=2+3+4+...+n=(n+2)(n-1)/2,故最坏情况算法复杂度为O(n^2);

平均情况:在第j趟操作中,插入记录大约需要同前面j/2个元素比较,移动记录的次数为j/2+1次,此时总的比较次数约为n^2/4;总移动次数为n^2/4;故时间复杂度为O(n^2);

代码:

void insertSort(int *a,int low,int high)//插入排序 
{
	for(int i=low+1;i<high;i++)
	{
		if(a[i]<a[i-1])
		{
			int tmp=a[i];
			a[i]=a[i-1];
			int j=i-2;
			for(;j>=low&&tmp<a[j];j--)
				a[j+1]=a[j];
			a[j+1]=tmp;
		}
	}
}
二、二分插入排序(稳定):

思想:与直接插入类似,在找寻插入位置时采用二分查找方式;

分析:与直接插入相同,仅仅减少了元素的比较次数,并没减少元素的移动次数,因此仍为O(n^2);

代码:

void binaryInsertSort(int *a,int low,int high)//二分插入排序 
{
	for(int i=low+1;i<high;i++)
	{
		int tmp=a[i];
		int hi=i-1;
		int lo=low;
		while(lo<=hi)
		{
			int mid=(lo+hi)/2;
			if(a[mid]>tmp) hi=mid-1;
			else lo=mid+1;
		}
		for(int j=i-1;j>hi;j--)
		{
			a[j+1]=a[j];
		}
		a[hi+1]=tmp;
	}
}
三、希尔排序(不稳定):

思想:”基本有序化“可以提升插入排序的效率,将记录序列分成若干子序列,每个子序列分别进行插入排序。需要注意的是,这种子序列是由间隔为某个增量的一组数据组成,即跳跃式选择数据组。(这个解释起来比较抽象,面试中也很少问,但编程贼简单)

分析:时间复杂度:开始时增量较大,分组较多,每组的记录数目较少,当排序规模n值较小时,n和n^2的差距也较小,但随着增量d逐渐缩小,分组逐渐减小,而各组的记录数目逐渐增多,但由于已经基本有序,所以新的一趟也较快。时间复杂度与增量的设置有关,约在O(n^1.25)和O(n^1.6)之间;

代码:

void shellInsert(int *a,int low,int high,int deltaK)//希尔排序 
{
	for(int i=low+deltaK;i<high;i++)
	{
		if(a[i]<a[i-deltaK])
		{
			int tmp=a[i];
			int j=i-deltaK;
			for(;j>=low&&tmp<a[j];j=j-deltaK)
				a[j+deltaK]=a[j];
			a[j+deltaK]=tmp;
		}
	}
} 
void shellSort(int *a,int low,int high,int *delta,int m)//希尔排序 
{
	for(int k=0;k<m;k++)
	{
		shellInsert(a,low,high,delta[k]);	
	}	
}
四、冒泡排序(稳定):

思想:贼简单,对n个数进行n-1次扫描,每次扫描比较相邻两个数字,大的移到后面。这样每趟排序会把最大的数字扔到最后面;

分析:

空间:需要一个辅助单元,故空间复杂度为O(1);

最好情况:数据全部排好序,这时循环n-1次,时间复杂度为O(n),但是程序中可以设置flag,某一次未发生位置交换,程序退出;

最坏情况:数据全部逆序存放,这时循环n-1次,比较次数为n-1+n-2+...+1=n(n-1)/2;移动次数为3*(n-1+n-2+...+1)=3n(n-1)/2,时间复杂度为O(n^2);

平均情况:与直接插入分析一样,时间复杂度为O(n^2);

代码:

void bubbleSort(int *a,int low,int high)//冒泡排序普通实现 
{
	int n=high-low;
	for(int i=0;i<n-1;i++)
	{
		int flag=0;
		for(int j=0;j<n-i-1;j++)
		{
			if(a[j]>a[j+1])
			{
				flag=1;
				int tmp=a[j];
				a[j]=a[j+1];
				a[j+1]=tmp;
			}
		}
		if(flag==0)
			break;
	}
}
这里有个小插曲,有一次腾讯面试,让用递归实现冒泡,瞬间懵逼。

代码(冒泡排序的递归实现):

void bubbleSort2(int *a,int low,int high)//冒泡排序递归实现 
{
	if(high==1)
		return;
	for(int i=0;i<high-1;i++)
	{
		if(a[i]>a[i+1])
		{
			int tmp=a[i];
			a[i]=a[i+1];
			a[i+1]=tmp;
		}
	}
	bubbleSort2(a,low,--high);
} 
五、快速排序(不稳定):

思想:每次选择一个元素放置到它应该在的位置,划分原来序列为前面的数都不大于它,后面的数都不小于它;

分析(消耗的时间空间主要在递归上):

空间:最好情况,每次取到的元素刚好平分整个数组,此时空间复杂度为O(log n);最坏情况,每次取到的就是数组的最大或最小的,这种情况就是冒泡排序,此时空间复杂度为O(n),所以空间复杂度为O(log n)~O(n);

最好情况:相当于一个完全二叉树结构,即每次标准元素都把当前数组分成两个大小相等的子数组,这是分解次数等于完全二叉树的深度lb n;每次快排过程无论把数组怎样划分,全部的比较次数都接近于n-1次,所以时间复杂度为O(n lb n);

最坏情况:退化成冒泡排序,时间复杂度为O(n^2);

平均情况:时间复杂度为O(n lb n);

代码:

void quickSort(int *a,int low,int high)//快速排序 
{
	int i=low;
	int j=high-1;
	int tmp=a[low];
	while(i<j)
	{
		while(i<j&&tmp<=a[j]) j--;
		if(i<j)
		{
			a[i]=a[j];
			i++;
		}
		while(i<j&&tmp>=a[i]) i++;
		if(i<j)
		{
			a[j]=a[i];
			j--;
		}
	}
	a[i]=tmp;
	if(low<i) quickSort(a,low,i-1);
	if(i<high) quickSort(a,i+1,high);
}
六、直接选择排序(不稳定):

思想:巨简单。在每趟过程中将待排序数组最小数放到最前面。不多解释了。

分析:

空间:需要一个辅助单元,故O(1);

最好最坏情况:无论初始状态如何,都需要进行n-1+n-1+...+1=n(n-1)/2次比较,所以时间复杂度为O(n^2);

代码:

void selectSort(int *a,int low,int high)//直接选择排序 
{
	int n=high-low;
	for(int i=0;i<n-1;i++)
	{
		int min=i;
		for(int j=i+1;j<n;j++)
		{
			if(a[min]>a[j])
				min=j;	
		}	
		if(min!=i)
		{
			int tmp=a[min];
			a[min]=a[i];
			a[i]=tmp;
		}
	} 
}
七、堆排序(不稳定):

思想:把待排序的数据元素构造成一个完全二叉树结构,则每次选择出一个最大的数据元素只需比较完全二叉树的高度次,即lb n次,则排序算法的最好最坏时间复杂度均为O(n lb n);

空间:O(1);

这个比较复杂,具体的讲解可以参考博文:http://www.cnblogs.com/skywang12345/p/3602162.html

代码:

void heapAdjust(int *a,int low,int high)//堆排序,调整堆 
{
	int tmp=a[low];
	for(int j=2*low+1;j<high;j=j*2+1)
	{
		if(j<high-1&&a[j]<a[j+1]) j++;
		if(tmp>=a[j]) break;
		a[low]=a[j];
		a[j]=tmp;
		low=j;
	}
	//a[low]=tmp;
}
void heapSort(int *a,int high)//堆排序 
{
	for(int i=(high)/2-1;i>=0;i--)
		heapAdjust(a,i,high);

	for(int i=high-1;i>0;i--)
	{		
		int tmp=a[0];
		a[0]=a[i];
		a[i]=tmp;
		heapAdjust(a,0,i);
	}		
} 
八、归并排序(稳定):

思想:将若干个已经排好序的子序列合并成一个有序的序列。

分析:

时间:归并的次数约为lb n,任何一次的归并比较次数约为n-1,所以最好最坏时间复杂度均为O(n lb n);
空间:需要一个与原数组大小一样的辅助数组,故空间复杂度为O(n).

代码:

void merge(int *a,int p,int q,int r)//一次归并算法 
{
	int n1=q-p+1;
	int n2=r-q;
	int *L=new int[n1+1];
	int *R=new int[n2+1];
	for(int i=0;i<n1;i++)
	{
		L[i]=a[p+i];
	}
	for(int j=0;j<n2;j++)
	{
		R[j]=a[q+j+1];
	}
	L[n1]=1000000;
	R[n2]=1000000;
	for(int i=0,j=0,k=p;k<=r;k++)
	{
		if(L[i]<=R[j])
		{
			a[k]=L[i];
			i++;
		}
		else
		{
			a[k]=R[j];
			j++;
		}
	}
	delete []L;
	delete []R;
}
void mergepass(int *a,int p,int r)//二路归并 
{
	if(p<r)
	{
		int q=(p+r)/2;
		mergepass(a,p,q);
		mergepass(a,q+1,r);
		merge(a,p,q,r);	
	}	
} 


总结:具体的算法过程以后有时间会补上哒~但面试前看看这些还是挺有效果的呢!

最后补上一张各种排序算法的对比:















易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!