数据结构与算法---排序---冒泡排序

今天来学一下十大排序

首先,我们先了解下各个排序的时间复杂度:
在这里插入图片描述

冒泡、选择、插入、归并、快速、希尔、堆排序属于比较排序

在这里,我们默认排序是从小到大排序。

一个动态演示各种排序算法的动画网站:visualgo

排序算法的稳定性(Stability)

如果相等的两个元素,在排序前后的相对位置保持不变,那么这个算法是稳定的排序算法。

比如 5 1 3a 4 7 3b
稳定排序:1 3a 3b 4 5 7
不稳定排序:1 3b 3a 4 5 7

对于自定义对象,稳定、不稳定排序还是有很大影响的。

常见的递推式与复杂度

在这里插入图片描述


冒泡排序(Bobble Sort)

冒泡排序就是两两交换
前大后小,交换,确保后面大。交换完毕后,+1,去比较下一组。
前小后大,不动,+1,去比较下一组。

官方点的语言:

执行流程:
1 从头开始比较每一对相邻元素,如果第1个比第2个大,就交换它们的位置。执行完一轮后,最末尾那个元素就是最大的元素。

2 或略1中曾经找到的最大元素,重复执行步骤1,直到全部元素有序。

首先,我们考虑,如何两两比较,将最大值找到并放在最末尾。

public class sort {
	
	public static void main(String[] args)
	{
		int[] array = {19, 18, 67, 199, 6, 56};
		
		for (int begin = 1; begin < array.length; begin++) {//左闭右开
				if (array[begin] < array[begin - 1]) {
					int temp = array[begin - 1];
					array[begin - 1] = array[begin];
					array[begin] = temp;
				}
		}
		
		for(int i = 0; i<array.length; i++)
		{
			System.out.print(array[i] + " ");
		}
	}
}

以上,是一次循环后,找出最大值,放入最后面。
后面,我们需要继续循环除去最后一个值的剩余元素,找出最大值,放在新的循环的最末尾。

然后,考虑比较时,
第1次比较的末尾索引值是array.length - 1
第2次比较的末尾索引值是array.length - 2
第3次比较的末尾索引值是array.length - 3
第k次比较的末尾索引值是array.length - k

第0个位置与第0个位置,不需要比较
也就是
最后的末尾值是1

也就是
end的取值范围是1 到 array.length - 1

因而,不难写出代码:

public class sort {
	
	public static void main(String[] args)
	{
		int[] array = {19, 18, 67, 199, 6, 56};
		
		for (int end = array.length - 1; end > 0; end--) {
			for (int begin = 1; begin <= end; begin++) {//左闭右开
				if (array[begin] < array[begin - 1]) {
					int temp = array[begin - 1];
					array[begin - 1] = array[begin];
					array[begin] = temp;
				}
			}
		}
		
		for(int i = 0; i<array.length; i++)
		{
			System.out.print(array[i] + " ");
		}
	}
}

当然,之前自己这样写过冒泡排序:

在这里插入图片描述
第一种写法,在begin、end上变量名取的好,比直接使用i,j好理解
第一种写法,基本上按照了冒泡排序的思路直接写出来的

第二种写法,手写的时候好写,写法也好看,i,j都从0开始,并且都是++操作。不像第一种写法,第一层循环–,第二层++。

不过,还是第一种思路上好理解。


对于上面的冒泡排序,循环两遍,很容易看出其时间复杂度为O(n^2)

假如,给我们的数组本身就是有序的,如果用之前的代码,程序会按部就班的执行,时间复杂度还是O(n^2)。但事实上,如果数组本身就是有序的,我们就不需要进行冒泡排序算法,直接打印出结果即可。也就是,我们可以对上述代码做优化

优化方案一:

如果序列已经完全有序,可以提前终止冒泡排序
定义一个变量sorted,如果从begin到end比较一遍,发现从头到尾没有发生交换,说明数组是有序的。然后end–,数量变小,依然是有序的,所以,可以直接结束循环。

比如 1 2 3 4 6 5
第一次循环过后,就是有序的,1 2 3 4 5 6
第二次循环,发现是有序的,直接结束打印。

public class sort {
	
	public static void main(String[] args)
	{
		int[] array = {19, 18, 67, 199, 6, 56};
		
		for (int end = array.length - 1; end > 0; end--) {
			boolean sorted = true;//假定一开始是有序的
			for (int begin = 1; begin <= end; begin++) {//左闭右开
				if (array[begin] < array[begin - 1]) {
					int temp = array[begin - 1];
					array[begin - 1] = array[begin];
					array[begin] = temp;
					sorted = false;//只要进入这里面,说明发生了交换,说明不是有序的
				}
			}
			if (sorted) {//如果true,说明是有序的
				break;
			}
		}
		
		for(int i = 0; i<array.length; i++)
		{
			System.out.print(array[i] + " ");
		}
	}
}

这个效率,一般来说,优化过后的运行时长要比没有优化的原生代码时长要长。
毕竟,优化的代码,里面多了三行代码。

优化过后的冒泡排序,适用于待排序数组,接近有序的时候。

优化方案二:

如果序列已经局部有序,可以记录最后一次交换的位置,减少比较次数

在这里插入图片描述

public class sort {
	
	public static void main(String[] args)
	{
		int[] array = {19, 18, 67, 199, 6, 56};
		
		for (int end = array.length - 1; end > 0; end--) {
			//sortedIndex的初始值在数组完全有序的时候有用 1 0 -1 都可以
			int sortedIndex = 1;
			for (int begin = 1; begin <= end; begin++) {//左闭右开
				if (array[begin] < array[begin - 1]) {
					int temp = array[begin - 1];
					array[begin - 1] = array[begin];
					array[begin] = temp;
					sortedIndex = begin;//sortedIndex记录最后一次交换的位置
				}
			}
			//既然sortedIndex已经有了,那么,下次结束的时候,就是最后交换的位置
			end = sortedIndex;
		}
		
		for(int i = 0; i<array.length; i++)
		{
			System.out.print(array[i] + " ");
		}
	}
}

最坏时间复杂度O(n^2),1 + 2 +3 + … + n-1
最好时间复杂度,只需要遍历一遍O(n)
空间复杂度,不需要借助其他空间O(1)


通过测试例子,1万个数组,前2千个无序,后8千有序,可以看到运行时间:

在这里插入图片描述

可以发现,第二个优化方案还是很有效果的。

©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页