程序员的成长之路
冒泡排序
什么是交换排序呢?
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。算法分析冒泡排序算法的性能
算法稳定性
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java
算法思想它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数。然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。详细的图解往往比大堆的文字更有说明力,所以直接上图: 上图中,演示了快速排序的处理过程:
- 初始状态为一组无序的数组:2、4、5、1、3。
- 经过以上操作步骤后,完成了第一次的排序,得到新的数组:1、2、5、4、3。
- 新的数组中,以 2 为分割点,左边都是比 2 小的数,右边都是比 2 大的数。
- 因为 2 已经在数组中找到了合适的位置,所以不用再动。
- 2 左边的数组只有一个元素 1,所以显然不用再排序,位置也被确定。(注:这种情况时,left 指针和 right 指针显然是重合的。因此在代码中,我们可以通过设置判定条件 left 必须小于 right,如果不满足,则不用排序了)。
- 而对于 2 右边的数组 5、4、3,设置 left 指向 5,right 指向 3,开始继续重复图中的一、二、三、四步骤,对新的数组进行排序。
核心代码
空间复杂度
快速排序在每次分割的过程中,需要 1 个空间存储基准值。而快速排序的大概需要 Nlog2N 次的分割处理,所以占用空间也是 Nlog2N 个。
https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java 样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。
算法思想
在讲解直接插入排序之前,先让我们脑补一下我们打牌的过程。
- 先拿一张 5 在手里,
- 再摸到一张 4,比 5 小,插到 5 前面,
- 摸到一张 6,嗯,比 5 大,插到 5 后面,
- 摸到一张 8,比 6 大,插到 6 后面,
- 。。。
- 最后一看,我靠,凑到的居然是同花顺,这下牛逼大了。
以上的过程,其实就是典型的直接插入排序,每次将一个新数据插入到有序队列中的合适位置里。很简单吧,接下来,我们要将这个算法转化为编程语言。假设有一组无序序列 R0, R1, … , RN-1。
- 我们先将这个序列中下标为 0 的元素视为元素个数为 1 的有序序列。
- 然后,我们要依次把 R1, R2, … , RN-1 插入到这个有序序列中。所以,我们需要一个外部循环,从下标 1 扫描到 N-1 。
- 接下来描述插入过程。假设这是要将 Ri 插入到前面有序的序列中。由前面所述,我们可知,插入 Ri 时,前 i-1 个数肯定已经是有序了。
所以我们需要将 Ri 和 R0 ~ Ri-1 进行比较,确定要插入的合适位置。这就需要一个内部循环,我们一般是从后往前比较,即从下标 i-1 开始向 0 进行扫描。核心代码
空间复杂度
由直接插入排序算法可知,我们在排序过程中,需要一个临时变量存储要插入的值,所以空间复杂度为 1 。
希尔排序
算法分析
希尔排序的算法性能直接插入排序和希尔排序的比较
- 直接插入排序是稳定的;而希尔排序是不稳定的。
- 直接插入排序更适合于原始记录基本有序的集合。
- 希尔排序的比较次数和移动次数都要比直接插入排序少,当 N 越大时,效果越明显。
- 在希尔排序中,增量序列 gap 的取法必须满足:**最后一个步长必须是 1 。**
- 直接插入排序也适用于链式存储结构;希尔排序不适用于链式结构。
要点简单选择排序是一种选择排序。选择排序:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。时间复杂度
简单选择排序的比较次数与序列的初始排序无关。假设待排序的序列有 N 个元素,则**比较次数总是 N (N - 1) / 2 **。而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 0。当序列反序时,移动次数最多,为 3N (N - 1) / 2。所以,综合以上,简单排序的时间复杂度为 O(N2)。堆排序
算法分析
堆排序算法的总体情况
示例代码
要点归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用 分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。时间复杂度
归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它的时间复杂度是 O(n*log2n)。归并排序和堆排序、快速排序的比较
示例代码
- 若从空间复杂度来考虑:首选堆排序,其次是快速排序,最后是归并排序。
- 若从稳定性来考虑,应选取归并排序,因为堆排序和快速排序都是不稳定的。
- 若从平均情况下的排序速度考虑,应该选择快速排序。
要点基数排序与本系列前面讲解的七种排序方法都不同,它不需要比较关键字的大小。它是根据关键字中各位的值,通过对排序的 N 个元素进行若干趟“分配”与“收集”来实现排序的。不妨通过一个具体的实例来展示一下,基数排序是如何进行的。设有一个初始序列为: R {50, 123, 543, 187, 49, 30,0, 2, 11, 100}。我们知道,任何一个阿拉伯数,它的各个位数上的基数都是以 0~9 来表示的。所以我们不妨把 0~9 视为 10 个桶。我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 50,个位数上是 0,将这个数存入编号为 0 的桶中。 分类后,我们在从各个桶中,将这些数按照从编号 0 到编号 9 的顺序依次将所有数取出来。这时,得到的序列就是个位数上呈递增趋势的序列。按照个位数排序:{50, 30, 0, 100, 11, 2, 123,543, 187, 49}。接下来,可以对十位数、百位数也按照这种方法进行排序,最后就能得到排序完成的序列。样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。空间复杂度
在基数排序过程中,对于任何位数上的基数进行“装桶”操作时,都需要 n+r 个临时空间。https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java