今天来学一下十大排序
首先,我们先了解下各个排序的时间复杂度:
冒泡、选择、插入、归并、快速、希尔、堆排序属于比较排序
在这里,我们默认排序是从小到大排序。
一个动态演示各种排序算法的动画网站: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 begin = 0; begin < end; begin++) {
if (array[begin] > array[begin + 1]) {
int temp = array[begin];
array[begin] = array[begin + 1];
array[begin + 1] = temp;
}
}
*/
}
for(int i = 0; i<array.length; i++)
{
System.out.print(array[i] + " ");
}
}
}
当然,之前自己这样写过冒泡排序:
第一种写法,在begin、end上变量名取的好,比直接使用i,j好理解
第一种写法,基本上按照了冒泡排序的思路直接写出来的
第二种写法,手写的时候好写,写法也好看,i,j都从0开始,并且都是++操作。不像第一种写法,第一层循环 --,第二层++。
不过,还是第一种思路上好理解。
对于上面的冒泡排序,循环两遍,很容易看出其时间复杂度为O(n2)
假如,给我们的数组本身就是有序的,如果用之前的代码,程序会按部就班的执行,时间复杂度还是O(n2)。但事实上,如果数组本身就是有序的,我们就不需要进行冒泡排序算法,直接打印出结果即可。也就是,我们可以对上述代码做优化
优化方案一:
如果序列已经完全有序,可以提前终止冒泡排序
定义一个变量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(n2),1 + 2 +3 + … + n-1
最好时间复杂度,只需要遍历一遍O(n)
空间复杂度,不需要借助其他空间O(1)
通过测试例子,1万个数组,前2千个无序,后8千有序,可以看到运行时间:
可以发现,第二个优化方案还是很有效果的。