十大基础排序算法[java源码+动静双图解析+性能分析] - 只会一点java

java

一、概述

作为一个合格的程序员,算法是必备技能,特此总结十大基础排序算法。java版源码实现,强烈推荐《算法第四版》非常适合入手,所有算法网上可以找到源码下载。

PS:本文讲解算法分三步:1.思想2.图示3.源码4.性能分析

1.1 时间复杂度

算法的运行时间,在这里主要考量:比较和交换的成本。

常见的时间复杂度排序:常数阶O(1)<对数阶O(

log2n)<线性阶O(n)<线性对数阶O(nlog2n)<平方阶O(n^2)<立方阶O(n^3)<指数阶O(2^n)

1.2 空间复杂度

使用的内存,是否需要额外的存储空间。

1.3 稳定性

相等元素排序前后相对位置保持不变,就认为是稳定的。

稳定性何时有意义?

1)排序对象包含多个属性

2) 原始序列其它属性有序

3)期望最终排序后原来有序的属性还有序,原来没序的字段有序了。

二、常见排序算法

通用函数封装

由于排序算法都用到一些相同源码,所以提炼出来作为一个父类,算法只需要继承这个类即可使用通用方法:

1.less()比较元素大小

2.exch()交换元素

3.show()打印排序后数组

4.isSorted()校验排序是否正确

 1 package algorithm;

2

3 /**

4 *

5 * @ClassName:MySort

6 * @Description:自定义排序基类,封装常用方法

7 * @author denny.zhang

8 * @date 2018年2月5日下午3:12:03

9 */

10 public class MySort {

11 protected static String[] a = new String[]{"a","d","c","b"};

12

13 /**

14 *

15 * @Description 打印排序后结果

16 * @param a

17 * @author denny.zhang

18 * @date 2018年2月5日下午3:11:46

19 * @since JDK1.8

20 */

21 protected static void show(Comparable[] a){

22 for(int i=0;i<a.length;i++){

23 System.out.print(a[i]+" ");

24 }

25 System.out.println();

26 }

27

28 /**

29 *

30 * @Description 比较是否v小于w

31 * @param v

32 * @param w

33 * @return

34 * @author denny.zhang

35 * @date 2018年2月5日下午3:11:14

36 * @since JDK1.8

37 */

38 protected static boolean less(Comparable v,Comparable w){

39 return v.compareTo(w)<0;

40 }

41

42 /**

43 *

44 * @Description 对于数组a,交换a[i]和a[j]。

45 * @param a

46 * @param i

47 * @param j

48 * @author denny.zhang

49 * @date 2018年2月5日下午3:09:41

50 * @since JDK1.8

51 */

52 protected static void exch(Comparable[] a,int i,int j){

53 Comparable temp= a[i];

54 a[i]=a[j];

55 a[j]=temp;

56 //System.out.println("交换a["+i+"]"+",a["+j+"]");

57 }

58

59 /**

60 *

61 * @Description 用于校验是否升序

62 * @param a

63 * @return

64 * @author denny.zhang

65 * @date 2018年2月5日下午3:10:57

66 * @since JDK1.8

67 */

68 protected static boolean isSorted(Comparable[] a){

69 //只要有一个后数<前数,返回失败

70 for(int i=0;i<a.length;i++){

71 if(less(a[i], a[i-1])) return false;

72 }

73 //都没问题,返回成功

74 return true;

75 }

76

77 /**

78 *

79 * @Description 用于校验是否升序

80 * @param a

81 * @param lo 起始下标

82 * @param hi 结束下标

83 * @return

84 * @author denny.zhang

85 * @date 2018年2月6日下午5:19:43

86 * @since JDK1.8

87 */

88 protected static boolean isSorted(Comparable[] a, int lo, int hi) {

89 for (int i = lo + 1; i <= hi; i++)

90 if (less(a[i], a[i-1])) return false;

91 return true;

92 }

93 }

 

2.1 冒泡排序

思想:

最简单的排序,内外两遍循环。外循环简单遍历a[n-1]~a[1],内循环一遍确定一个最大值,类似"冒泡"。

