冒泡排序与选择排序的实现与分析

冒泡排序与选择排序是两种非常基础的排序方式,也是许多程序员入门的排序算法。很多数据结构或者算法教材清晰明了地描述了两种排序的工作原理,复杂度等,但纸上得来终觉浅,为了摸清楚两种算法的性能,我还是亲自动手操作了一波~~~

下面请跟上我的思路~~~

冒泡排序作为最最基础的排序方法,应该连小学生也能弄懂它的原理(如果你看不懂的话建议重新去小学深造,开个玩笑):

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个。

  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较 

为了说明这一情况,我们先写个程序来跑一下:

************************************************
public static void main(String[] args){
		int[] a={10, 7, 5, 4, 8, -3, 6};
		bubble_sort.sort(a);
	}
************************************************
下面是每一次排序的过程(一段算一次)
7 10 5 4 8 -3 6 
7 5 10 4 8 -3 6 
7 5 4 10 8 -3 6 
7 5 4 8 10 -3 6 
7 5 4 8 -3 10 6 
7 5 4 8 -3 6 10 

5 7 4 8 -3 6 10 
5 4 7 8 -3 6 10 
5 4 7 8 -3 6 10 
5 4 7 -3 8 6 10 
5 4 7 -3 6 8 10 

4 5 7 -3 6 8 10 
4 5 7 -3 6 8 10 
4 5 -3 7 6 8 10 
4 5 -3 6 7 8 10 

4 5 -3 6 7 8 10 
4 -3 5 6 7 8 10 
4 -3 5 6 7 8 10 

-3 4 5 6 7 8 10 
-3 4 5 6 7 8 10 

-3 4 5 6 7 8 10
***************************************************
可以看到,每一次排序,相邻元素里面大的那个都会跑到后面去
尽管有的排序(因为元素已经有序)算不上真正意义上的排序,
比如后两次,这依赖于输入元素的次序。
***************************************************
冒泡排序算法:
public static void sort(int[] a){
		int n=a.length;
		for (int i=0; i<n-1; i++)
		{
			for (int j=0; j<n-i-1; j++)
			{
				if (a[j]>a[j+1])
				swap(a, j, j+1);
			}//for 2
		}//for 1
	}
***************************************************

说完了冒泡,我们来说一下选择排序:

原理很简单。首先,找到数组中最小的那个元素,其次,将它和数组的第一个数交换;再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。以此类推,直到数组全部有序。总的来说就是在不断地选择剩余元素中的最小者。

来看怎么排的:

***************************************************
public static void main(String[] args){
		int[] a={10, 7, 5, 4, 8, -3, 6};
		select_sort.sort(a);
	}
***************************************************
第0次排序
10 7 5 4 8 -3 6 
min=5, a[min]=-3

第1次排序
-3 7 5 4 8 10 6 
min=3, a[min]=4

第2次排序
-3 4 5 7 8 10 6 
min=2, a[min]=5

第3次排序
-3 4 5 7 8 10 6 
min=6, a[min]=6

第4次排序
-3 4 5 6 8 10 7 
min=6, a[min]=7

第5次排序
-3 4 5 6 7 10 8 
min=6, a[min]=8

第6次排序
-3 4 5 6 7 8 10 
min=6, a[min]=10
*****************************************************
和冒泡排序相比,选择排序省去了相邻元素的移动,每次排序只
交换a[min]和a[i]的位置,大大减少了时间开销。
*****************************************************
选择排序算法:
public static void sort(int[] a){
		int n=a.length;		
		for (int i=0; i<n; i++)
		{
			int min=i;
			for (int j=i+1; j<n; j++)
				if (a[j]<a[min])
					min=j;//for 2	
			swap(a, min, i);
		}//for 1
	}
*****************************************************

直观上,我们认识了两种排序算法。现在我们来定量分析它们:

首先说【冒泡排序】,外层循环确定每次排序的结果,内层循环不断交换目的元素。

冒泡排序的性能很大程度上依赖于输入,假设数组初始为正序的,那么一趟扫描即可完成排序。

