快速排序
思想
快排采用了分治的思想,每次分治,将数组一分为二,选取数组中一个元素作为标准,将小于标准的数分为一个数组,将大于标准的数分为一个数组
,然后在对这两个新数组进行分治,直到每个子数组都有序,数组也就被排序好了。
算法过程
以递增排序为例
- 定义一个头指针指向头部,定义一个尾巴指针指向尾部
- 定义一个划分标准flag,这里直接以头指针指向的数字作为flag(注意头指针指向flag)
- 尾指针向左移动,直到找到一个数字小于flag
- 尾指针和头指针的数字交换(这时候尾巴指针指向flag)
- 头指针向右边移动,直到找到一个数字大于或等于flag
- 交换(这时候头指针指向flag)
- 重复以上步骤直到两指针相遇
- 将头指针或者尾巴指针作为小值数组的右边界和大值数组的左边界,开始分治
- 直到数组的分界线重合,结束分治
示例代码
import java.util.Arrays;
import java.util.Random;
public class QuickSort {
public static void sort(int[] nums) {
int len = nums.length;
if (len <= 1) {
return;
}
helper(0, len - 1, nums);
}
private static void helper(int left, int right, int[] nums) {
if (left >= right) { return; }
int i = left; //头指针
int j = right; //尾指针
int flag = nums[left]; //标准
//直接交换flag
while (i < j) {
while (nums[j] >= flag && i < j) { j--; } //左移到小于标准的值
if (i < j) { // 交换
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
while (nums[i] < flag && i < j) { i++; } // 右移动到大于等于标准的值
if (i < j) { //交换
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
helper(left, i - 1, nums); //分治
helper(i + 1, right, nums);
}
public static void main(String[] args) {
int[] test = new Random().ints(1,20).limit(20).toArray(); //生成一个随机测试集合
Arrays.stream(test).forEach(x-> System.out.print(x+" "));
System.out.println();
sort(test);
Arrays.stream(test).forEach(x-> System.out.print(x+" "));
}
}
算法分析
稳定性
快排是不稳定的,可能会出现相同值对换的情况,比如:2 2 2 1 1 1 2 2 2 ,第二个2会被交换到第三个2的后面
复杂度
在最好的情况下,即每次标准的几乎能吧数组刚好分成数量相同的两半的时候,时间复杂度是O(nlog(n))
在最坏的情况下,比如一个排序好的数组,每次将数组分成1数字和剩余的数字,这样就要递归n次,时间复杂度为O(n^2)
优化角度
划分标准优化:标准的选择对算法有不小的影响,上面的示例中默认以头指针作为标准,这样有可能会遇到最坏的情况,可以采用随机数或者三数取中法(头值,尾值,n/2的值三个数的中值)来选取标准来避免最坏的情况
其余优化:递归优化,分治小到一定的规模是采用插排,每次划分后将与标准值相等的数聚集在一起等等。