forked from dairongpeng/algorithm-note
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path03.md
More file actions
638 lines (534 loc) · 20.8 KB
/
03.md
File metadata and controls
638 lines (534 loc) · 20.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
- [1 归并排序、随机快排](#1)
* [1.1 归并排序](#11)
+ [1.1.1 递归思路:](#111)
+ [1.1.2 非递归思路](#112)
+ [1.1.3 归并排序时间复杂度](#113)
+ [1.1.4 归并面试题](#114)
* [1.2 快排](#12)
+ [1.2.1 Partion过程](#121)
+ [1.2.2 快排1.0:每次partion搞定一个位置](#122)
+ [1.2.3 快排2.0:每次partion搞定一批位置](#123)
+ [1.2.4 快排3.0:随机位置作为num标记位](#124)
+ [1.2.5 快排的时间复杂度与空间复杂度](#125)
<h1 id='1'>1 归并排序、随机快排</h1>>
<h2 id='11'>1.1 归并排序</h2>>
1、 整体是递归的,左边排好序右边排好序,最后merge让整体有序,merge过程需要申请和被排序数组等长度的辅助空间
2、 让其整体有序的过程里用了排外序的方法
3、 利用master公式来求解归并的时间复杂度
4、 归并排序可改为非递归实现
<h3 id='111'>1.1.1 递归思路:</h3>>
> 主函数希望一个数组的0~3位置排序f(arr, 0, 3)
> 第一层递归希望f(arr, 0, 1)和f(arr, 2, 3)分别有序。
> 第二层递归:f(arr, 0, 1)希望f(arr, 0, 0)和f(arr, 1, 1)有序, f(arr, 2, 3)希望f(arr, 2, 2)和f(arr, 3, 3)分别有序。
> f(arr, 0, 0)和f(arr, 1, 1)已经有序,回到第一层递归f(arr, 0, 1)中去merge0位置的数和1位置的数后刷回元素组的0到1位置,0到1位置变为有序; f(arr, 2, 2)和f(arr, 3, 3)已经有序,回到f(arr, 2, 3)中去merge2位置的数和3位置的数后刷回原数组的2到3位置,2到3位置变为有序。
> f(arr, 0, 3)需要merge f(arr, 0, 1)和f(arr, 2, 3)此时f(arr, 0, 1)和f(arr, 2, 3)已经有序merge后copy到原数组的0到3位置。于是f(arr, 0, 3)整体有序
<h3 id='112'>1.1.2 非递归思路</h3>>
> 对于一个给定长度为n的数组arr,我们希望arr有序
> 初始分组为a=2,我们让每两个有序,不够一组的当成一组
> 分组变为a=2*2=4,由于上一步已经保证了两两有序,那么我们可以当前分组的四个数的前两个和后两个数merge使得每四个数有序
> 分组变为a=2*4=8,...直至a>=n,整体有序
```Java
package class03;
public class Code01_MergeSort {
// 递归方法实现
public static void mergeSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// 传入被排序数组,以及左右边界
process(arr, 0, arr.length - 1);
}
// arr[L...R]范围上,变成有序的
// L...R N T(N) = 2*T(N/2) + O(N) ->
public static void process(int[] arr, int L, int R) {
if (L == R) { // base case
return;
}
// >> 有符号右移1位,相当于除以2
int mid = L + ((R - L) >> 1);
process(arr, L, mid);
process(arr, mid + 1, R);
// 当前栈顶左右已经排好序,准备左右merge,注意这里的merge递归的每一层都会调用
merge(arr, L, mid, R);
}
public static void merge(int[] arr, int L, int M, int R) {
// merge过程申请辅助数组,准备copy
int[] help = new int[R - L + 1];
// 用来标识help的下标
int i = 0;
// 左边有序数组的指针
int p1 = L;
// 右边有序数组的指针
int p2 = M + 1;
// p1和p2都没越界的情况下,谁小copy谁
while (p1 <= M && p2 <= R) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
// 要么p1越界了,要么p2越界了,谁没越界把谁剩下的元素copy到help中
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
// 把辅助数组中整体merge后的有序数组,copy回原数组中去
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
}
// 非递归方法实现
public static void mergeSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int N = arr.length;
// 当前有序的,左组长度,那么实质分组大小是从2开始的
int mergeSize = 1;
while (mergeSize < N) { // log N
// L表示当前分组的左组的位置,初始为第一个分组的左组位置为0
int L = 0;
// 0....
while (L < N) {
// L...M 当前左组(mergeSize)
int M = L + mergeSize - 1;
// 当前左组包含当前分组的所有元素,即没有右组了,无需merge已经有序
if (M >= N) {
break;
}
// L...M为左组 M+1...R(mergeSize)为右组。右组够mergeSize个的时候,右坐标为M + mergeSize,右组不够的情况下右组边界坐标为整个数组右边界N - 1
int R = Math.min(M + mergeSize, N - 1);
// 把当前组进行merge
merge(arr, L, M, R);
// 下一个分组的左组起始位置
L = R + 1;
}
// 如果mergeSize乘2必定大于N,直接break。防止mergeSize溢出,有可能N很大,下面乘2有可能范围溢出(整形数大于21亿)
if (mergeSize > N / 2) {
break;
}
// 无符号左移,相当于乘以2
mergeSize <<= 1;
}
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
mergeSort1(arr1);
mergeSort2(arr2);
if (!isEqual(arr1, arr2)) {
succeed = false;
printArray(arr1);
printArray(arr2);
break;
}
}
System.out.println(succeed ? "Nice!" : "Oops!");
}
}
```
<h3 id='113'>1.1.3 归并排序时间复杂度</h3>>
> 递归复杂度计算,用master公式带入,子问题规模N/2,调用2次,除了递归之外的时间复杂度为merge的时间复杂度,为O(N)。a=2,b=2,d=1满足master第一条logb^a == d规则
```math
T(N) = 2T(N/2) + O(N) => O(N*logN)
```
> 非递归复杂度计算,mergeSize*2等于分组从2->4->8->...,每个分组下执行merge操作O(N)。所以非递归和递归的时间复杂度相同,也为O(N)*O(logN) = O(NlogN)
==递归和非递归的归并排序时间复杂度都为O(NlogN)==
Tips: 为什么选择,冒泡,插入排序的时间复杂度为O(N^2)而归并排序时间复杂度为O(NlogN),因为选择,冒泡,插入排序的每个元素浪费了大量的比较行为N次。而归并没有浪费比较行为,每次比较的结果有序后都会保存下来,最终merge
<h3 id='114'>1.1.4 归并面试题</h3>>
1、在一个数组中,一个数左边比它小的数的总和,叫做小和,所有数的小和累加起来,叫做数组的小和。求数组的小和。例如[1, 3, 4, 2, 5]
```text
1左边比1小的数:没有
3左边比3小的数:1
4左边比4小的数:1、3
2左边比2小的数为:1
5左边比5小的数为:1、3、4、2
所以该数组的小和为:1+1+3+1+1+3+4+2 = 16
```
> 暴力解法,每个数找之前比自己小的数,累加起来,时间复杂度为O(N^2),面试没分。但是暴力方法可以用来做对数器
> 归并排序解法思路:O(NlogN)。在递归merge的过程中,产生小和。规则是左组比右组数小的时候产生小和,除此之外不产生;当左组和右组数相等的时候,拷贝右组的数,不产生小和;当左组的数大于右组的时候,拷贝右组的数,不产生小和。实质是把找左边比本身小的数的问题,转化为找这个数右侧有多少个数比自己大,在每次merge的过程中,一个数如果处在左组中,那么只会去找右组中有多少个数比自己大
```Java
package class03;
public class Code02_SmallSum {
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
// arr[L..R]既要排好序,也要求小和返回
// 所有merge时,产生的小和,累加
// 左 排序 merge
// 右 排序 merge
// arr 整体 merge
public static int process(int[] arr, int l, int r) {
// 只有一个数,不存在右组,小和为0
if (l == r) {
return 0;
}
// l < r
int mid = l + ((r - l) >> 1);
// 左侧merge的小和+右侧merge的小和+整体左右两侧的小和
return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r);
}
public static int merge(int[] arr, int L, int m, int r) {
// 在归并排序的基础上改进,增加小和res = 0
int[] help = new int[r - L + 1];
int i = 0;
int p1 = L;
int p2 = m + 1;
int res = 0;
while (p1 <= m && p2 <= r) {
// 当前的数是比右组小的,产生右组当前位置到右组右边界数量个小和,累加到res。否则res加0
res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
// 只有左组当前数小于右组copy左边的,否则copy右边的
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return res;
}
// for test
public static int comparator(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int res = 0;
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < i; j++) {
res += arr[j] < arr[i] ? arr[j] : 0;
}
}
return res;
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
if (smallSum(arr1) != comparator(arr2)) {
succeed = false;
printArray(arr1);
printArray(arr2);
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
}
}
```
类似题目:求一个数组中的所有降序对,例如[3,1,7,0,2]降序对为:(3,1), (3,0), (3,2), (1,0), (70), (7,2)。也可以借助归并排序来解决。实质就是要求一个数右边有多少个数比自身小
> 什么样的题目以后可以借助归并排序:纠结每个数右边(左边)有多少个数比自身大,比自身小等。求这种数的数量等等
<h2 id='12'>1.2 快排</h2>>
<h3 id='121'>1.2.1 Partion过程</h3>>
> 给定一个数组arr,和一个整数num。请把小于等于num的数放在数组的左边,大于num的数放在数组的右边(不要求有序)。要求额外空间复杂度为O(1),时间复杂度为O(N)。例如[5,3,7,2,3,4,1],num=3,把小于等于3的放在左边,大于3的放在右边
思路:设计一个小于等于区域,下标为-1。
1、 开始遍历该数组,如果arr[i]<=num,当前数和区域下一个数交换,区域向右扩1,i++
2、 arr[i] > num, 不做操作,i++
> 给定一个数组,和一个整数num。请把小于num的数放在数组的左边,等于num的放中间,大于num的放右边。要求额外空间复杂度为O(1),时间复杂度为O(N)。[3,5,4,0,4,6,7,2],num=4。实质是经典荷兰国旗问题
思路:设计一个小于区域,下标为-1。设计一个大于区域,下表为arr.length,越界位置。
1、 如果arr[i]当前位置的数==num, i++直接跳下一个
2、 如果arr[i]当前位置的数< num,当前位置的数arr[i]和小于区域的右一个交换,小于区域右扩一个位置,当前位置i++
3、 如果arr[i]当前位置的数> num,当前位置的数arr[i]与大于区域的左边一个交换,大于区域左移一个位置,i停在原地不做处理,这里不做处理是因为当前位置的数是刚从大于区域交换过来的数,还没做比较
4、i和大于区域的边界相遇,停止操作
<h3 id='122'>1.2.2 快排1.0:每次partion搞定一个位置</h3>>
思路:在给定数组上做partion,选定数组最右侧的位置上的数作为num,小于num的放在该数组的左边,大于num的放在该数组的右边。完成之后,把该数组最右侧的数组num,交换到大于num区域的第一个位置,确保了交换后的num是小于等于区域的最后一个数(该数直至最后可以保持当前位置不变,属于已经排好序的数),把该num左侧和右侧的数分别进行同样的partion操作(递归)。相当于每次partion搞定一个数的位置,代码实现quickSort1
<h3 id='123'>1.2.3 快排2.0:每次partion搞定一批位置</h3>>
思路:借助荷兰国旗问题的思路,把arr进行partion,把小于num的数放左边,等于放中间,大于放右边。递归时把小于num的区域和大于num的区域做递归,等于num的区域不做处理。相当于每次partion搞定一批数,与标记为相等的数。代码实现quickSort2
> 第一版和第二版的快排时间复杂度相同O(N^2):用最差情况来评估,本身有序,每次partion只搞定了一个数是自身,进行了N次partion
<h3 id='124'>1.2.4 快排3.0:随机位置作为num标记位</h3>>
==随机选一个位置i,让arr[i]和arr[R]交换,再用=arr[R]作为标记位。剩下的所有过程跟快排2.0一样。即为最经典的快排,时间复杂度为O(NlogN)==
> 为什么随机选择标记为时间复杂度就由O(N^2)变为O(NlogN)?如果我们随机选择位置那么就趋向于标记位的左右两侧的递归规模趋向于N/2。那么根据master公式,可以计算出算法复杂度为O(NlogN)。实质上,在我们选择随机的num时,最差情况,最好情况,其他各种情况的出现概率为1/N。对于这N种情况,数学上算出的时间复杂度最终期望是O(NlogN),这个数学上可以进行证明,比较复杂
> 例如我们的num随机到数组左侧三分之一的位置,那么master公式为
```math
T(N) = T((1/3)N) + T((2/3)N) + O(N)
```
> 对于这个递归表达式,master公式是解不了的,master公式只能解决子问题规模一样的递归。对于这个递归,算法导论上给出了计算方法,大致思路为假设一个复杂度,看这个公式是否收敛于这个复杂度的方式,比较麻烦
<h3 id='125'>1.2.5 快排的时间复杂度与空间复杂度</h3>>
> 时间复杂度参考上文每种的复杂度
> 空间复杂度:O(logN)。空间复杂度产生于每次递归partion之后,我们需要申请额外的空间变量保存相等区域的左右两侧的位置。那么每次partion需要申请两个变量,多少次partion?实质是该递归树被分了多少层,树的高度,有好有坏,最好logN,最差N。随机选择num之后,期望仍然是概率累加,收敛于O(logN)。
```Java
package class03;
public class Code03_PartitionAndQuickSort {
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// partion问题
public static int partition(int[] arr, int L, int R) {
if (L > R) {
return -1;
}
if (L == R) {
return L;
}
int lessEqual = L - 1;
int index = L;
while (index < R) {
if (arr[index] <= arr[R]) {
swap(arr, index, ++lessEqual);
}
index++;
}
swap(arr, ++lessEqual, R);
return lessEqual;
}
// arr[L...R] 玩荷兰国旗问题的划分,以arr[R]做划分值
// 小于arr[R]放左侧 等于arr[R]放中间 大于arr[R]放右边
// 返回中间区域的左右边界
public static int[] netherlandsFlag(int[] arr, int L, int R) {
// 不存在荷兰国旗问题
if (L > R) {
return new int[] { -1, -1 };
}
// 已经都是等于区域,由于用R做划分返回R位置
if (L == R) {
return new int[] { L, R };
}
int less = L - 1; // < 区 右边界
int more = R; // > 区 左边界
int index = L;
while (index < more) {
// 当前值等于右边界,不做处理,index++
if (arr[index] == arr[R]) {
index++;
// 小于交换当前值和左边界的值
} else if (arr[index] < arr[R]) {
swap(arr, index++, ++less);
// 大于右边界的值
} else {
swap(arr, index, --more);
}
}
// 比较完之后,把R位置的数,调整到等于区域的右边,至此大于区域才是真正意义上的大于区域
swap(arr, more, R);
return new int[] { less + 1, more };
}
public static void quickSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process1(arr, 0, arr.length - 1);
}
public static void process1(int[] arr, int L, int R) {
if (L >= R) {
return;
}
// L..R上partition 标记位为arr[R] 数组被分成 [ <=arr[R] arr[R] >arr[R] ],M为partion之后标记位处在的位置
int M = partition(arr, L, R);
process1(arr, L, M - 1);
process1(arr, M + 1, R);
}
public static void quickSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process2(arr, 0, arr.length - 1);
}
public static void process2(int[] arr, int L, int R) {
if (L >= R) {
return;
}
// 每次partion返回等于区域的范围
int[] equalArea = netherlandsFlag(arr, L, R);
// 对等于区域左边的小于区域递归,partion
process2(arr, L, equalArea[0] - 1);
// 对等于区域右边的大于区域递归,partion
process2(arr, equalArea[1] + 1, R);
}
public static void quickSort3(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process3(arr, 0, arr.length - 1);
}
public static void process3(int[] arr, int L, int R) {
if (L >= R) {
return;
}
// 随机选择位置,与arr[R]上的数做交换
swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
int[] equalArea = netherlandsFlag(arr, L, R);
process3(arr, L, equalArea[0] - 1);
process3(arr, equalArea[1] + 1, R);
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
int[] arr3 = copyArray(arr1);
quickSort1(arr1);
quickSort2(arr2);
quickSort3(arr3);
if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Oops!");
}
}
```