排序的等价命题
给出一个数组
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sorted | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
unsorted | 6 | 1 | 2 | 7 | 9 | 3 | 4 | 5 | 10 | 8 |
观察上面的两个数组的每一个元素,你会发现经过排序的(以有小到大的顺序为例)数组的每个元素array(i)
左边的所有元素均小于array(i)
,右边的所有元素均大于array(i)
。
这个命题反过来同样成立,如果一个数组的每个元素array(i)
左边的所有元素均小于array(i)
,右边的所有元素均大于array(i)
,那么它就是有序的。
假设某数组中的元素x
左边的所有元素均小于x
,右边的所有元素均大于x
,那么x
就被称为这个数组的关键值x,或者称为第k大的数,k
为x
在数组中的位置。
现在排序的问题就可以被转化为寻找一个数组的所有关键值x
的位置k
。
快速排序计算方法
首先随机从数组中选择了一个数x
,然后寻找它作为关键值x的位置k,一般为了实现简单都会选择第一个或最后一个元素。这个过程可以成为一趟快速排序,下面我演示一下一趟快速排序。
给定i,j,x
,i
为数组的起始位置,j
为数组的结束位置,x
为一个特殊值null
。
第一步,将array(i)
和x
互换
i=0,j=9,x=6,array(i)=null
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sorted | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
unsorted | null | 1 | 2 | 7 | 9 | 3 | 4 | 5 | 10 | 8 |
第二步,将j
向前移动,并比较array(j)
和x
的值,当发现小于x
的array(j)
时,将array(j)
和array(i)
互换
i=0,j=7,x=6,array(i)=5,array(j)=null
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sorted | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
unsorted | 5 | 1 | 2 | 7 | 9 | 3 | 4 | null | 10 | 8 |
第三步,将i
向后移动,并比较array(i)
和x
的值,当发现大于x
的array(i)
时,将array(j)
和array(i)
互换
i=3,j=7,x=6,array(i)=null,array(j)=7
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sorted | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
unsorted | 5 | 1 | 2 | null | 9 | 3 | 4 | 7 | 10 | 8 |
第四步,重复二到三步,直到i=j
i=3,j=6,x=6,array(i)=4,array(j)=null
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sorted | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
unsorted | 5 | 1 | 2 | 4 | 9 | 3 | null | 7 | 10 | 8 |
i=4,j=6,x=6,array(i)=null,array(j)=9
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sorted | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
unsorted | 5 | 1 | 2 | 4 | null | 3 | 9 | 7 | 10 | 8 |
i=4,j=5,x=6,array(i)=3,array(j)=null
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sorted | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
unsorted | 5 | 1 | 2 | 4 | 3 | null | 9 | 7 | 10 | 8 |
i=j=5,x=6,array(i)=array(j)=null
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sorted | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
unsorted | 5 | 1 | 2 | 4 | 3 | null | 9 | 7 | 10 | 8 |
第四步,将x
和array(i)
互换
i=j=5,x=null,array(i)=array(j)=6
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sorted | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
unsorted | 5 | 1 | 2 | 4 | 3 | 6 | 9 | 7 | 10 | 8 |
至此,我们完成了一趟快速排序,为array(1)
找到了它的位置k=5
,回顾一下整个过程,我们可以描述为“挖坑-填坑”,首先我们选择将array(1)
挖走并交给x
,然后开始寻找x
应处的位置(这个位置左边的元素都小于x
,右边的元素都大于x
),然后开始从数组两端扫描数组(注意要选择“坑”的反方向扫描)。当“坑”左边的元素都满足要求(即都小于x
)时,我们称其在左端,反之称其为在右端。“坑”在左端时,我们要从右端选择小于x
的数来“填坑”;“坑”在右端时,我们要从左端选择大于x
的数来“填坑”。当“坑”即处于左端也处于右端时,说明它左右均满足要求,也就是它到了关键值x的位置,现在将x
拿来填坑。
一趟快速排序还原一个关键值的位置,这个关键值将数组分为左右两个,对这个数组递归的进行还原关键值的位置过程就完成了整个快速排序。
Code
自己实现的快排代码:
public static void quickSort(int[] a, int low, int high) {
if (low == high) return;
int pivot = low, i = low + 1, j = high - 1;
boolean forward = false;
while (i <= j) {
if (forward) {
if (a[pivot] > a[i]) {
i++;
} else {
swap(a, pivot, i);
pivot = i;
forward = false;
}
} else {
if (a[pivot] <= a[j]) {
j--;
} else {
swap(a, pivot, j);
pivot = j;
forward = true;
}
}
}
quickSort(a, low, pivot);
quickSort(a, pivot + 1, high);
}
public static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}