快速排序的原理是有序的数组(顺序)一定满足这样的充分必要条件,对所有的元素而言(亦即每一个元素都可以作为基准元素),每一个比自己大的元素都排列在自己的身后,每一个比自己小的元素都排列在自己的身前。
所以,快速排序的步骤是:
1. 选定一个基准元素(有很多种方法,如随机、第一个元素、中间的元素……,随你所愿);
2. 创建两个游标,一个用于从后向前检索比基准元素小的元素,一个用于从前向后检索比自己大的元素;
3. 如果检索到了这样的元素,则进行交换,交换的规则是:
假定小元素的位置j,大元素的位置为i,基准为k,基准元素为p,数组为a,那么步骤是:
1. 小的元素放置在基准位置,即a[k] = a[j], k = j;
2. 小的元素位置放置大的数据,即a[k] = a[i], k = i;
3. 新的基准位置放置基准数据,即a[k] = p;
4. 不断重复步骤2,直到两个游标相遇;
5. 在游标相遇的位置划分出2个新的排序区间,然后对每个区间进行1-4步,直到划分不出新的区间为止;
根据上面的步骤,我们可写出第一种写法(递归写法):
static int sort(int[] nums, int start, int end) {
// 选定基准元素
int pivot = nums[start];
int k = start;
while(end > start) {
while(end > start && nums[end] >= pivot) {
end --;
}
if(end > start) {
nums[k] = nums[end];
k = end;
}
// 排除基准元素(即第一个元素),需要使用<=判断
while(end > start && nums[start] <= pivot) {
start ++;
}
if(end > start) {
nums[k] = nums[start];
k = start;
}
nums[k] = pivot;
}
// 返回相遇位置
return k;
}
//
static void quickSort(int[] nums, int start, int end) {
int mid = sort(nums, start, end);
// 如果只有一个元素,没有继续的必要
if(mid - 1 > start) {
quickSort(nums, start, mid - 1);
}
// 如果只有一个元素,没有继续的必要
if(mid + 1 < end) {
quickSort(nums, mid + 1, end);
}
}
根据上面的代码,我们可以很容易写出第二种实现方法——循环实现方法:
static void quickSortArr(int[] nums) {
// 作为辅助数据结构
Stack<int[]> stack = new Stack<>();
// 放入两个索引的位置
stack.push(new int[] {0, nums.length - 1});
while(!stack.isEmpty()) {
int[] index = stack.pop();
int start = index[0];
int end = index[1];
// 第一种解决办法
// 可以避免<= 判断,见下面的第二种方法
int i = start + 1;
int j = end;
int k = start;
int pivot = nums[start];
while(i < j) {
while(i < j && nums[j] > pivot) {
j --;
}
if(i < j) {
nums[k] = nums[j];
k = j;
}
// 第二种解决办法
// 避免起始位置死循环的方法
// while(i < j && nums[i] <= pivot)
while(i < j && nums[i] < pivot) {
i ++;
}
if(i < j) {
nums[k] = nums[i];
k = i;
}
nums[k] = pivot;
}
if(k - 1 > start) {
stack.push(new int[] {start, k - 1});
}
if(k + 1 < end) {
stack.push(new int[] {k + 1, end});
}
}
}
结论
快速排序的难点在于对两个扫描索引的理解,尤其是相遇时就是基准元素的正确位置,理解了这个知识点,快速排序就易于反掌。