1.外循环遍历a[n-1]~a[1]。例如选定a[n-1]

2.内循环,从a[0]-a[n-1] 从左往右,比较相邻的元素对。如果第一个比第二个大,就交换它们两个,一直到最后一对,这样在最后的元素a[n-1]是最大的数;

2.重复以上的步骤,一直到外循环遍历到a[1],内循环会比较a[0],a[1],至此排序完毕。

优化点:添加一个标记,当内循环一遍不交换,排序完毕。

动态图:

源码:

 1 /**

2 * @author denny

3 * @Description 冒泡排序

4 * @date 2019/7/2 下午6:19

5 */

6 public class Bubble extends MySort {

7

8 public static void sort(Comparable[] a) {

9 int n = a.length;

10 // 标记内层遍历是否交换过

11 int flag = 0;

12 // 外层遍历n-1次,每次确定一个最大值 a[i]

13 for (int i = n - 1; i > 0; i--) {

14 // 内层遍历:比较a[0]a[1]...a[n-1-1]a[n-1]

15 for (int j = 0; j < i; j++) {

16 // 相邻元素两两对比,如果后<前,交换

17 if (less(a[j + 1], a[j])) {

18 // 元素交换

19 exch(a, j + 1, j);

20 flag = 1;

21 }

22 }

23 // flag=0,说明数列已有序不需要再交换了!!!

24 if(flag==0){

25 break;

26 }

27 }

28 }

29

30 public static void main(String[] args) {

31 Bubble.sort(a);

32 show(a);

33 }

34 }

分析:

时间复杂度:外部循环O(n) * 内部循环O(n) -->O(n²)

空间复杂度:不需要借助外部空间-->O(1)

是否稳定:相等元素不交换-->是。

2.2 选择排序

思想:

最简单的排序,内外两遍循环。外循环简单遍历a[0]~a[n-1],内循环每次确定一个最小值

1.外循环假设第一个数是最小值

2.内循环拿后面所有元素和第一个元素比较,找到最小值,放在第一位(如果第一位不是最小值就交换),第一小的元素确定。

3.外循环从第一个元素遍历到最后,重复1,2操作,排序完毕。

图示:

动态图:

源码:

 1 package algorithm;

2

3 /**

4 *

5 * @ClassName:Selection

6 * @Description:选择排序:对于数组长度为N的数组<br>

7 * <ul>

8 * <li>比较次数:(n-1)+(n-2)+...2+1=n(n-1)/2,大约N²/2次</li>

9 * <li>交换次数:N次</li>

10 * </ul>

11 * @author denny.zhang

12 * @date 2018年2月5日上午11:13:26

13 */

14 public class Selection extends MySort{

15

16 @SuppressWarnings("rawtypes")

17 public static void sort(Comparable[] a){

18 int n = a.length;

19 //遍历n次,每次确定一个最小值

20 for(int i=0 ; i<n ; i++){

21 int min=i;//初始化最小值下标为i

22 //把i之后的每一项都和a[min]比较,求最小项

23 for(int j=i+1;j<n;j++){

24 if (less(a[j], a[min])) min = j;

25 }

26 //a[i]和a[min]交换,第i位排序完毕

27 exch(a, i, min);

28 }

29 }

30

31 public static void main(String[] args) {

32 Selection.sort(a);

33 show(a);

34 }

35 }

分析:

时间复杂度:外部循环O(n) * 内部循环O(n) = O(n²)

空间复杂度:O(1)不需要借助外部空间

是否稳定:否。只要遇到小的就交换,被交换对象如果有等值元素存在且在交换元素之前,打破相对顺序了。列子:58529-》第一个5和2交换,2个5的相对顺序变了。

2.3 插入排序

思想:

类似扑克牌抓牌一样,一次插入一张牌保证当前牌有序。

从第二张牌开始,插入第一张牌;第三张牌插入前两张牌...一直到最后一张牌。插入牌时,两两比较,遇到逆序交换。

外循环i++,内循环a[j]和a[j-1]比较,逆序交换。j--往左移动,两两比较。

图示:

动态图:

源码:

 1 package algorithm;

2

