1 基础介绍7 z# N6 Q2 O5 ~. R: E- y" C( {% O
排序算法是很常见的一类问题,主要是将一组数据按照某种规则进行排序。 ( d1 p4 R6 w; V1 b* D+ ?0 O2 m7 x( ~9 R& A ~7 p( b
以下是一些常见的排序算法:/ U0 e( ~5 B4 M$ V$ s; _- V
$ d# ~9 k5 p! }
冒泡排序(Bubble Sort) + ` \" ]. X v7 D ?& P, i6 D8 n8 p, E1 V2 M; G2 d) p4 v
插入排序(Insertion Sort) & Z* z8 C# A0 u% | X* {+ ?+ z9 M3 Z* T+ m, R% d- S
选择排序(Selection Sort)+ ~! J( A5 J Q6 |% A
, a# h. b4 o+ ~( L) ^' e$ O归并排序(Merge Sort) ]4 ?6 k' B: ^! B( l 1 f% U0 O" U" |5 o& h) S* a& v# e# [快速排序(Quick Sort) 2 i- y3 O) X% \ . F! N0 C! _0 C8 O; P7 I' Q# _堆排序(Heap Sort), g. s9 o( A0 }9 H" g5 {
% p& Q# v8 F _8 ?" h. x
一、基本介绍介绍7 b8 j# ]% V( O+ R5 C5 {
1.1 原理介绍 7 x) M Q3 s" `归并排序(Merge Sort)是一种基于分治思想的排序算法,它将待排序的数组分成两部分,分别对这两部分递归地进行排序,最后将两个有序子数组合并成一个有序数组。它的时间复杂度为 O(nlogn)。 ; U" W: l& ?6 l9 x* v$ v. G/ ]. L. l$ J7 A* G4 X
归并排序的基本思路是将待排序的数组分成两个部分,分别对这两部分进行排序,然后将排好序的两部分合并成一个有序数组。这个过程可以用递归来实现。具体的实现步骤如下:0 x8 v& C1 T' n5 A: m2 D0 e
6 w$ O- }2 H" | ^& G% O
分解:将待排序的数组不断分成两个子数组,直到每个子数组只有一个元素为止。 . A6 z) p: i( T! r$ E; d3 m: z7 Z+ {- B! b, w- C; \( ]# ~" \# S% t" H, w# J
合并:将相邻的两个子数组合并成一个有序数组,直到最后只剩下一个有序数组为止。2 x* I) J5 q, l. p" x
6 s' i- Y8 t' ?1 A6 P7 m合并的过程中,需要用到一个辅助数组来暂存合并后的有序数组。具体来说,假设待合并的两个有序数组分别为 A 和 B,它们的长度分别为 n 和 m,合并后的有序数组为 C,那么合并的过程可以按如下步骤进行:" x! s2 l* D. P ^8 g5 |6 D* P
4 r7 Z) k1 y( A# C7 p定义三个指针 i、j 和 k,分别指向数组 A、B 和 C 的起始位置。 " f; ^4 G6 \) i& f " F+ L' k3 c0 g比较 A 和 B[j] 的大小,将小的元素放入 C[k] 中,并将对应指针向后移动一位。' T# m h9 K# d& r
. J& L' _' ~8 ?1 k
重复步骤 2,直到其中一个数组的元素全部放入 C 中。 - w Z3 L5 O0 \. h$ j7 W4 o! K/ C( @/ Y
将另一个数组中剩余的元素放入 C 中。: z# m' w0 H7 \ n* u
# Q. Q9 s3 k: }7 n( r6 w S归并排序的优点是稳定性好,即对于相等的元素,在排序前后它们的相对位置不会改变。缺点是需要额外的空间来存储辅助数组。' y7 @! }# X: R6 s- v6 s: t
9 S& D) k7 d. ]! E2 G0 a
原理简单示例 ; l" O' L: X) r5 M$ T6 I X6 a
以下是一个示例,演示了如何使用归并排序对一个数组进行排序: ' c8 Y9 B( q% j; f3 w 8 U& V9 s8 r- A* @假设要对数组 [5, 2, 4, 6, 1, 3] 进行排序。 1 @2 C" ]1 R9 L1 h h$ S j7 E: ` ! }1 M& }+ j& V首先将数组分成两部分:[5, 2, 4] 和 [6, 1, 3]。# T+ W% N& V2 Q* O2 z: j; V
' x2 ?/ i/ j4 q5 X; S4 v
对左右两部分分别递归调用归并排序。对于左半部分,继续进行分解,将其分成两部分:[5] 和 [2, 4]。对于右半部分,也进行相同的操作,将其分成两部分:[6] 和 [1, 3]。: W$ K& [6 f" f6 A! M
+ x( Z# T F& Y% O$ O A对于 [5] 和 [2, 4],由于它们的长度都小于等于 1,因此直接返回它们本身。对于 [6] 和 [1, 3],同样返回它们本身。 + |! R0 M6 j% g/ ^( G6 D x! r- {! W" k+ L2 T
接下来将排好序的左右两部分合并成一个有序数组。对于左半部分,由于它只有一个元素,因此可以直接将其作为有序数组。对于右半部分,需要将 [1, 3] 进行排序,排序后得到 [1, 3, 6]。. q4 \' C4 j* s+ E
C1 {2 M' P) A0 W0 k0 n |
将排好序的左右两部分合并成一个有序数组。对于左半部分,指针 i 指向其起始位置,即 0;对于右半部分,指针 j 指向其起始位置,即 0。比较左右两部分的元素大小,发现左半部分的第一个元素 5 大于右半部分的第一个元素 1,因此将 1 添加到新的数组 sorted_arr 中,并将右半部分的指针 j 向后移动一位。此时,sorted_arr 的内容为 [1]。接着比较左半部分的第二个元素 2 和右半部分的第一个元素 3,发现左半部分的元素较小,因此将 2 添加到 sorted_arr 中,并将左半部分的指针 i 向后移动一位。此时,sorted_arr 的内容为 [1, 2]。接着继续比较左右两部分的元素大小,将它们依次添加到 sorted_arr 中。最终得到排好序的数组 [1, 2, 3, 5, 6]。3 P) |$ x# G& v$ ?4 Z6 j; n. h
/ Z9 m! f9 D& l) q6 Q
因此,对于输入的数组 [5, 2, 4, 6, 1, 3],使用归并排序后得到的排好序的数组为 [1, 2, 3, 4, 5, 6]。 * J0 `7 u) h S0 l' e3 g/ T( U! y4 p; J: r
1.2 复杂度 + {/ ^8 n: s5 K2 e* e6 X8 f; b5 I归并排序的时间复杂度为 O(nlogn),其中 n 是待排序数组的长度。 & |+ R1 V0 X+ n+ y% f+ e G+ o9 w. C! n4 N6 M/ I! _5 v2 g
这个复杂度可以通过分治的思想来解释。 1 h: h( [6 q. ?* D0 Z% S6 N/ O, H6 h, a* P
首先将待排序的数组分成两部分,对每一部分递归调用归并排序,然后将两部分合并成一个有序数组。, v+ L8 E5 A% Y
mergeSort(arr, mid + 1, right); ! I( ~5 e s0 ?% g, y
merge(arr, left, mid, right);* E. w. M# P* T5 Z
} $ o- ~! N k9 E9 j. d- ~\" z0 |
5 B: a: p Q1 a5 u3 a- _% K
复制代码
这个函数使用递归的方式对数组进行排序。 2 D3 y8 F! b/ u1 n2 G5 E1 M# j7 D' ]* r' D% _" v
对于输入的数组和左右下标,首先判断左下标是否大于等于右下标,如果是,则直接返回。否则,将数组分成两个部分,分别对左半部分和右半部分递归调用 `mergeSort()` 函数。7 k: B0 v0 ]/ k+ a
; }; J$ S' s' g7 s( Q/ F最后,将排好序的左右两部分合并成一个有序数组,并将其作为结果返回。需要注意的是,此处的 `merge()` 函数是在 `mergeSort()` 函数中调用的,因为只有在递归到最底层时才会对单个元素进行排序,而在其他情况下需要将数组分成两部分进行递归调用。$ Y0 S$ a. O* A merge() 函数
public static void merge(int[] arr, int left, int mid, int right) {! F- h' D5 Z7 |: j
int[] temp = new int[right - left + 1]; \" {4 L8 ?7 j, ~( `! ~9 D
int i = left, j = mid + 1, k = 0;/ H, x: {6 y5 P7 ^
, e/ u2 H. D& y2 S r
while (i <= mid && j <= right) {( M0 Z' _1 ^/ c5 N9 p1 f
if (arr[i] < arr[j]) { 2 o9 Q8 `\" n5 b2 M, m\" e
temp[k++] = arr[i++];4 f! V& q+ U7 v9 l: |8 z
} else {5 ]* y, [: E( y5 M) l+ O( |& Y
temp[k++] = arr[j++];, i2 b# D. }' W9 P) ]
}. J5 b! x% N1 m0 A. a! z
}* J; x* Q5 }) I/ R8 i
- K) v3 l8 V, z# k
while (i <= mid) { : x6 w3 ~ O1 A* O% { F0 r: M( _
temp[k++] = arr[i++];# k! t- U1 a& x. ?6 v
}$ S% J# J' R5 Z/ r: ?1 @6 z
. ~+ R8 N ^- [. R& i& ^
while (j <= right) {! [% a- s% b) Z5 p* u: s8 V1 k9 X5 _
temp[k++] = arr[j++];7 c6 K/ W( [\" O\" \0 m- a
} # V\" M! ~0 b! U6 Z' K- e
7 v; j8 X9 m. e+ S0 R+ k: D, U; h! d7 F
for (int m = 0; m < temp.length; m++) {) a: A0 c& Z8 Z7 ~