正序情况下,设所需关键字的比较次数为C,记录的移动次数为M,数组长度为n-1,则C=Cmin=n-1,M=Mmin=0。由此可见,冒泡排序的时间复杂度最优为O(n)。你可能会猜到,对于冒泡排序来说,最坏的情况莫过于初始状态为逆序了。的确如此,当数组逆序时,需要n-1趟排序,每趟排序需要n-i次关键字比较,且每次比较都必须交换相邻元素(1次交换看做3次移动),此时C=Cmax=n(n-1)/2,M=Mmax=3Cmax=3n(n-1)/2,其时间复杂度为O(n^2)。为了验证以上推论,我们还是通过程序来说明:

******************************************************
public class bubble_sort {
	public static void sort(int[] a){
		int n=a.length;
		int C=0, M=0;
		for (int i=0; i<n-1; i++)
		{
			C++;
			for (int j=0; j<n-i-1; j++)
			{
				if (a[j]>a[j+1])
				{
					swap(a, j, j+1);
					M+=3;
				}	
			}//for 2
		}//for 1
		System.out.println("C="+C+", M="+M);
	}
}
******************************************************
正序:
输入int[] a={1, 2, 3, 4, 5, 6, 7};
测试:C=6, M=0

逆序:
输入int[] a={7, 6, 5, 4, 3, 2, 1};
测试:C=6, M=63

乱序:
输入int[] a={10, 7, 5, 4, 8, -3, 6};
测试:C=6, M=45

=>冒泡排序性能:正序>乱序>逆序
******************************************************

接下来看【选择排序】

从原理来看,对于长度为n的数组,选择排序一定会交换n次元素。并且0到n-1的任意i都会进行一次交换和n-i-1次比较,所以总的比较次数为n(n-1)/2,时间复杂度依然是O(n^2)。请看代码:

******************************************************
public class select_sort {
	public static void sort(int[] a){
		int n=a.length;	
		int C=0, M=0;
		for (int i=0; i<n; i++)
		{
			C++;
			int min=i;
			for (int j=i+1; j<n; j++)
				if (a[j]<a[min])
					min=j;//for 2	
			swap(a, min, i);
			M+=3;
		}//for 1
		System.out.println("C="+C+", M="+M);
	}
}
******************************************************
正序:
输入int[] a={1, 2, 3, 4, 5, 6, 7};
测试:C=6, M=21

逆序:
输入int[] a={7, 6, 5, 4, 3, 2, 1};
测试:C=6, M=21

乱序:
输入int[] a={10, 7, 5, 4, 8, -3, 6};
测试:C=6, M=21

选择排序性能和输入无关,且数据移动次数最少
******************************************************

可能对于上面测试数据那样小规模的输入,两种算法都可以在一瞬间做完工作。但对于成千上万个数据来说,算法的性能就能决定你的心情。不信的话来测试一下:

设n为随机数组的长度,对于两种不同排序算法分别进行t次运行时间测试
最后取平均值(单位s)。设t=100

test1:
n=100
选择排序:0.011000000000000003
冒泡排序:0.014000000000000005

test2:
n=200
选择排序:0.027000000000000017
冒泡排序:0.024000000000000014

test3:
n=400
选择排序:0.030000000000000013
冒泡排序:0.045000000000000026

test4:
n=1600
选择排序:0.23700000000000018
冒泡排序:0.42700000000000027

test5:
n=6400
选择排序:3.3909999999999996
冒泡排序:7.593000000000002

test6:
n=20000
选择排序:34.11899999999999
冒泡排序:116.45700000000005

test7:
已失去耐心

数据证明了对于小规模的排序,冒泡和选择差别并不大。当数据越来越大时,二者耗时的差距也越来越大,但两个算法的时间复杂度都是O(n^2),为什么冒泡会比选择慢这么多呢?究其原因,在于冒泡的移动元素次数实在太多,如果你的电脑买的便宜,还是选一种快一点的算法吧!但冒泡也具有优越性,那就是它具有稳定性,也就是说相同键值的元素相对位置在排序前后并不改变(这一点通过代码自己体会),而选择排序就不具有稳定性。

当然你可以测试冒泡排序在正序情况下的样例,那样没有交换操作。

如果你觉得光看数据不爽的话,我建议你自己试一试!

BY DXH924

2018.10.18

猜你喜欢

转载自blog.csdn.net/DXH924/article/details/83148328
今日推荐