3 /**

4 *

5 * @ClassName:Insertion

6 * @Description:插入排序:对于数组长度为N的数组<br>就像扑克牌插纸牌一样

7 * <ul>

8 * <li>比较次数:N-1~N²/2 平均N²/4 </li>

9 * <li>交换次数:0~N²/2 平均N²/4</li>

10 * </ul>

11 * @author denny.zhang

12 * @date 2018年2月5日上午11:13:26

13 */

14 public class Insertion extends MySort{

15

16 @SuppressWarnings("rawtypes")

17 public static void sort(Comparable[] a){

18 int n = a.length;

19 //遍历n-1次,a[i]插入前i个数

20 for(int i=1 ; i<n ; i++){

21 //System.out.println("i="+i);

22 //i之前的每两项比较,出现逆序,立即交换

23 for(int j=i;j>0 && less(a[j], a[j-1]);j--){

24 exch(a, j, j-1);

25 }

26 }

27 }

28

29 public static void main(String[] args) {

30 Insertion.sort(a);

31 show(a);

32 }

33 }

分析:

时间复杂度:O(n~n²)如果元素有序就是n, 元素逆序就是n²

空间复杂度:O(1)

是否稳定:是

 

2.4 希尔排序

思想:

希尔排序又称缩小增量排序,是插入排序的升级版。核心思想是使数组中任意间隔为h的元素有序,针对每个h,取出间隔h的子数组并插入排序,h越来越小一直到1,即排序完成。这个间隔h的选择有考究。

 

图示:

动态图:

步长:3-2-1

源码:

递增序列有很多,只要满足最后一个数为1即可。即最少有间隔为1的插入排序即可保证排序。至于这个数列具体最优不在本文讨论范围内。

下图展示了2种序列,sort()方法是复杂序列,sort2()是简单序列。如果只是理解算法的话sort2()足矣。

 1 package algorithm;

2

3 /**

4 *

5 * @ClassName:Shell

6 * @Description:希尔排序:改进自插入排序,交换不相邻元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序<br>

7 *

8 * @author denny.zhang

9 * @date 2018年2月5日上午11:13:26

10 */

11 public class Shell extends MySort{

12

13 /**

14 *

15 * @Description 更加符合要求的算法

16 * @param a

17 * @author denny.zhang

18 * @date 2018年3月7日下午5:35:07

19 * @since JDK1.8

20 */

21 @SuppressWarnings("rawtypes")

22 public static void sort(Comparable[] a){

23 int n = a.length;

24

25 // 3x+1 increment sequence: 1, 4, 13, 40, 121, 364, 1093, ...

26 int h = 1;

27 while (h < n/3) {//这里确保了每个子数组有>=3个元素,h间隔过大,每个子数组元素太少就没有排序的意义了

28 h = 3*h + 1; //1.如果小于n/3,h变大,直到h>=n/3为止

29 }

30 while (h >= 1) {

31 // h-sort the array 2.遍历从h->n

32 for (int i = h; i < n; i++) {

33 //3.从j开始往左每h间隔比较一次,逆序就交换

34 for (int j = i; j >= h && less(a[j], a[j-h]); j -= h) {

35 exch(a, j, j-h);

36 }

37 }

38 assert isHsorted(a, h);

39 h /= 3;//排完序再把h降低回来

40 }

41 assert isSorted(a);

42 }

43

44 /**

45 *

46 * @Description d=n/2序列(向上取整)简单算法

47 * @param a

48 * @author denny.zhang

49 * @date 2018年3月7日下午5:33:38

50 * @since JDK1.8

51 */

52 @SuppressWarnings("rawtypes")

53 public static void sort2(Comparable[] a){

54 int n = a.length;

55 int h = n;

56 //1.第一层循环 是h值计算

57 while (h >= 1) {

58 h=(int) Math.ceil(h/2);//向上取整

59 //2.第二层循环i++ 从h->n-1

60 for (int i = h; i < n; i++) {

61 //3.第三层循环 从j开始往左每h间隔比较一次,逆序就交换,做到局部有序

62 for (int j = i; j >= h && less(a[j], a[j-h]); j -= h) {

63 exch(a, j, j-h);

64 }

65 }

66 assert isHsorted(a, h);

67 }

68 assert isSorted(a);

69 }

70

71 // is the array h-sorted?

72 private static boolean isHsorted(Comparable[] a, int h) {

73 for (int i = h; i < a.length; i++)

74 if (less(a[i], a[i-h])) return false;

75 return true;

76 }

77

78 public static void main(String[] args) {

79 Shell.sort(a);

80 show(a);

81 }

82 }

 

