Java排序算法总结

常见排序算法:插入排序,冒泡排序,归并排序,快速排序,堆排序等。

插入排序:

基本思想

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应的位置并插入。


插入排序非常类似于整扑克牌。

在开始摸牌时,左手是空的,牌面朝下放在桌上。接着,一次从桌上摸起一张牌,并将它插入到左手一把牌中的正确位置上。为了找到这张牌的正确位置,要将它与手中已有的牌从右到左地进行比较。无论什么时候,左手中的牌都是排好序的。

如果输入数组已经是排好序的话,插入排序出现最佳情况,其运行时间是输入规模的一个线性函数。如果输入数组是逆序排列的,将出现最坏情况。平均情况与最坏情况一样,其时间代价是Θ(n2)。

也许你没有意识到,但其实你的思考过程是这样的:现在抓到一张7,把它和手里的牌从右到左依次比较,7比10小,应该再往左插,7比5大,好,就插这里。为什么比较了10和5就可以确定7的位置?为什么不用再比较左边的4和2呢?因为这里有一个重要的前提:手里的牌已经是排好序的。现在我插了7之后,手里的牌仍然是排好序的,下次再抓到的牌还可以用这个方法插入。编程对一个数组进行插入排序也是同样道理,但和插入扑克牌有一点不同,不可能在两个相邻的存储单元之间再插入一个单元,因此要将插入点之后的数据依次往后移动一个单元。

算法描述

假定n是数组的长度,

首先假设第一个元素被放置在正确的位置上,这样仅需从1-n-1范围内对剩余元素进行排序。对于每次遍历,从0-i-1范围内的元素已经被排好序,

每次遍历的任务是:通过扫描前面已排序的子列表,将位置i处的元素定位到从0到i的子列表之内的正确的位置上。

将arr[i]复制为一个名为target的临时元素。

向下扫描列表,比较这个目标值target与arr[i-1]、arr[i-2]的大小,依次类推。

这个比较过程在小于或等于目标值的第一个元素(arr[j])处停止,或者在列表开始处停止(j=0)。

在arr[i]小于前面任何已排序元素时,后一个条件(j=0)为真,

因此,这个元素会占用新排序子列表的第一个位置。

在扫描期间,大于目标值target的每个元素都会向右滑动一个位置(arr[j]=arr[j-1])。

一旦确定了正确位置j,

目标值target(即原始的arr[i])就会被复制到这个位置。

与选择排序不同的是,插入排序将数据向右滑动,并且不会执行交换。

java代码如下:

//插入排序

public class InsertSort {
	public static void InsertSort(int[] arr){
		int i,j;
		int n=arr.length;
		int target;
		
		//假定第一个元素被放到了正确的位置上,这样仅需要遍历1-n-1次
		for(i=1;i<n;i++){
			j=i;
			target=arr[i];
			
			while(j>0 && target<arr[j-1]){
				arr[j]=arr[j-1];
				j--;
			}
			
			arr[j]=target;
		}
	}

	public static void main(String[] args) {
		int[] arr={4,3,1,2,5};
		InsertSort(arr);
		for(int i=0;i<arr.length;i++){
			System.out.print(arr[i]+" ");
		}

	}

}

效率分析

