1.快速排序
思想
快速排序是属于交换排序的基本思想。选择一个基准值val,把比val小的放在前面,比val大的放在后面,最后把val放在两个区域中间,val就到了最终的位置。
很明显快排是一个原地排序,也是一个不稳定排序。
空间复杂度:1.可以是为新数组开辟额外空间O(n) 2.当然也可以在原数组内交换得来O(1)
时间复杂度:O(nlogn)
代码实现
1.把数组第一个元素作为val,先用变量val存下来,设置两个指针,i用来标记遍历的元素,j-1用来标记<val区间的最后一个元素
2.i= start+1;j= start;遇到array[i]小于val的情况,就把array[i]和array[j]交换,小于区域扩张,j++;i++;遇到大于val的情况,就继续判断下一个i,i++;直到i>=j,把第一个元素和第j个元素交换,返回j的下标,为下一次递归边界做准备。
3.运用递归,接着把[start,key-1]和[key+1,end]进行第2步操作。
4.重复2.3.步,直到start>=end,退出递归。
start(j)--i----------------------end
start-----j---------i----------end
public static int[] qiuckSort(int[] array){
if(array.length <=1) return array;
qiuck(array,0,array.length-1);
return array;
}
private static void qiuck(int[] array ,int start, int end){
if(start >=end) return;
//选取key值
int key = selectionKey(array, start, end);
//key左边快排
qiuck(array,start,key-1);
//key右边快排
qiuck(array,key+1,end);
}
//l…<……>…r
//v……j……i
private static int selectionKey(int[] array, int start, int end){
//先设置为第一个元素
int key = array[start];
//[start,j]是<key的区域
int j = start;
//[j+1,i]是>key的区域
int i = start+1;
while(i <= end){
//大于key不动 小于key跟array[j]交换
if(array[i] <key){
swap(array,j+1,i);
j++;
}
i++;
}
//当遍历完之后把key放在j处 也就是最终位置处
swap(array,start,j);
return j;
}
存在优化
1.当数组中元素基本上有序,每次取得第一个元素都是最大或最小值,会导致元素左右个数分布不不均匀,当完全有序时近似于一个O(n^2)的排序。
解决:随机选取val值,在与第一个元素交换,让递归顺利进行。
2.当数组中存在大量重复元素,无论是小于等于交换,还是小于交换,都会导致一边区域数据远大于另一半,当重复量很大时近似于一个O(n^2)的排序。
解决:运用双路快排,把等于val的大量元素均匀的分在两个区域里。
2.双路快排
思想
把等于val的元素均匀分布在小于、大于区域内。
代码实现
1.避免数组近乎有序,先随机取出一个val,和第一个元素交换。
2.定义两个指针,i从头开始指向小于val的区域后一个元素,j从尾开始指向大于val的第一个元素。
3.当i所指向的值小于等于val,i++,否则暂停。当j所指向的值大于等于val,j--,否则暂停。当i和j都暂停时,交换i和j所指位置的元素。直到i>j结束,让start赋给j所指向的位置,返回j。
4.重复2.3直到start>end,排序完成。
public static int[] qiuckSort2(int[] array){
if (array.length <=1) return array;
qiuck2(array,0,array.length-1);
return array;
}
private static void qiuck2(int[] array ,int start, int end){
if(start >end) return;
int key = selectionKey2(array,start,end);
qiuck2(array,start,key-1);
qiuck2(array,key+1,end);
}
private static int selectionKey2(int[] array, int start, int end){
int random = (int)(Math.random()*(end-start+1)+start);
swap(array,random,start);
//找到一个随机key
int value = array[start];
//从头开始往后[start+1,i-1]
int i = start+1;
//从尾开始往前[j+1,end]
int j = end;
while (true){
//相当于把等于key的值均分到两边
while (i<=end && array [i] <value ) i++;
while (j>=start+1 &&array [j] >value ) j--;
//交换后两个指针都移动一步
if(i>j) break;
swap(array,i,j);
i++;j--;
}
swap(array,start,j);
return j;
}
存在优化
1.当数组重复元素过多时,每次比较==val的元素会浪费时间,虽浪费的时间不足一提,但是还是可以优化的。
解决:三路快排,把等于value的元素放在另一个区间内,不参与下次的排序。
3.三路快排
思想
在二路排序的基础上,把等于value的元素放在另一个区间内,不参与下次的排序。
代码实现
1.避免数组近乎有序,先随机取出一个val,和第一个元素交换。
2.定义三个指针,lt从头开始指向小于val的区域后一个元素lt = start-1,i指向目前比较的元素i= start,gt从尾开始指向大于val的第一个元素gt = end+1。保证一开始都是空集合。
3.当i所指向的值小于等于val,swap(i,lt+1),lt++。当i所指向的值大于等于val,swap(i,gt-1)gt--,否则i++。直到i>=gt排序完成。将start和lt交换。
4.[start,lt-1]和[gt,end]重复2.3直到start>end,排序完成。
start----lt----i----gt-----end
小于 [start,lt-1]
等于[lt,gt-1]
大于[gt,end]
public static void qiuckSort3(int[] array){
if(array.length <=1)return;
int start = 0;int end = array.length-1;
qiuck3(array,start,end);
}
private static void qiuck3(int[] array ,int start, int end){
if(start >end) return;
if(end - start <=15){
//如果数据量少就使用直接插入
insert(array,start,end);
return;
}
int random = (int)(Math.random()*(end-start+1)+start);
swap(array,random,start);
//找到一个随机key
int value = array[start];
int lt = start; int gt = end+1; int i = start+1;
for(;i<gt;i++){
if(array[i]<value){
//放到小于的区域
swap(array,lt+1,i);
lt++;i++;
}else if(array[i]>value){
//放到大于区域
swap(array,gt-1,i);
gt--;
}
}
swap(array,lt,start);
//直接跳过相等元素的比较
qiuck3(array,start,lt-1);
qiuck3(array,gt,end);
}
注:在此省略了直接插入和swap交换函数。
这样一步一步优化下来,快排适用了任何数据模式,具有了稳定的时间复杂度。