分析:

时间复杂度:O(n的1~2次方,暂无法证明最优递增数列)

空间复杂度:O(1)

是否稳定:否

2.5 归并排序

思想:

将数组拆分为两部分(递归)分别排序,再将结果归并排序。

图示:

动态图:

源码:

 1 package algorithm;

2

3 /**

4 *

5 * @ClassName:Merge

6 * @Description:归并排序:自顶向下+自底向上。性能差不多:比较次数:1/2NlgN-NlgN 访问次数6NlgN

7 * @author denny.zhang

8 * @date 2018年2月6日下午5:09:57

9 */

10 public class Merge extends MySort{

11

12

13 /**

14 *

15 * @Description 归并

16 * @param a

17 * @param aux

18 * @param lo

19 * @param mid

20 * @param hi

21 * @author denny.zhang

22 * @date 2018年2月6日下午4:55:25

23 * @since JDK1.8

24 */

25 private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {

26

27 // 复制数组a->aux

28 for (int k = lo; k <= hi; k++) {

29 aux[k] = a[k];

30 }

31

32 // 归并进a数组

33 int i = lo, j = mid+1;

34 //从aux数组找到小值并赋值给数组a

35 for (int k = lo; k <= hi; k++) {

36 if (i > mid) a[k] = aux[j++];//左半边用尽,取右半边元素aux[j]->赋值给a[k],并j++

37 else if (j > hi) a[k] = aux[i++];//右半边用尽,取左半边元素aux[i]->赋值给a[k],并i++

38 else if (less(aux[j], aux[i])) a[k] = aux[j++];//aux[j]小->赋值给a[k],并j++

39 else a[k] = aux[i++];//aux[i]小->赋值给a[k],并i++

40 }

41 }

42

43 // 自顶向下归并排序 a[lo..hi] 使用辅助数组 aux[lo..hi]

44 private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {

45 if (hi <= lo) return;

46 int mid = lo + (hi - lo) / 2;

47 sort(a, aux, lo, mid);//左半边排序

48 sort(a, aux, mid + 1, hi);//右半边排序

49 merge(a, aux, lo, mid, hi);//归并结果

50 }

51

52 /**

53 *

54 * @Description 自底向上归并排序

55 * @param a

56 * @author denny.zhang

57 * @date 2018年2月6日下午6:46:01

58 * @since JDK1.8

59 */

60 public static void sortBU(Comparable[] a,Comparable[] aux) {

61 int n = a.length;

62 for (int len = 1; len < n; len *= 2) {//len子数组大小,有多少个子数组遍历多少回

63 //子数组从2个元素开始自己归并:22归并->44归并->88归并...最后一个大归并

64 for (int lo = 0; lo < n-len; lo += len+len) {//lo子数组起始下标

65 int mid = lo+len-1;

66 int hi = Math.min(lo+len+len-1, n-1);//最后一个数组可能小于len,即不能按照2的倍数分小数组,最后一个取最小值

67 merge(a, aux, lo, mid, hi);

68 }

69 }

70 }

71

72 /**

73 *

74 * @Description 归并排序

75 * @param a

76 * @author denny.zhang

77 * @date 2018年2月6日下午4:52:45

78 * @since JDK1.8

79 */

80 public static void sort(Comparable[] a) {

81 //定义一个辅助数组

82 Comparable[] aux = new Comparable[a.length];

83 //自顶向下归并

84 sort(a, aux, 0, a.length-1);

85 //自底向上归并

86 //sortBU(a,aux);

87 //校验是否排序成功

88 assert isSorted(a);

89 }

90

91 public static void main(String[] args) {

92 Merge.sort(a);

93 show(a);

94 }

95 }

