排序算法初步——冒泡排序法

排序:
排序操作是算法学习中的经典部分。尽管很多语言中都已经提前实现了排序功能,直接提供了排序函数,对于初学者来说,学习各种排序算法的思想仍是十分必要的。

今天主要介绍三种算法:经典的冒泡排序、插入排序和选择排序。不过在此之前,先来看一下评估排序算法优劣的几个标准。

分析算法的方向:

  1. 排序算法的执行效率:

注意,这里的执行效率并不能简单的等同于分析时间复杂度。

(1)要考虑低阶、常数还有系数。通常情况下,我们分析的时间复杂度都是针对在输入数据非常大的情况,所以低阶、系数和常数都被忽略了。然而在实际的开发当中,对于较小的数据量的排序也是经常需要的,所以在评估使用哪种算法是也需要把这些考虑进来。

(2)最好情况、最坏情况和平均时间复杂度。由于原始数据的秩序不定,所以需要了解算法在各种情况下的执行情况。

(3)比较次数和交换次数。

  1. 排序算法的内存消耗,就是通过算法的空间复杂度来衡量。特别说一下原地排序:就是空间复杂度为O(1)的排序算法。

  2. 排序算法的稳定性:经过排序之后,相等的数据原有的先后顺序不变。尽管进行了排序,原数据序列被“打乱”,但是稳定性较高的算法仍然保存了原本序列生成时的一些信息(排序时比较的常常是数据的主键,而非主键信息之间的关系有时也具有使用价值,比如输入时间的的先后关系)。


冒泡排序法

所谓冒泡就是从序列的第一个元素开始,与序列中其他的元素一一比较(可能遍历整个数组),每次只比较相邻的两个元素,如果满足大小条件,则交换位置,然后再进行下一个数的比较。我们来看一下具体的实现。

public static void main(String args[]) {
 
    //等待被排序的数组
 
    int[] nums = {4,6,1,5,3,2};
 
    for(int i = 0; inums[j+1]) {
 
    //交换位置
 
    int temp = nums[j];
 
    nums[j] = nums[j+1];
 
    nums[j+1] = temp;
 
    }
 
}
 
if(!=flag) break; //有关flag的两行等下会解释
 
    }
 
}
 

以第一遍历比较为例,解释一下这个操作究竟干了些什么:

数组的比较情况

容易得出一轮遍历比较只能得出一个“最大数”,让它“冒”到队伍末尾。第二层循环的意义在于能够一直紧盯着当前的较大值,让它找到合适的位置,找到之后在开始下一轮大循环。

(比如上例中nums [ j ] = 6,nums [ j + 1 ] = 1,交换位置后( + + j ),新的nums [ j ] = 6,再与nums [ j + 1 ] 进行比较,较大的值能被一直关注,直到找到最终位置)

让我们来分析一下这种算法:

1. 算法的内存消耗:

如果发生交换行为,程序只需要申请一个(常量级)变量,空间复杂度为 O(1),属于原地排序。

2. 算法的稳定性:

示例中,数值相等的情况下不进行交换,原有的顺序不受干扰。

3. 算法的执行效率:

最好情况当然就是初始序列就是顺序的,也就“不用排序了”。然而上述结论是由我们的眼睛观察得出的。对于计算机来说,还是要遍历数组。一开始我觉得flag两行是多余的,因为在绝大多数情况下没什么用。但是在最好情况下,它却能降低在最好的情况下时间复杂度。在这里简单解释一下两层循环的意义:内循环是为了"跟随",每次比较的都是针对一个数。外循环是为了找到下一个大数。如果没有发生交换"小循环",就说明没有"逆序"存在,也就是不需要交换,"跟随"没有发生,序列无需再次寻找所谓"下一个大数"。

如果没有这样的判断,在一切情况下两层循环都会执行,复杂度为O(n^2);现在添加了判断,在最好情况下就只有里层循环执行,复杂度为O(n)。

你可能感兴趣的:(排序算法初步——冒泡排序法)