稳定 
空间复杂度O(1) 
时间复杂度O(n2
最差情况:反序,需要移动n*(n-1)/2个元素 
最好情况:正序,不需要移动元素

数组在已排序或者是“近似排序”时,插入排序效率的最好情况运行时间为O(n)

插入排序最坏情况运行时间和平均情况运行时间都为O(n2)

通常,插入排序呈现出二次排序算法中的最佳性能。

对于具有较少元素(如n<=15)的列表来说,二次算法十分有效。

在列表已被排序时,插入排序是线性算法O(n)。

在列表“近似排序”时,插入排序仍然是线性算法。

在列表的许多元素已位于正确的位置上时,就会出现“近似排序”的条件。

通过使用O(nlog2n)效率的算法(如快速排序)对数组进行部分排序,

然后再进行选择排序,某些高级的排序算法就是这样实现的。


---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

冒泡排序:

在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

public class bubbleSort {
	static void sort(int a[],int n){
		for(int i=0;i<n-1;i++){
			for(int j=0;j<n-i-1;j++){
				if(a[j]>a[j+1]){
				int temp=a[j];
				a[j]=a[j+1];
				a[j+1]=temp;
			}
			}
		}
	}

	public static void main(String[] args) {
		int[] a={12,20,5,16,15,1,30,45};
		int n=a.length;
		sort(a,n);
		for(int i=0;i<n;i++){
			System.out.print(a[i]+" ");
		}

	}

}

冒泡排序算法的改进

传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。

改进后代码如下:

//冒泡算法改进
public class BubbleSort_2 {
	public static void print(int[] arr){
		for(int i=0;i<arr.length;i++){
			System.out.print(arr[i]+"\t");
		}
		System.out.println();
	}
	public static void Bubble_2(int[] arr,int n){
		int low=0;
		int high=n-1;
		int tmp,i;
		while(low<high){
			for(i=low;i<high;i++){//正向冒泡,找到最大值
				if(arr[i]>arr[i+1]){
					tmp=arr[i];
					arr[i]=arr[i+1];
					arr[i+1]=tmp;
				}
			}
				high--;//修改high的值,向前移一位
				for(i=high;i>low;i--){
					if(arr[i]<arr[i-1]){
						tmp=arr[i];
						arr[i]=arr[i-1];
						arr[i-1]=tmp;
					}
				}
				low++;//修改low的值,向后移一位	
		}
	}

	public static void main(String[] args) {
		int[] arr={5, 3, 6, 2, 1, 9, 4, 8, 7};
		int n=arr.length;
		print(arr);
		Bubble_2(arr,n);
		System.out.println("排序后的数组为:");
		print(arr);

	}

}

结果如下:

5	3	6	2	1	9	4	8	7	
排序后的数组为:
1	2	3	4	5	6	7	8	9



---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

归并排序

归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n)),算法不是自适应的,不需要对数据的随机读取。

工作原理:

1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

2、设定两个指针,最初位置分别为两个已经排序序列的起始位置

3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

4、重复步骤3直到某一指针达到序列尾

5、将另一序列剩下的所有元素直接复制到合并序列尾

java代码如下:
//归并排序
public class MergeSort {
	public static void print(int[] arr){
		for(int i=0;i<arr.length;i++){
			System.out.print(arr[i]+"\t");
		}
		System.out.println();
	}

	public static void sort(int[] arr,int left,int right){
		if(left>=right)
			return;
		//找出中间索引
		int center=(left+right)/2;
		//对左边的数组进行递归
		sort(arr,left,center);
		//对右边的数组进行递归
		sort(arr,center+1,right);
		//合并
		merge(arr,left,center,right);
		//打印每次排序结果
		print(arr);
	}
	
	/*
	 * 将两个数组进行归并,归并前两个数组有序,归并后还是有序的
	 * @param arr
	 * 			数组对象
	 * @param left
	 * 			左边数组第一个元素的索引
	 * @param center
	 * 			左边数组最后一个元素的索引,center+1右边数组第一个元素的索引
	 * @param right
	 * 			右边数组最后一个元素的索引
	 * */
	public static void merge(int[] arr,int left,int center,int right){
		//临时数组
		int[] tmpArr=new int[arr.length];
		//右边数组第一个索引
		int mid=center+1;
		//third记录临时数组的索引
		int third=left;
		//临时数组的第一个元素索引
		int tmp=left;
		while(left<=center && mid<=right){
			//从两个数组中取出较小的数放入临时数组
			if(arr[left]<=arr[mid]){
				tmpArr[third++]=arr[left++];
			}else{
				tmpArr[third++]=arr[mid++];
			}
		}
		//生育部分依次放入临时数组(实际上另个while只会执行一个)
		while(left<=center){
			tmpArr[third++]=arr[left++];
		}
		while(mid<=right){
			tmpArr[third++]=arr[mid++];
		}
		//将临时数组中的内容拷贝到原数组中
		while(tmp<=right){
			arr[tmp]=tmpArr[tmp++];
		}
	}
	public static void main(String[] args) {
		int[] arr={5, 3, 6, 2, 1, 9, 4, 8, 7};
		print(arr);
		int left=0;
		int right=arr.length-1;
		sort(arr,left,right);
		System.out.println("排序后的数组:");
		print(arr);
		
		

	}

}
运行结果如下:
5	3	6	2	1	9	4	8	7	
3	5	6	2	1	9	4	8	7	
3	5	6	2	1	9	4	8	7	
3	5	6	1	2	9	4	8	7	
1	2	3	5	6	9	4	8	7	
1	2	3	5	6	4	9	8	7	
1	2	3	5	6	4	9	7	8	
1	2	3	5	6	4	7	8	9	
1	2	3	4	5	6	7	8	9	
排序后的数组:
1	2	3	4	5	6	7	8	9



