一路快排
思想:
从数组中选取一个数作为基准值,按照从大到小排序小于基准值的放在基准值的左边,大于基准值的在基准值的右边
核心partition过程:
选取数组第一个元素作为基准值,小于区[left+1,j-1],大于等于区[j,i-1],让变量i从left+1位置开始遍历数组,j变量位置是小于区间的而最后一个数,若是i位置的元素是小于基准值的数,就让小于区间增大,即小于基准值的数与大于等于区的第一个数进行交换,小于区扩大,依次比较交换,直到遍历整个数组。最终返回基准值的位置,基准值左边为小于区间,右边为大于区间,递归即可完全排序
private static int partition1(int[] arr, int left, int right) {
int value = arr[left];
int i = left + 1;
int j = left;
//让i变量去遍历整个数组,j变量位置是小于区间的而最后一个数
while (i <= right){
//若i位置的元素是小于基准值的数 就让小于区间增大
if (arr[i] < value){
swap(arr,++j,i++);
}else {//否则i向后遍历寻找小于基准值的数,找到后交换位置
i++;
}
}
//直到i遍历完序列中所有数后,将小于区最后一个数与基准值进行交换
swap(arr,left,j);
return j;
}
private static void swap(int[] arr, int i, int j) {
if(i != j) {
arr[i] ^= arr[j];
arr[j] ^= arr[i];
arr[i] ^= arr[j];
}
}
存在问题:当数组中等于基准值的元素太多时,会将等于基准值的元素划分在大于区,这是两个区域的大小会严重失衡,会导致分区的次数大大增加,这时排序的时间复杂度就会退化,从O(nlogn)退化为O(n^2)
二路快排
优化:此时,我们优化为双路快排,双路快排的思想是,定义两个变量,一个从数组的左边开始进行遍历,一个从序列的右边开始进行遍历,把右边遍历时比基准值小的数与左边遍历比基准值大的数进行交换,依次比较交换,直到两个变量相等,这样的方法使得等于基准时的数尽可能平均的分摊在两个区域,解决失衡问题
核心partition过程:
private static int partition(int[] arr, int left, int right) {
int val = arr[left];
int i = left + 1;
int j = right;
while (true){
//随机选择一个数作为基准数
// int index = (int) (Math.random()*(right-left+1)+1);
// swap(arr,left,index);
while (i <= right && arr[i] < val) {
i++;
}
while (j >= left+1 && arr[j] > val){
j--;
}
if (i > j){
break;
}
swap(arr,i++,j--);
}
swap(arr,left,j);
return j;
}
存在问题:如果这是一个有序或接近有序的序列,如果我们选择最后一个或第一个元素作为基准元素,那每次得到的两个分区是不均等的,我们需要进行大约n次分区操作才能完成整个快排操作,这是快排的时间复杂度就会退化到O(n^2)
**解决:**引入随机快排,在选取基准值时,随机选取一个元素,将这个随机的元素与数组首元素进行交换,这是每次选取到最大最小值的概率就会无限小,此时解决有序导致的时间复杂度退化问题
三路快排
思想:将数组按照基准值分为三部分:大于区、小于区、等于区,分区的过程中等于基准值的元素已经到了最终位置,在下一次的递归排序时只需要进行大于和小于区的处理,直到将序列排序成功,对于等于值较多的序列,这种方法效率很高
核心partation过程:
让变量从数组开始进行遍历,当遇见比基准值小的元素,将小于基准值的元素与等于区第一个元素进行交换,(算法不稳定)小于区扩大,当遇见比基准值大的元素,将大于基准值的元素与等于区最后一个元素进行交换,大于区扩大,一次排序后返回等于区的数组范围
private static int[] partition(int[] arr, int left, int right) {
int less = left - 1;
int more = right + 1;
int l = left;
int index = (int) (Math.random()*(right-left+1)+left);
swap(arr,right,index);
int val = arr[right];
while (l < more){
if (arr[l] < val){
swap(arr,++less,l++);
}else if (arr[l] > val){
swap(arr,--more,l);//此时遍历数组的变量l不移动,因为不能确定交换过来的数的大小,所以要在原地再次比较一次
}else {
l++;
}
}
//返等于区
return new int[]{less+1,more-1};
}
分析:
稳定性:不稳定
时间复杂度:最好:O(nlogn) 最坏:O(n^2)
恐慌间复杂度:递归过程最好 O(logn) 最坏O(n)