源码解析Java数组如何选择排序的算法 5 H! n* g1 s- r% R9 D- m " p i( ?! z, M1 缘起. F1 n! T9 S) @+ @1 A: f' h4 j
源于对排序算法的学习, ! H' z2 a4 u+ |+ P! Q: B( ?7 C1 G0 K' I算法的学习,绕不过理论学习,同时,学者会自己实现算法,以加深理解,3 f. M' K; y$ [8 ]4 `1 N
这是一个阶段,我们所学习的知识,最终是要应用的,8 o t5 e+ m* x/ Z$ l$ y
比如,学习了各种排序算法后,大部分学者,要么应对考试,要么应对面试,0 m5 N0 @( ~5 I3 `) Y* w
在实际的工作中,可能用不到自己实现的算法,或者很少用到, M( |9 d# U1 h( y为什么?! @5 M* ?& \% [$ o) I
因为,我们的科学巨人,已经将其工程化,久经考验,成为工业级应用工具了, + ~ b' l8 Q5 A4 @1 W9 |所以,我们需要进一步看看科学巨匠们是如何应用这些算法的,$ \' V. Y1 b Y& ^- d- v* r
这不,从Java的数组排序开始,学习前人的优秀实践, # Z1 V, H e/ `4 r W帮助读者,进一步了解不同的排序算法在软件工业中的应用,提高设计和应对知识考核及交流的能力。! V6 x }" X F+ v- D
6 f+ v0 s5 N& ?* d' q6 Y* K
2 前置知识 + C! S! \7 O8 ]6 ?1 s首先,需要了解时间复杂度、排序算法以及对应的时间复杂度。 5 k9 ]* m! |7 R6 n: R其次,分析Java源码中的排序算法,了解不同情况下的排序算法选择。$ P: P, j2 A8 [
最后,为自己设计排序算法或者测试时提供参考。$ b$ K2 D6 L9 V5 F6 u- {
- |0 H! r7 E$ e C9 k2.1 时间复杂度0 H6 h+ g# Z2 N+ M$ L
时间复杂度用于度量某个程序随输入数据量级的变化运行所消耗时间的变化趋势。0 }1 e/ T( u9 b9 n0 o$ h' w4 ^" a' y
不同时间复杂度的定量趋势(平均时间复杂度), 2 L) D" A! z2 q( Y: `9 H: c2 |& X时间复杂度函数图像如下图所示, 3 Z2 c4 n. ~/ {# u这里横轴表示输入的数据量(如个数),纵轴没有单位(不过,可以使毫秒,秒等),* K% i* \9 b5 E- @
不同的时间复杂度曲线间对比,纵轴可以不设单位,3 z- S9 H. E8 w6 u0 f7 Y
只做趋势分析。 & R( r e' P1 Z( r由图可知,在数据量不是很多的情况下,' G/ C( ~8 }# r8 C6 ` ]
平均时间负载趋势差别不大,- L! a: F5 X, s: P* m! Z+ t' ~% G1 C
但是,数据量增长到一定数量级后,差距尽现,: r( `) P/ f2 G
大家在使用过程中用,可以实际测试一下。. n! j' p' W0 u( l3 l6 p ~4 B
% u: Y, { b8 e" g2 i# V # c3 C& v7 V" l2 B' i" i2.2 排序算法的时间复杂度 * ^3 p; v/ c W! p0 H序号 排序算法 平均时间复杂度 稳定性 2 I8 z7 d9 ^% n9 c% Y6 J1 冒泡排序 $ U2 W/ r. F2 I) z' `1 j
O ( n 2 ) O(n^2)- B6 v: m+ z7 L3 e
O(n 4 Z6 w! S1 b: r& O2, d& _* g0 O# g- F0 ^
)2 P3 g* z2 ^/ P' x9 y: ~% O
稳定 ) ~: Z# S6 Q2 E- q: {7 L/ r) n2 快速排序 ) _& T' m2 Y. s. s/ b
O ( n l o g n ) O(nlogn)2 O" D6 K! J; Y# A
O(nlogn) 4 h# ]: @( T# d% Y/ F ~. {/ C不稳定; t' w* I: r& b0 G
3 直接插入排序 9 n; o$ ^7 d9 b5 J1 A, Z
O ( n 2 ) O(n^2); M; O& S9 _0 e: ~! }
O(n ) I: O0 ]( ~4 ]3 G. X7 B! {* p2 5 U0 v% t8 s! B; b: G5 { ) , @ r5 M H8 \9 X稳定 & Z" G$ g1 y2 a$ k6 P5 L4 希尔排序 2 v1 |; ~( Z* }4 mO ( n 1.3 ) O(n^{1.3}), m# \0 x5 [- c
O(n 2 D. H2 l5 w" Y# z/ Y5 B2 m1.3 6 k) e5 W% _1 v; y G1 p3 r ) h$ b* ?6 f {4 {
不稳定 : w' D9 M% h; \+ W# Z S; o5 选择排序 6 x- `' U. l. I! ~
O ( n 2 ) O(n^2) & [6 H5 ]/ T2 H5 n$ _% i eO(n ( S& J; T; w+ m& r3 |2 , r0 W, |9 J# D )/ x' d3 x$ P8 S' |' ?. m. {, u
不稳定 ! B8 i( Q8 ^1 d2 z* C6 堆排序 ( B" Y w! f1 S/ F% ~3 \O ( n l o g n ) O(nlogn) ( Y" Q7 g2 T6 z; oO(nlogn)( B9 d8 R1 G3 ]) b- A6 z: Q6 U
不稳定7 i; ~. P7 g2 f4 b9 }# j2 P; V
7 快速排序 5 r: X8 X* Q' A7 ]" D. w+ C
O ( n l o g n ) O(nlogn)% e; K. y. Y" Q$ Y) Q$ C
O(nlogn) : `9 C% t1 W3 @. p& M- o* P: `; A: v不稳定( e" R6 z+ k+ q" B9 c
8 归并排序 ( L' u3 s8 _+ Y+ d) M$ u7 q
O ( n l o g n ) O(nlogn)3 e. B( M8 N: d
O(nlogn)0 N) M! t H* g8 e8 X
稳定 / j& V5 E& a5 {9 计数排序 6 G# n$ D0 J( @O ( n + k ) O(n+k)2 y& s, P8 X7 _* }0 m# n7 G
O(n+k)7 p- v: U$ y B6 H& u8 p
稳定7 b2 v C7 K9 t+ A$ ~' l, |/ H! h7 u
10 桶排序 + H6 H7 R. T* V9 U0 @8 F: F) x2 k
O ( n + k ) O(n+k)$ F9 q% {! R! w/ {
O(n+k) 2 T5 ^3 E' Z a5 @4 T2 w% Y# ~* C7 t, M稳定9 L9 n2 ?/ p* r! ?/ y2 F# Q
11 基数排序 ) L, x7 q* \1 @; {O ( n ∗ k ) O(n*k)8 A" E; J% ]1 ~& D5 Y: W% B
O(n∗k): U, f. U, T) D# F/ q
稳定; d- V4 y: @4 ]" H4 |( U
3 数组排序 / Q2 d/ U9 [' XJava源码中的数组排序分两大类: ) J7 B9 E( m* P: A$ `7 i' {(1)基础数据类型数组排序; 4 H% v) M' W: _0 ?0 t. b(2)包装数据类型数组排序; & I r$ K. u: P3 p这两类数组排序方法, & E( M( q1 N: k3 B' F$ ~都依据排序的数量量级选择不同的排序算法, 4 M7 E0 V. u/ h' g3 J4 b以达到较优的计算性能。8 f$ h# E, t* z! ~! o: y$ E( V+ k* `
注意:这里仅分析不同情况使用的排序算法,并没有深究是如何实现的,我打算在后续的文章中一一分析。! o- A4 c! N- |: b# }
9 S+ @9 g! @1 b4 \3.1 基础数据类型数组排序9 g2 }5 K* z* `: \
基础数据类型数组排序算法: 5 l8 Z" B, y+ @1 H$ n# o依据不同的数据量选择结果如下表所示,从源码中提炼而来, ! D! a$ R; G- C通过上面的基础知识可知,数据量较少时采用插入排序,平均时间复杂度为O ( n 2 ) O(n^2)O(n ! E6 |( {# O v
2 0 A5 b* a6 k1 Z) B6 t1 S" v ),数据量较多时采用双端快速排序,平均时间复杂度为O ( n l o g n ) O(nlogn)O(nlogn),但是不稳定,当数据量再多时,采用稳定的归并排序,平均时间复杂度为O ( n l o g n ) O(nlogn)O(nlogn),至于,阈值,是设计者按照实践得出的较优方案。/ O% f U! C @5 f9 n1 I, R0 v9 }
. H3 f; o2 Q! O3 C0 }序号 数据量 排序算法3 a% D4 E, v- o) m* S
1 [2, 47) 插入排序7 g* k% e& L! Y. {6 N, {/ `; _
2 [47, 286) 双端快速排序6 Q. K `6 @, I) U. ~6 n) f. q
3 [286, 无穷) 归并排序( ?% f+ I! l+ Q$ A2 ?
接下来,看看Java的数组排序源码, 8 s; \6 H' `; [8 p首先进入数组排序入口:sort,$ U* F( ]& ~6 |! L
位置:java.util.Arrays#sort(int[])5 ]! B1 |) O6 X, k: b) R
源码如下图所示, - Q( e: P3 d$ m" `由源码可知,入口方法调用双轴快速类的排序算法:DualPivotQuickSort.sort, 5 A* g$ N! Z9 l" l3 }, [通过名称可知,该方法使用快速排序, ) K# |% Z6 H3 T但是,进入该方法后,可知,不单单使用快速排序,且看下面的介绍。 / H, Y1 I, D3 S2 A% i9 l" E . H/ r' s) Q0 G' `9 w$ q S" M& L $ M% C- `- S& j/ T* G" P7 B好了。进入DualPivotQuickSort类的sort方法, 5 i, q4 I6 }/ H- Q0 b- w位置:java.util.DualPivotQuicksort#sort(int[], int, int, int[], int, int) ) N0 i _( |. q9 ~0 {: b8 n$ y1 x+ y由源码可知,当排序的数据量小于286时, ' y- y5 E! D( Q2 \: C+ e2 k使用快速排序:sort,但是,这里同样嵌入了其他排序算法,' I6 P% A' S5 X f* l4 t
数据量大于286时使用 归并排序。 + t, K* P! g6 }6 M7 ~先进入这里的快速排序看看。, v: s0 q, F1 Z: \
! {% ?* y0 [, \1 ?, w" p
4 q- R' R1 \0 Q下面进入满足286以内数据的快速排序算法:sort,$ t3 g* r+ e; D1 J1 C
位置:java.util.DualPivotQuicksort#sort(int[], int, int, boolean) : s4 ^7 v, J8 S# N q' N9 @/ [7 ^源码如下图所示,/ R, m l8 r' A4 a% r: m, Z
由源码可知,这里的快速排序方法同样分分了两层, 5 D% N& g% c/ v当数据量小于47条时,使用插入排序, 4 L. J! b& Q# W1 ~5 h1 x/ N大于等于47,小于286时,采用双轴快速排序。 . N$ I- u3 r0 B0 k% s2 q4 s6 y- w+ ?/ I. f% j
/ a+ Z8 L" l# ~2 ]2 s : T, ~5 A# s @, u( l3.2 包装类型数组排序 ' Q% V6 |0 a, G2 L' g# Z和上一节一样,先上结论,如下表所示, 6 c+ P( T* a. a2 [新排序算法:: t6 y9 R6 C+ a6 h2 ~