---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

快速排序

快排算法原理:选择一个关键值作为基准值(一般选择序列的第一个元素)。通过一系列移动后,比基准值小的都在左边序列(一般是无序的),比基准值大的都在右边(一般是无序的)。然后利用递归,将两边序列都分别排序。

一次循环:从后往前比较,用基准值和最后一个值比较,如果比基准值小的交换位置,如果没有继续比较下一个,直到找到第一个比基准值小的值才交换。找到这个值之后,又从前往后开始比较,如果有比基准值大的,交换位置,如果没有继续比较下一个,直到找到第一个比基准值大的值才交换。直到从前往后的比较索引>从后往前比较的索引,结束第一次循环,此时,对于基准值来说,左右两边就是有序的了。

接着分别比较左右两边的序列,重复上述的循环。

如下图所示:


java代码实现如下:

public class QuickSort {
	public static void sort(int [] a,int low,int high){
		int start=low;
		int end=high;
		int key=a[low];
		
		while(end>start){
			//从后面往前比较
			while(end>start && a[end]>=key)//如果没有比关键值小的,比较下一个,直到有比关键值小
											//的交换位置,然后又从前往后比较
				end--;
			if(a[end]<=key){
				int temp=a[end];
				a[end]=a[start];
				a[start]=temp;
			}
			//从前往后比较
			while(end>start && a[start]<=key)//如果没有比关键值大的,比较下一个,直到有比关键值大
											//的交换位置
				start++;
			if(a[start]>=key){
				int temp=a[start];
				a[start]=a[end];
				a[end]=temp;
			}
			//此时第一次循环比较结束,关键的位置已经确定。左边的值都比关键值小,右边的值都比关键值大,
			//但是两边可能还是不是排序好的,下面将两边分开进行递归调用。
		}
		//递归调用
		if(start>low) sort(a,low,start-1);
		if(end<high) sort(a,end+1,high);
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a={12,20,5,16,15,1,30,45};
		int low=0;
		int high=a.length-1;
		sort(a,low,high);
		for(int i=0;i<a.length;i++){
			System.out.print(a[i]+" ");
		}

	}

}


快排的时间复杂度,最好的情况下是O(nlgn),最坏的情况下位O(n^2)。当待排序数据基本无序时,快排的速度是最快的,而当数据是基本有序时,其反而是最慢的。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

堆排序


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

总结:

时间复杂度来说:

(1)平方阶(O(n2))排序
  各类简单排序:插入排序和冒泡排序;
 (2)线性对数阶(O(nlog2n))排序
  快速排序、堆排序和归并排序

说明:

当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至On);

而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为On2);

原表是否有序,对简单堆排序、归并排序的时间复杂度影响不大。

 

稳定性:

排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。 
     稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较;

稳定的排序算法:冒泡排序、插入排序、归并排序

不是稳定的排序算法:快速排序、堆排序

 

选择排序算法准则:

每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。

选择排序算法的依据

影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:

1.待排序的记录数目n的大小;

2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;

3.关键字的结构及其分布情况;

4.对排序稳定性的要求。

设待排序元素的个数为n.

1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

   快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
       堆排序 :  如果内存空间允许且要求稳定性的,

       归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。

2)  当n较大,内存空间允许,且要求稳定性——>归并排序

3)当n较小,可采用插入排序。

    插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。

4)一般不使用或不直接使用传统的冒泡排序。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值