快速排序

快速排序

快速排序效率高,空间复杂度底,在工作中一般是用的最多的排序算法,所以需要掌握。

在看快速排序之前,先看一下另一个比快排简单点的问题,就是荷兰国旗问题

给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)

解决思路:

准备准备三个指针less、more、cur

  • less指针,从数组的第一个元素的前一个位置开始,第一个元素的位置为0,所以less从-1开始
  • more指针,从数组最后一个元素的后一个位置开始,最后一个元素的位置是数组长度减一位置,所以more的值就是数组的长度
  • cur指针,当前比较的元素的位置,从第一个元素开始比较,也就是从0开始
  • 如果当前元素小于num,less的后一个位置的元素跟cur位置的元素交换。交换完成之后,当前位置cur加一,比较下一个元素
  • 如果当前元素大于num,more的前一个元素跟cur位置的元素交换。交换完成之后,此时交换回来的元素我们不知道它大于num还是小于num,所以cur位置不变,继续比较
  • 如果当前元素的值等于num,cur直接加一,比较下一个。

代码如下

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
public static int[] partition(int[] arr, int num) {
if(arr == null || arr.length < 2){
return arr;
}
//less从数组第一个元素的前一个元素开始
int less = -1;
//more从数组的最后一个元素的后一个元素开始
int more = arr.length;
//cur 当前比较的位置
int cur = 0;
while (cur < more) {
if (arr[cur] < num) {
//less的后一个位置和cur位置元素交换
//交换之后,cur加一
swap(arr, ++less, cur++);
} else if (arr[cur] > num) {
//cur和more位置的前一个交换
swap(arr, --more, cur);
}else {
cur++;
}
}
//返回等于num数据的位置
return new int[] { less + 1, more - 1 };
}

private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}

快速排序跟荷兰国旗算法的思想差不多,不同的是荷兰国旗算法是给了一个num值用来比较,快速排序中,我们自己从数组中随便找一个数来作为比较值。数组分成两部分之后,这两部分按照同样的方式递归在分自己,最后完成排序

解决思路

  • 首先选一个参照点,一般选择数组的最后一个值当做参照点
  • 之后的交换思路就跟荷兰国旗思路一样了
  • 需要注意,如果需要排序的数据本来就是一个有序的数组,那么如果一直选择最后的点作为参考点,那么每次分区得到的两个区间是不相等的,每次都要进行n次分区操作,每次分区大约扫描n/2个元素,时间复杂度会退化为O(N²)
  • 解决方式,每次不在取最后一个,从数组中随机取一个数,然后跟最后一个元素交换,在按照前的方式循环。改成随机事件,就从概率上降低了时间复杂度

代码如下

代码中直接使用left这个变量代替cur,可以省一个变量

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
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr,int left,int right){
if(left<right){
//在经典快排中,一般取最后一个位置的点作为参考点,但是加入数据本来就是有序的时候,快排的时间复杂度会退化到O(N²)
//所以,可以在数组中随机找一个数来作为参考点,使用这种方法来降低时间复杂度
//在left到right的范围中随机取一个数,把它放到最后一个位置
swap(arr,left + (int)(Math.random()*(right-left+1)),right);
//选一个参考点,把数组分区,比如选数组的最后一个点
//把比它小的放到它的左边,比它大的放到它右边,相等的不动
//返回的p数组中包含两个元素,这两个元素就是跟参考点相等的数的左边界和右边界
int []p = partition(arr,left,right);
//递归分区
quickSort(arr,left,p[0]-1);
quickSort(arr,p[1]+1,right);
}
}

private static int[] partition(int[] arr, int left, int right) {
//左边指针 最开始在数组最左边的前一个位置 也就是 -1 位置
int less = left-1;
//右边指针,最开始在数组最右边的位置(所以最后位置的那个数不参与循环)
int more = right;
// left变量 作为当前指针的位置
while (left<more){
//以数组最右边的数值为参考点
//如果当前指针的位置的数小于参考点的数
if(arr[left]<arr[right]){
//左边指针的下一个位置的数和当前指针位置的数交换,然后当前位置加一
//比如第一次的时候其实是数组第一个元素和自己交换
swap(arr,++less,left++);
}
//如果当前指针的位置的数大于参考点的数
else if(arr[left]>arr[right]){
//当前指针位置的元素和数组最后一个元素交换
swap(arr,left,--more);
}else {
//如果相等,当前位置的指针加一
left++;
}
}
//因为数组最后位置的数是最为参考点来的,没有参与循环,最后需要把它交换到正确的位置
swap(arr,more,right);
return new int[]{less+1,more};
}

public static void swap(int[] arr,int left,int right){
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
# 进阶

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×