【基于C的排序算法】归并排序; l4 Y3 g9 k1 |6 ~) L
: M- W' q; S1 u" A- I: ^+ J
前言 ! [! R" O* L T9 K P本文基于C语言来分享一波笔者对于排序算法的归并排序的学习心得与经验,由于水平有限,纰漏难免,欢迎指正交流。 ; L& u2 N( Q' B/ |* B/ A8 J z/ g, e3 `: p. z- }+ H1 {% G
归并排序$ V, a) f) Y% P$ ^+ E2 ~2 ?9 V
基本思想, e' K1 D. |# ^. f7 a' U; A/ S
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 ) U. D) P- X/ Q4 Y* |9 g* t! |) v% C; E3 Y% d9 n/ \+ _- ]% Q
) M' D9 c- o! b 8 P) M5 ~+ ]: g& n& D( [' L 合并的思想其实和有道题目的思想如出一辙: ' i6 F( P6 n& V3 j" N 0 S' x2 k2 g1 a9 w q W* h5 L 8 @3 R. i# M4 n/ M % K; Y4 I6 B$ [ I" {$ u 我们考虑新开一个数组来放入排序好的值,要不改变顺序的话就要用尾插,让nums1和nums2数组元素的较小值尾插到新数组中,两个数组总会有一个先插完,另一个数组就把剩下的全部尾插接在新数组后面。4 o: s: Z3 ~0 M; A/ q
. t- |2 }/ G. h
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gsgj7Cmx-1662793985599)(https://typora-picture-1313051246.cos.ap-beijing.myqcloud.com/归并原理.gif)] ) s3 w7 A4 s# [/ V+ m( e7 y$ o; g5 p& Y2 ~+ m2 a1 s
int* merge(int* nums1, int m, int* nums2, int n) 5 c8 u; [, _: I& a2 A* k1 y{ 1 W1 W0 y$ D5 e9 P$ c int* arr = (int*)malloc((m + n));6 a# X- F/ Y7 ?8 Q. L
if(arr == NULL) 8 y0 V' \( b. w- [( a {( K6 N5 p' P, ~7 D
perror("malloc fail");* h. Q; \3 V% m! O; j1 l2 _/ B
return;2 @' v3 I/ w. O1 X
} 1 O( t, b8 m6 X9 C; q- F3 m 7 \2 R4 a# s" w int p1 = 0; & R* P L$ \- B Z; b int p2 = 0;% K! S) P3 t! [7 }7 a
int cnt = 0;! G7 `; c. d: j8 ?# Q j
while(p1 < m && p2 < n)* M& N1 j% |" u) ~( v( k
{* z$ w; c5 k' _9 G6 B
if(nums1[p1] < nums2[p2]) ; o& |* C( E! l8 T* \ { & G) H5 x) U! ~& Z0 Q" Y' K8 f: a' d arr[cnt++] = nums1[p1++];' ?& a- w+ y% j4 E0 z5 E' W/ j
}/ f" I n* |$ I% T
else% q% i; t1 u! P8 d
{ . E3 y1 G6 r8 [1 l6 E arr[cnt++] = nums2[p2++];, C$ h+ `4 z! [
}3 Y( H/ d" e1 g% @
}: X& p: L) O9 x8 q. S& Z9 w
while(p1 < m) ) h& q' ^% ?. t arr[cnt++] = nums1[p1++]; ! t: _7 f- ?/ o+ R* v * e) J) g- V4 o% u# P B while(p2 < n)6 X; H# R- j5 H. ] u
arr[cnt++] = nums2[p2++];3 e3 Z5 T5 {: l0 w# }
% Z6 q: v" h5 j7 y( u. I: @ return arr; 3 J L5 p5 o/ z- q" T} ( _- |! S, B" K: f; K- @ : I- P9 m/ @+ g5 d( J/ I1 Q1 0 `( d9 f7 }3 v3 n9 i) [& g7 R21 b& y& e$ Y# C" N; F
3) R0 F) K% a* f- ?0 G0 ?2 k6 B
46 P1 |) i3 M% a) n A: C( ]
5 + U6 D( p, z. @- s6 ~6$ X9 v& h" c" B1 N& S. E
7* i2 Y/ _! d* o0 T3 C! D- u8 K
87 {' [$ w4 }6 }2 {. P* z
9( d) D1 ~# u6 L4 v* ?% R3 Q4 \
10$ }# I9 e+ ?) p: Q4 G
11/ Z# U) U2 B# T5 Y
12* a) t2 C1 F) E) N' u
13 L5 w3 a9 ~& a( S: F- T; w, i* b14 . S( z! e4 c# \0 K158 `7 G5 ^6 L# ]3 U8 ~; c' z* O
16 o9 K1 J% D% R; x% |; R' O
17 n W! P( n" s
189 d6 y/ T$ M! p' _$ d4 E
19 * c1 u3 i v" `( v' g/ }1 {+ @205 x/ `$ h2 ^0 f
21 8 V: R% c Q' G* t5 F22 % x0 B( R# w# M+ r8 N _" e! J9 I233 O) Q& n. h: f5 ]2 t5 P( t$ q
24 " u; b/ O9 m: U7 Q# U25 0 ~$ P6 x# L& J0 ^26 ' B \) w4 b4 ?9 M- i. v X27 , \. L& H3 B: P" `7 N, z: ~28 . J9 F6 g8 _7 T# V29! y8 z7 F+ [" Y7 Y3 R; @1 a
30+ K0 n" C/ q0 `" C/ G
31 4 B" [, R" m/ j3 I5 i- ~; ~3 @' W 所谓的合并就是利用这样尾插的思路,我们就想到要把原数组分解成两个有序子序列来合并,那么如何将原数组分解成有序子序列呢?容易想到用递归,其实非递归(迭代)也能实现,我们接下来具体来看看实现方法。 $ q- P. G: W) G8 C3 f U! m6 b- ^' P3 {3 K; s% X- z8 C0 E7 u/ N* K1 k, j2 W
递归实现" j4 L: ]6 {3 K+ S3 `& c, A* W' ~
通过二分分割出两个子序列,然后进行递归,先左后右,不断分割直到子序列仅有一个元素时子序列一定有序,这时候就可以往回退了,等到左右子序列都退回后就可以归并了,不过不能直接归并到原数组,因为会覆盖而丢失值,不妨归并到另一个辅助数组,归并后再拷贝回原数组,思想就是前面讲的合并的思想。5 u0 j7 b9 k1 | [8 S' S; W
3 h c) b/ _$ |" r: n. u
6 d- ~$ w+ I0 i7 s8 n, g2 M1 V. N h: J+ g" {2 P$ \
& \ \5 R/ C+ g & w9 d G' V$ P/ rvoid _MergeSort(int* arr, int* tmp, int left, int right) # y( ^9 Z, s1 l, ~, e7 A5 I3 O{4 M9 y9 A) z, r/ G5 f
assert(arr);# y! z/ o9 L! ` F4 f8 |
t7 p8 }. _1 |+ ~6 A8 ^ if (left >= right)//递归结束条件不要漏了) u+ J# ~; b- \/ @! f- {4 C
return;( `( z9 |# E8 U, C D+ P
4 d+ j% p+ M) h( C9 W
int mid = (right - left) / 2 + left;5 z9 b) s7 x! A3 w1 F6 \9 J
8 I0 m& g5 e' K& X5 J3 G( w //划分左右子区间[left, mid]和[mid + 1, right] " P4 T2 ^# v) b( H. V" E9 T _MergeSort(arr, tmp, left, mid); 3 N, I0 u% F. S5 W# h: n7 G _MergeSort(arr, tmp, mid + 1, right);" C: c7 r7 e# _( R# e5 Y0 e1 n
: ]8 U- o! C$ P) |7 `8 T" I5 @) [) ?
//归并 o+ w: G8 B; M( G5 w int begin1 = left, end1 = mid;# a) V" P; n$ L8 W
int begin2 = mid + 1, end2 = right; - b7 k$ P1 {( s5 Z1 S int i = left; ( p( k2 O+ N0 N+ \( l while (begin1 <= end1 && begin2 <= end2)" h+ Q, ~, s) ~* E
{* {: W6 n) w6 c( r, w+ ^& ~) F, G
if (arr[begin1] < arr[begin2]) ; F5 q0 Z u5 d2 s+ a tmp[i++] = arr[begin1++];3 R0 V# n; y6 R6 \3 j8 S% C" b9 j
else 3 h9 [1 u( T2 X9 k1 M& T: B2 \ tmp[i++] = arr[begin2++]; 2 P, U; M9 O9 B/ J }# x% Y% _( f% c, B& B
, z: v# a8 J7 |- g* g while (begin1 <= end1)+ S: M7 S" F4 b9 Q% c5 a, J( |2 r4 b3 i: [; ?
tmp[i++] = arr[begin1++]; 6 L. M8 Y$ o' \6 m2 H9 z8 _& `5 B1 s while (begin2 <= end2) - e! s* R' @8 d; S& N! E0 N tmp[i++] = arr[begin2++];1 T, r. c! N c* q/ R3 B5 D w; e* H
8 c3 z7 e8 x% t# O1 ? //拷贝回原数组——归并哪部分就拷贝哪部分回去8 V9 ~! _* k: f
//而不是拷贝整个数组回去 4 G4 F, m1 p& d( U% ` memcpy(arr + left, tmp + left, sizeof(int) * (right - left + 1)); ( i7 m: ^, \0 S+ ], x6 D+ K}2 E/ f, r: H# @6 u& c
; ?# g8 B* x* N0 r2 @
void MergeSort(int* arr, int left, int right) , i/ G& m/ P7 {" i# ~' N{, z2 ^: K' k, M6 p H" z5 L
assert(arr);/ @3 h# d# |. s* M7 u
4 h% l/ w4 b. W8 a6 U' S" j" k
int* tmp = (int*)malloc((right - left + 1) * sizeof(int)); 7 B; \- k) L' i* A: d% P if (tmp == NULL) ; r V: v3 ^6 Q { - A* t6 g w C L& _% h perror("malloc fail"); $ a: q7 R9 Z. @ return;8 k9 {! ~5 y; F" y( Q3 w+ v
}9 M' a- N- F8 X
" h1 ]" _% @& q( g _MergeSort(arr, tmp, left, right); 3 E8 a3 A! J$ p5 ^0 U O1 J9 F/ P+ b" w2 z& L4 {% V% X
free(tmp);* s9 @3 I1 Y9 X$ B% E
tmp = NULL; ! P0 V' L* ^) W1 v7 @}% A+ H: m% Q' Q6 @" C3 e" w, q+ G
1 z' A0 F: C; A6 W! d; u; {5 A1 g) g
16 l" t6 D1 k5 o+ I! P6 T8 c
2$ E3 h8 D @4 q
3( S4 `0 t3 q N2 Y4 v
4 : ?! b' s; C" j5. d" \2 ?2 u3 j9 ]0 `
6 ( m" v/ V# L$ H: s3 ~/ K" [7; k+ l# K. J$ L6 {9 U. p( R
8 ; X( s& h$ f, O, P* V4 o9: u2 l( l: G0 o ]3 B" V
10( j6 |) i! X8 K q" W# l5 Z1 B
11 O$ P$ B! \2 U' g( p, J126 L9 V. |8 u7 Y5 N/ N P0 k, M
13 : ~4 f1 c6 M4 ]" W# c14 ; A% n; K; {+ h1 i15* T; G% C7 ?! t1 D/ {8 l
16* e2 b+ j& `* v9 V. n0 a
17 4 M3 d, J2 B$ \, U2 U0 t1 p18) o+ C0 @0 X6 N* W
19- c' l$ h( j$ y& t) H
20 . E `3 C& j! ]* u; _: O21+ J- W3 h l3 f, f$ Y3 u0 J' g" j4 r
22/ S, G) V6 d& e0 t" O9 j
23 7 p# U# l& E4 \0 U& H8 L) d24 . p; U% z. c0 t4 X9 ~255 U- E6 a# h! |( x* P
26 # Y" Y) R+ B' L* E27 ; Y# y/ [, N. t( [, f28& [ d+ R; r- v S: v
29 7 I/ y) [- N# S6 b& a6 ]30* X% h$ k) F. @) A4 o# K
317 Z8 Z) K b- o& e* l( ~
327 k$ R$ y9 L1 A0 y
334 U/ }" n8 v" R- \3 u* Z
34, ]' C' O* A8 j( K) n
35 9 @5 Q3 w% @+ M1 u; ? r36 ! N$ @2 w7 W/ I37 1 ?0 U% _/ D3 ]+ I38 ! y, T9 j& I+ P. r3 Z7 O39 ) B( b( \ L; `* F2 |+ i" |' S40) @' }4 N6 v( Y8 T# `4 I
411 B* h% r2 Q3 v' O* I$ z) P
421 J1 O. Q. }, ^3 H0 o5 A
43 & x8 l: V1 w/ p- o4 F44. t3 }4 B* x4 O) v
458 U7 S1 V/ j/ }+ L+ h
46' T& g. m/ e# E5 w0 M1 e
47% T& a8 E. }" C
48 3 r' d9 f5 H! N0 ^" k) B49 0 ?/ B+ [! S! x& L1 K8 T+ g* N/ j: b50* w+ s" u. R% q- d4 K1 [
51 - D. |0 c$ _6 U7 s! c' x非递归实现 : G( U* P1 r7 r( B' v 直接在原数组基础上归并,设gap是子区间元素个数,从gap = 1开始,因为仅有一个元素的子区间一定有序。为了方便,我们把gap=1叫做第一层,以此类推。: T( Z- Z% n# j4 B2 @, A4 ]* G% Q+ D
6 }; x- `; o) j
( P1 {) g- g' v& I. N$ T : S& u, O/ l$ \. f: q3 X0 n 不同的gap值代表所在层数不同,每一层都是从左到右两组为一对地取对配对归并,i就是每对起始位置,之所以更新i的时候要i += 2 * gap是因为每队两组、每组gap个元素,所以要让i跑到下一对的起始位置的话不就要跳过一整对的空间嘛。( P; C/ h4 i2 m) c+ b) ^
* g2 h6 B; G4 Z3 G3 m( v, |0 N 还要注意区间的取值,每个区间就是一组,就有gap个元素。 `) w7 n3 E1 s
+ w& C( _) J& Z$ W6 |2 m
整体拷贝遇到越界就会比较难搞,所以我们这里用部分拷贝的思路,每次归并后直接拷贝,要注意一下指针偏移量不是begin1而是i,因为begin1已经在归并过程中被改变了。 m( o4 l! j( p: L8 V6 _2 ? - G0 t1 S. k7 u0 \4 [' E) G代码实现# F, A6 m2 y: ~9 q9 n% `% S, j( h0 ^* g
8 K, k) F ?$ m# {" j( q
void MergeSortNonR(int* arr, int sz)+ a9 B$ g. f+ h! G, d
{3 k* g' P; Y7 M4 |$ g
assert(arr);9 H$ b1 X) k& p- ^& H0 j
+ W; X$ \" V8 q, A6 y
int* tmp = (int*)malloc(sz * sizeof(int));6 ]7 a# n" N+ H: d, p, [. Q" ~' ~
if (tmp == NULL) & e- z4 U, K+ u% j" E5 t" u { - z) J8 P% u" y4 ^+ R2 X perror("malloc fail"); 0 D {8 n" G" [1 \4 g return;7 f6 `8 A$ A. e
} 4 R3 ~/ w% r ]0 f- f1 T3 h , \$ O6 m: p/ E% @' g3 N% H3 C int gap = 1; ' x6 c: t% z0 w# J while (gap < sz) 2 h3 o/ T- Q- e- {7 ^2 q { " f3 g" c+ s z0 C, P for (int i = 0; i < sz; i += 2 * gap) {8 u0 B, F6 e0 t {# F. R7 |$ X/ m, D
int begin1 = i, end1 = begin1 + gap - 1;' d& d: ~0 W0 x6 P
int begin2 = end1 + 1, end2 = begin2 + gap - 1;5 @2 J1 ?1 g+ l8 S
int j = begin1; 7 n0 d" G/ c' U: i* F6 _6 d ?7 e9 t l. A/ a //归并& i3 j5 O" B9 `3 B( Y
while (begin1 <= end1 && begin2 <= end2). X4 F3 g1 A& m" B
{ : d# ~2 x. K) F. S" T& t6 Y8 t+ e if (arr[begin1] < arr[begin2]) " |2 Y# v4 G% ?: G4 x. y tmp[j++] = arr[begin1++]; $ q; {/ Z& f3 N3 F D else ; r# [- m; U% V1 X9 G! S; i1 A
tmp[j++] = arr[begin2++];! x" X, a9 G" \* g. m. @( P2 e
}% u" T6 b( b9 v5 |" m3 Z: ]5 s
4 Y: Q: k+ c+ j* o while (begin1 <= end1)3 S: I3 _0 O5 L5 ?# B( X
tmp[j++] = arr[begin1++];; L$ `& ?- \+ A8 Z$ e
while (begin2 <= end2)% D" \9 ] M/ ~. `3 U0 h
tmp[j++] = arr[begin2++];2 G. h7 ^' Z% K; j$ N2 e
: {! X4 m H: n z //拷贝回原数组——归并哪部分就拷贝哪部分回去 + u* a( l$ X. K0 ]* G; U: ` memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1)); 7 y3 z6 G U& v6 N, A } ! ?* u$ p7 e) Z' M9 x4 A7 O7 e gap *= 2;) e+ ^7 f r5 u, U# R4 e) D; s
}- k7 g$ n) ~/ Q I" d% n
1 P8 `% o' b0 j) S1 U( l8 R' _: o- S* `
}0 A& D! b# i, l/ Q8 I4 |
3 Q, M) ?/ C' b- d M% _5 t
1) T8 i2 h* `% r; J
2 $ e4 |6 [7 L" M$ G/ F, r3 ; D5 Q7 |: |9 _4 {4 2 G/ h5 @2 m" _: D$ @& D5, Y& s; @" d/ C/ T- g
6/ E m* E! h. A
79 q1 R7 |. M" W% u0 q
8# E% L# \( [- A
9 8 j. X6 h: W I109 D4 Y! n4 l6 J2 k; y, n# `% ?
11 Y1 o: w6 W1 l
128 f) `% b6 H8 N8 ^; R2 y
13 ( U+ J. Y3 H& n6 x14; U( L2 t8 V5 Y
159 s0 _$ ?" E' T6 P9 E8 z
16 6 Z% o. t- U( M17 6 d: J" C3 M2 x' z% M2 C7 ~18 6 b& z J0 C0 X* N, J; Z l1 O19 ( f4 R- K3 R& H9 |20 1 p+ ]( r* t3 m7 D9 d7 V: U; _21 , t' @5 y3 i2 M! O# d22 6 }3 X2 X L g6 N23 ! ?5 `% A! @' q; ~24 $ P4 x. ?. c5 ~" B) p/ j25 # P4 X% N1 X; y% T8 r26 1 E% |4 i" {2 [) u27 1 u5 e/ |1 q( A4 {* J28 " x" b# O4 R$ V& e* {* N29" V; H4 U% p+ x5 s0 |$ X
30) [6 e% k7 [% a- g
31! Q& w$ Y4 N4 O
32 4 A4 e X* e/ f33 # s6 X m6 w' B' z) V34. {" u5 [5 ~8 m: _8 B
358 c; O/ y: o. P ]
36' T# T" e% T' d; X2 Y
37 9 k6 v. l" M: d& a2 _& l$ Z3 U38 . k0 Y/ q4 l( M ?- w7 e$ N393 k: v, r, g& Q& a; }
40 1 Y1 [9 S2 C: i41- }) F* O: T, M) k+ k7 o
边界问题& n3 h4 O9 C9 R! H: r* }* U( C
实际上还需考虑是否越界的问题,上面那段代码并没有考虑,所以还需一些改进。为什么会存在越界的可能呢?因为我们是以gap的整数倍去取区间来归并的,而区间个数不一定总能满足两两配对。% i/ s( n- X( C% b* s D7 |, {! M
, x+ @( H2 U; B- U/ P' ]5 F& Y
举个例子,就把前面的那个数组后面加上个元素5,没有越界检测时出现的情况:5 Q- {( b' e: n
7 p) r3 E5 I2 g5 [8 o
* Q' G1 d2 a6 B+ }0 u! T" N' F ) a s9 f! _9 y1 X8 w9 w由上图可知越界分为三类(这里将[begin1, end1]、[begin2, end2]分别作为第一和第二组)! u9 r Z; m$ c
2 Q4 j+ k8 M. ^9 N第一组越界(即end1越界): B! K0 v0 j" e/ U
3 n K( J( k$ z1 n7 o; [
应对方法:这种情况一般介于第一层和最后一层之间,break跳出for循环,不让越界值被访问。" f6 o7 X5 ?; y Q6 w
+ _# P( C8 h T& h) o& U6 m# D# [5 u
第二组全部越界(即begin2和end2越界)1 x! w' b* j d t3 U1 t0 [, C. o
: D# A+ c/ h( K8 _应对方法:这种情况一般在第一层,break跳出for循环,不让越界值被访问。 6 P v3 m7 S' C3 b! S ! _- P) X5 N* i9 x5 j第二组部分越界(即end2越界). J; d1 s% A6 E) N+ g; M" O8 T
" Y+ A- j' K* r" o9 |0 N |4 m; C: t应对方法:实际上这时候就到了最后一层了,把end2修正为sz - 1,不跳出for循环而继续归并。4 U9 A( E/ o4 Q, u; Z5 G2 ~+ k, X: \' a