快速排序
众所周知,快速排序是一个非常常用并且效率很高的排序算法,也是面试中经常问到的算法。本文介绍了快速排序的详细过程,内容比较紧凑,同时也很直观,相信看过之后会有收获。
算法思想
在数组内部进行排序,每次确定一个数的位置。以递增次序为例,每次移动一个数,使得它前面的数都比他小,后面的数都比它大(为了方便理解,假设数组没有相等的元素),而前半部分和后半部分内部的次序是不确定的。该过程如图所示:
将x移动后要保证B部分都比x小,C部分都比x大,这样就确定了x最后的位置。要完成整个数组的排序只要将B部分和C部分做同样的操作。
一直进行下去,直到某一次B部分和C部分都只有一个元素为止,这样就完成了整个数组的排序。这是一个典型的分治思想,将整个数组的排序分解成部分排序的子问题,每次只需要找到一个元素的确定位置,再将其他的部分做同样的操作。
那么算法的重点实际上是如何确定x的位置,使得B部分都小于x,C部分都大于x。实现的方法如下所示。
每次将待排序数组的第一个元素设定为基准元素x。
1. 初始化两个指针i和j,i指向第二个元素,j指向最后一个元素。i遍历过的数最后都比x小,j遍历过的数最后都比x大
2. i向右移动直到找到一个大于x的数
3. j向左移动直到找到一个小于x的数
4. 交换i和j所指元素的位置(这样就保证了i遍历过的都比x小,j遍历过的都比x大)
重复2,3,4步,直到j<i,跳出循环。
循环结束之后将x元素和j指向的元素交换位置,这样就确定了x的位置。
复制代码
- 初始化指针。
- i向右移动找到一个大于x的数。
- j向左移动找到一个小于x的数。
- 交换i和j指向元素的位置。交换过后的状态就还是i经过的元素都比x小,j经过的元素都比x大。
- 重复2,3,4步骤,直到i<j,这是后必定会停下来,因为j一旦在i的左边,j指向的元素肯定小于x,符合j停止的条件,i同理。
- 交换j指向元素和x的位置,这样就保证了x左边的元素都比x小,x右边的元素都比j大。
至此,就确定了一个元素的位置。我们这里忽略掉了数组内有重复元素的情况,在编写代码时还有很多的边界情况需要考虑。
Java实现
这里的实现只放出了找到x位置并返回的代码,这也是快速排序最关键的部分,有了这部分代码,实现完整的排序就很简单了。这里考虑了众多边界情况,请仔细看注释。该部分建议大家可以背下来,这部分代码可以适用于很多编程题。
// 该方法用于找到x的位置并返回
// low,high参数表示要操作的区域(直接在待排序数组内部操作);nums参数就是整个待排序数组。
// 返回值是x的下标,方法执行之后[low,x)部分都比x小,(x,high]部分都比x大。
public static int partion(int low, int high, int[] nums) {
// 如果low不比high小,则该部分不需要排序
if (low >= high) return -1;
// if (low > high) return -1;
// if (low == high) return low;
int pivot = nums[low]; // 记录x的值,这里用pivot变量表示
int i = low; // 因为下面的循环体内是以++i开始的,所以这里i=low而不是i=low+1
int j = high + 1; // 同理,j=high+1而不是high
while (true) {
// i不超过上界的前提下一直往右移动,直到找到一个数大于或者等于x,这里表示等于的情况也要交换位置。
while (++i < high && nums[i] < pivot);
// 与i同理
while (--j > low && nums[j] > pivot);
/*
有两种情况会出现i==j,第一种是所有的元素都小于x,这样i会移动到最后,j一开始便跳出循环,这时已经可以确定x的位置就在最右边了。另一种情况是当i和j相遇在一个等于x的元素位置时,这时也确定了x的位置。所以这里设定当i>=j时就确定了x的位置应该在j处。这样的话实际上是保证了x左边的元素小于等于x,右边的大于等于x。
*/
if (i >= j) break;
// ij还没有相遇时交换ij所指元素的位置
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
// 确定了x的位置为j后交换元素位置
nums[low] = nums[j];
nums[j] = pivot;
// 将x的位置返回
return j;
}
复制代码
完整代码
public static int partion(int low, int high, int[] nums) {
if (low >= high) return -1;
int i = low;
int j = high + 1;
int pivot = nums[low];
while (true) {
while(++i < high && nums[i] < pivot);
while(--j > low && nums[j] > pivot);
if (j <= i) break;
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
nums[low] = nums[j];
nums[j] = pivot;
return j;
}
// 实际的排序方法
public static void sort(int low, int high, int[] nums) {
int index = partion(low, high, nums);
if (index == -1) return;
sort(low, index - 1, nums);
sort(index + 1, high, nums);
}