Java算法学习——排序算法(冒泡排序,快速排序)

排序算法

定义:

对一序列对象根据某个关键字进行排序。

衡量排序算法的指标:

时间复杂度:一个算法执行所耗费的时间。
空间复杂度:运行完一个程序所需内存的大小。
稳定性:如果一个序列中有两个相等的对象(A=B),此时A排在B前面,排序之后A仍在B的前面,则该排序算法稳定。反之,则不稳定。
内排序:所有排序操作都在内存中完成。
外排序:由于数据太大,因此把数据放到磁盘里,而排序需要通过内存和磁盘的数据传输才能够进行。

排序算法分类:

排序算法可以按以下方法分成两大类:

比较排序:快速排序,冒泡排序,选择排序,归并排序,堆排序。
在比较排序中,元素之间的次序依赖于他们之间的比较。每个数都要与其他数进行比较,才能确定自己的位置。
如:在冒泡排序中,问题规模为n,又需要进行n次比较,所以冒泡排序的平均时间复杂度为O(n2)。而在归并和快排的排序中,问题规模使用分治法消减到logn次。所以平均时间复杂度为O(nlogn)

比较排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都可以进行排序。也就是说,比较排序适用于一切需要排序的情况。

非比较排序:计数排序,桶排序,基数排序。
非比较排序是通过确定每个元素之前应该有多少个元素来进行排序的。非比较排序针对数组arr,计算arr[i]之前有多少个元素,则确定了arr[i]在排序后数组中的位置。非比较排序只要确定每个元素之前的已有元素的个数即可。一次遍历就可解决问题。算法的时间复杂度为O(n)。
Note:非比较排序的时间复杂度低,但它需要占用空间来确定元素唯一的位置。所以对数据的规模和数据的分布有一定的要求。

也有另外一种分类方法为:

内部排序(使用内存):

插入排序:直接插入排序,希尔排序
选择排序:简单选择排序,堆排序
交换排序:快速排序O(nlogn),冒泡排序O(n2)
归并排序:O(nlogn)
基数排序

外部排序(内存和外衬结合使用)

各种排序算法:

首先,我们先来看一下,各种排序算法的比较。
在这里插入图片描述
In-place: 占用常数内存,不占用额外内存
Out-place: 占用额外内存

1.冒泡排序(Bubble Sort)
冒泡排序是一种简单的排序算法,它重复的走访需要排序的序列,一次比较两个元素,如果他们的顺序错误就把他们交换过来,走访序列的工作是一直重复进行直到没有需要交换的元素,也就是排序已经完成。

算法描述:
步骤一:比较相邻的元素,如果第一个比第二个大,则交换两个元素的顺序,反之,则不交换。
步骤二:对每一对相邻元素进行同样的工作,从第一对到最后一对,这样可在最后得的最大的数。
步骤三:针对所有元素进行以上步骤,除了已经排好序元素
步骤四:重复步骤一到三,直到排序完成。

public class BubbleSort {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {9,8,7,6,5,4,3,2,11};
		//use bubble sort to sort
		int end=arr.length;
		while(end>1) {
			int newEnd=0;
			for(int i=0;i<arr.length;i++) {
				for(int j=0;j<arr.length-1-i;j++) {
					if(arr[j]>arr[j+1]) {
						int temp;
						temp=arr[j];
						arr[j]=arr[j+1];
						arr[j+1]=temp;
						newEnd=j+1;
					}
				}
			}
			end=newEnd;
		}
		
		for(int i=0;i<arr.length;i++) {
			System.out.print(arr[i]+"-->");
		}

	}

}

算法分析:冒泡排序的最佳情况O(n),最差情况O(n2),平均情况:O(n)。同时提供了稳定的排序,没有占用额外内存,空间复杂度为O(1)。
冒泡算法的最佳情况是序列已经排好序了,这是冒泡排序只需进行n-1次比较,没有任何元素移动,所以最好情况下时间复杂度为O(n);
最坏情况下是,序列是逆序排列的,那么这时每个元素都需一步一步的挪动到序列的首部,所以最坏情况下是O(n2)。

2.快速排序(Quick Sort)
快速排序是对冒泡排序的一种改良算法。在平均情况下,排序n个元素需要O(nlogn) 次比较,最坏情况下需要O(n2) 次比较。可以通过随机算法来避免最坏情况的发生。 事实上快速排序通常比其他O(nlogn)算法更快,因为它的内部循环可以在大部分架构上很有效率的地达成。

Note:当划分产生的两个子问题分别包含 n-1 和 0 个元素时,最坏情况发生。划分操作的时间复杂度为Θ(n),T(0)=Θ(1),这时算法运行时间的递归式为 T(n)=T(n−1)+T(0)+Θ(n)=T(n−1)+Θ(n),解为T(n)=Θ(n2)。
当划分产生的两个子问题分别包含⌊n/2⌋和⌈n/2⌉−1个元素时,最好情况发生。算法运行时间递归式为 T(n)=2T(n/2)+Θ(n),解为T(n)=Θ(nlgn)。
其实只要是以常数比例划分的,算法的运行时间总是O(nlogn)。

快速排序是每个程序员都要掌握的排序算法。对于插入和冒泡算法来说,一旦数据量达到几万,性能就会变得非常差。这时候快排的时间复杂度的优势就显现出来了。

算法描述:
快速排序使用分治法将一个list分为两个sublists,具体算法描述如下:
步骤一:从数列中挑选出一个元素,称为一个基准。
步骤二:重新排列序列,所有元素比基准值小的放在基准前面,所有元素比基准值大的放在基准的后面,在这个分区退出之后,该基准就位于数列的中间位置。这个称为分区操作(partition)。
步骤三:递归的把小于基准值和大于基准值的子序列进行排序。

public class QuickSort {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {3,2,1,10,9,8,7,5};
		sort(arr,0,7);
		for(int i=0;i<arr.length;i++) {
			System.out.print(arr[i]+"-->");
		}
	}
	
	public static int[] sort(int[] arr,int start,int end) {
		if (arr.length < 1 || start < 0 || end >= arr.length || start > end) return null;
        int smallIndex = partition(arr, start, end);
        if (smallIndex > start)
            sort(arr, start, smallIndex - 1);
        if (smallIndex < end)
            sort(arr, smallIndex + 1, end);
        return arr;
	}
	public static int partition(int[] arr,int start,int end) {
		int ran=(int) (start+Math.random()*(end-start+1));
		System.out.println("random="+ran);
		int first=start-1;
		System.out.println("first="+first);
		swap(arr,end,ran);
		
		for(int j=start;j<=end;j++) {
			if(arr[j]<=arr[end]) {
				first++;
				if(j>first) {
					swap(arr,first,j);
				}
			}
		}
		return first;
	}
	
	public static void swap(int[] arr,int a,int b) {
		int temp;
		temp=arr[a];
		arr[a]=arr[b];
		arr[b]=temp;
	}
}

算法分析:快排的时间复杂度为O(nlogn)。空间复杂度为O(logn),因为快排是通过递归的调用实现的,而且每次函数中只调用了常数内存空间,因此空间复杂度等于递归的深度O(logn)。同时快排是非稳定的,两个相等元素的相对位置可能会发生改变。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值