分治思想

分治,顾明思意就是分而治之,将一个大问题分解成小的子问题来解决,小的子问题解决了,大问题也就解决了。

分治与递归的区别

分治算法一般都用递归来实现的。分治是一种解决问题的处理思想,递归是一种编程技巧。

归并排序

算法原理

归并的思想

先把数组从中间分成前后两部分,然后对前后两部分分别进行排序,再将排序好的两部分合并到一起,这样整个数组就有序了。

这就是归并排序的核心思想。如何用递归实现归并排序呢?

写递归代码的技巧就是分写得出递推公式,然后找到终止条件,最后将递推公式翻译成递归代码。

递推公式

merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))

终止条件

p = r 不用再继续分解

代码实现

使用哨兵可以简化代码

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
import java.util.Arrays;

public class MergeSort {
public static void merge(int[] a, int begin, int end) {
if (begin == end) {
return;
}
if (begin < end) {
int mid = (begin + end) / 2;
merge(a, begin, mid);
merge(a, mid + 1, end);
mergeAndSort(a, begin, mid, end);
}
}

public static void mergeAndSort(int[] a, int begin, int mid, int end) {
int[] left = new int[mid - begin + 2];
int[] right = new int[end - mid + 1];
for (int i = begin; i <= end; i++) {
if (i <= mid) {
left[i - begin] = a[i];
} else {
right[i - mid - 1] = a[i];
}
}
left[mid - begin + 1] = Integer.MAX_VALUE;
right[end - mid] = Integer.MAX_VALUE;
int i = 0;
int j = 0;
int index = begin;
while (index <= end) {
a[index++] = left[i] < right[j] ? left[i++] : right[j++];
}
}

public static void main(String[] args) {
int[] a = {1, 9, 5, 7, 4, 6, 3, 8, 11, 24};
merge(a, 0, a.length - 1);
System.out.println(Arrays.toString(a));
//打印结果为:[1, 3, 4, 5, 6, 7, 8, 9, 11, 24]

}

}

性能分析

算法稳定性

归并排序是一种稳定排序算法。

时间复杂度

归并排序的时间复杂度是O(nlogn)。

空间复杂度

归并排序算法不是原地排序算法,空间复杂度是O(n)

因为归并排序的合并函数,在合并两个数组为一个有序数组时,需要借助额外的存储空间

快速排序

一般情况下,快速排序被认为是最快的排序算法(人如其名啊),因此可以说是最常用的排序算法,并受大多数公司的青睐,是一定要熟练掌握的。

算法原理

快排的思想

如果要排序数组中下标从p到r之间的一组数据,我们选择p到r之间的任意一个数据作为pivot(分区点)。然后遍历p到r之间的数据,将小于pivot的放到左边,将大于pivot的放到右边,将povit放到中间。

经过这一步之后,数组p到r之间的数据就分成了3部分,前面p到q-1之间都是小于povit的,中间是povit,后面的q+1到r之间是大于povit的。

根据分治、递归的处理思想,我们可以用递归排序下标从p到q-1之间的数据和下标从q+1到r之间的数据,直到区间缩小为1,就说明所有的数据都有序了。

递推公式

quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1, r)

终止条件

p >= r

代码实现

左右两个指针分别向左向右移动,取最后一位做临界值,左边遇到比这个值大的,就交换,换反方向指针比较,遇到比这个值小的,再次交换。

参考这里

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
import java.util.Arrays;

public class QuickSort {
public static void quickSort(int[] a, int begin, int end) {
if (end <= begin) {
return;
}
int i = begin;
int j = end;
int tmp = a[end];
boolean tag = true;
while (i != j) {
if (tag) {
if (a[i] <= tmp) {
i++;
continue;
} else {
a[j] = a[i];
j--;
tag = false;
}
} else {
if (a[j] >= tmp) {
j--;
continue;
} else {
a[i] = a[j];
i++;
tag = true;
}
}
}
a[i] = tmp;
quickSort(a, begin, i - 1);
quickSort(a, i + 1, end);
}

public static void main(String[] args) {
int[] a = {5, 7, 8, 2, 6, 125, 6, 41, 341, 34, 63, 28, 97};
quickSort(a, 0, a.length - 1);
System.out.println(Arrays.toString(a));
}
}

性能分析

算法稳定性

快速排序是不稳定的排序算法。

时间复杂度

如果每次分区操作都能正好把数组分成大小接近相等的两个小区间,

那快排的时间复杂度递推求解公式跟归并的相同。快排的时间复杂度也是O(nlogn)。

如果数组中的元素原来已经有序了,快排的时间复杂度就是O(n^2)。

前面两种情况,一个是分区及其均衡,一个是分区极不均衡,

它们分别对应了快排的最好情况时间复杂度和最坏情况时间复杂度。

T(n)大部分情况下是O(nlogn),只有在极端情况下才是退化到O(n^2)。

空间复杂度

快排是一种原地排序算法,空间复杂度是O(1)

归并排序与快速排序的区别

归并排序

先递归调用,再进行合并,合并的时候进行数据的交换。所以它是自下而上的排序方式。

何为自下而上?就是先解决子问题,再解决父问题。

快速排序

先分区,在递归调用,分区的时候进行数据的交换。所以它是自上而下的排序方式。

何为自上而下?就是先解决父问题,再解决子问题。