分析:

时间复杂度:O(NlogN)简单理解:看上图源码44行sort(),把数组拆分2个有序数组,然后再归并2个小数组。长度为N的数组递归“折半拆分成2个有序数组”需要logN步(二叉排序树的树高),每一步的归并耗时N,相乘得到NlogN即是时间复杂度。

空间复杂度:O(N)不用多说使用了额外的辅助数组aux[N]

是否稳定:是

2.6 快速排序

思想:

两向切分:取数组第一个元素作为切分元素,左边子数组全部小于等于切分元素,右边子数组全部大于等于切分元素,这样每切分一次就可以确定一个元素的位置。左右子数组再分别递归排序。

三向切分:对于数组中重复元素多的数组,更适合三向切分。也是第一个元素作为切分元素值=v,三向切分:第一段:lo~lt元素<v,第二段:lt~gt元素=v,第三段:gt~hi元素>v。递归给第一段和第三段排序。

注意:对于小数组(<=15),插入排序更适合。

图示:

下图是一个两向切分

 动态图

源码:

  1 package algorithm;

2

3 import edu.princeton.cs.algs4.StdRandom;

4

5 /**

6 *

7 * @ClassName:Quick

8 * @Description:快速排序

9 * <ul>

10 * <li>两向切分</li>

11 * <li>三向切分:重复元素多的时候</li>

12 * </ul>

13 * @author denny.zhang

14 * @date 2018年2月7日下午5:50:56

15 */

16 @SuppressWarnings("rawtypes")

17 public class Quick extends MySort{

18

19

20 public static void sort(Comparable[] a) {

21 StdRandom.shuffle(a);//随机排列,避免数组有序出现最差排序

22 show(a);

23 sort(a, 0, a.length - 1);//两向切分

24 //sort3Way(a, 0, a.length - 1);//三向切分

25 assert isSorted(a);

26 }

27

28 /**

29 *

30 * @Description 两向切分快排

31 * @param a

32 * @param lo

33 * @param hi

34 * @author denny.zhang

35 * @date 2018年2月7日下午4:36:48

36 * @since JDK1.8

37 */

38 private static void sort(Comparable[] a, int lo, int hi) {

39 if (hi <= lo) return;//

40 int j = partition(a, lo, hi);//拆分,找到拆分下标

41 System.out.println("j="+j);

42 show(a);

43 sort(a, lo, j-1);//左边排序,递归

44 sort(a, j+1, hi);//右边排序,递归

45 assert isSorted(a, lo, hi);

46 }

47

48 // 两向切分数组,找到拆分下标并返回,保证a[lo..j-1] <= a[j] <= a[j+1..hi]

49 private static int partition(Comparable[] a, int lo, int hi) {

50 System.out.println("lo="+lo+",hi="+hi);

51 int i = lo;//左指针

52 int j = hi + 1;//右指针

53 Comparable v = a[lo];//初始化一个切分元素

54 //一个大的自循环

55 while (true) {

56

57 // 分支1:如果左指针元素小于v,++i,一直到右边界退出,或者左边有不小于v的元素停下,执行分支2

58 while (less(a[++i], v))

59 if (i == hi) break;

60

61 // 分支2:如果右指针元素大于v,j--,一直到到左边界退出,或者右边有不大于v的元素为止,执行分支3

62 while (less(v, a[--j]))

63 if (j == lo) break;

64

65 // 分支3:如果左右指针碰撞,甚至左指针大于右指针,退出

66 if (i >= j) break;

67 // 交换需要交换的a[i]>=v>=a[j],使得a[i]<a[j]

68 System.out.println("交换 a[i] a[j] i="+i+",a[i]="+a[i]+",j="+j+",a[j]="+a[j]);

69 exch(a, i, j);

70

71 }

72 System.out.println("i="+i+",j="+j+",设置 a[j]="+a[j]+",替换为a[lo]="+a[lo]);

73 十大基础排序算法[java源码+动静双图解析+性能分析] - 只会一点java 的全部内容, 来源链接: utcz.com/z/392375.html

回到顶部