为了把这玩意整明白写了不少注释,先贴上来吧,回头有时间慢慢解释
快速排序在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快
算法步骤
从数列中挑出一个元素,称为 "基准"(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
来自菜鸟教程
首先是前面的部分,主要就是生成随机数组和递归调用,分治法,以及一个swap
每次调用返回一个中间的数,这个数是在正确的位置的,接下来要把这个mid位置两边,既left-mid-1,mid+1-right分别执行一次排序方法
public class test
{
@Test
public void quickSort(){
int[] list = new int[10];
for(int i=0;i<10;i++){
list[i]=new Random().nextInt(100);
}
sort(list,0,list.length-1);
}
public void sort(int[] list,int low,int high){
if(left<right){
int mid =partition(list,low,high);
//System.out.println(Arrays.toString(list));
//因为顺序是先进行partition再递归调用,运行时会先排最大的,再排两半边小的,再排更小的
//按从大到小的顺序,直到每一段长度都为1
sort(list,low,mid-1);
sort(list,mid+1,high);
}
private static void swap(int[] list,int i,int j){
int temp = list[i];
list[i]= list[j];
list[j]= temp;
}
}
常规快速排序经常把第一个数作为基准,但是如果待排列数组是完全倒序之类的情况,时间复杂度会趋近于最坏情况
为了让时间复杂度更接近平均值,在开始时随机选择一个数与最前面的数交换
public static int partition(int[] list,int low,int high){
int r = low+(new Random().nextInt(high-low+1));
swap(list,low,r); //随机选择一个数与最前面交换,作为基准数值
int mid = low; //基准初始位置在第二个数
System.out.println(list[low]);
for(int i = low+1;i<=high;i++) {
if (list[i] < list[low]) {
mid++; //基准位置右移一位
swap(list, mid, i); //将比基准小的数与当前基准位置交换
}
System.out.println(Arrays.toString(list));
} //此时从下标low+1到下标mid全都是比下标low更小的了
swap(list,mid,low); //最后让基准值所在的low和基准位置mid交换
//这样mid之前的全都小于等于mid了
//同理也可以先把基准值放在high最后再换
System.out.println(Arrays.toString(list));
return mid;
}
双路快速排序:排除选定的基准数之后,从最左端和最右端向中间靠拢
这种写法会在外层while的每一轮,分别对左端和右端进行一次交换数据的尝试,但不需要单独的swap()方法,最初单独拿出来存放的基准值就是swap里的那个temp,每一轮循环结束后刚好会剩下一个位置给基准值放进去
反正我是绕晕了,但代码写起来很优雅很好记,至于面试官会不会被整晕,那就另说了
public static int partition(int[] list,int low,int high){
int temp = list[low]; //以第一个数为比较值
if(low>=high){
return low;
}
while(low<high){
while( low<high &&list[high]>=temp){ //从右边找第一个比temp小的值
high--;
}
list[low]=list[high]; //将这个比temp小的值放到最前面low的位置,
//low本来的值存在temp中,high多了一个
while(low<high && list[low]<=temp){ //从左边找第一个比temp大的值
low ++;
}
list[high] = list[low]; //将这个比temp大的值放到high那边去
//high本来的值已经存进旧的low里,这里可以直接覆盖
//这样low又多了一个,下一轮会被覆盖掉
//如果没找到,low=high,数据没有变化
//如果只有两个数,需要让low/high最后停在tmp的位置
//即发生交换的话停在交换的二者的后面
//所以必须先high发生交换,后面low++,下一轮时high不发生交换,high--,跳出循环了
//如果反过来,low发生交换然后high--,low不交换low++,就错了
}
list[low] = temp; //最后low与high相等,而且与某个之前的值重复
//这个多出的位置放最初的temp
return low;
}
另一种双路快速排序,循环体里简单粗暴两个while,两边索引总会停在两个需要换到另一边的数上或者两个数相等,所以直接一个swap完事,逻辑上好理解多了
只是最后需要判断一下mid的值该放在哪
private static int partition(int[] list,int low,int high){
//随机化懒得写了
int midNum =low;
low++;
while(true){
while (low<high && list[high]>=list[midNum]){
high--;
}
while(low<high && list[low]<=list[midNum]){
low++;
}
//直到low与high相等或者
//low停在比mid大的数,high停在比mid小的数上
swap(list,low,high); //交换这两个数
//如果low与high相等了,跳出循环,否则再找下一对
if(low >=high)break;
}
//跳出时low=high
//但是这个位置是比基准大还是小不确定
if(list[midNum]<list[low]) {
low--; //如果是比基准大,就跟low左边的数字交换
}
return low;
最后是连在一起的完整代码,测试的时候自己改n的值和 partition 123
快速排序
package CollectionTest;
import org.junit.Test;
import java.util.Arrays;
import java.util.Random;
public class test
{
@Test
public void quickSort(){
final static int n = 100;
int[] list = new int[n];
for(int i=0;i<n;i++){
list[i]=new Random().nextInt(100);
}
sort(list,0,list.length-1);
}
public void sort(int[] list,int left,int right){
if(left<right){
int mid =partition1(list,left,right);
System.out.println(Arrays.toString(list));
sort(list,left,mid-1);
sort(list,mid+1,right);
}
}
public int partition1(int[] list,int left,int right){
int r = low+(new Random().nextInt(high-low+1));
swap(list,low,r);
int mid = left;
int tmp = list[left];
int i =left+1;
for(;i<=right;i++){
if(list[i]<list[left]) {
mid++;
swap(list, i, mid);
}
}
swap(list,left,mid);
return mid;
}
public int partition2(int[] list,int left,int right){
int mid = left;
int midNum = list[left];
left++;
//System.out.println(" "+left+" "+right);
while(left<right){
while(left<right&&list[left]<=midNum){
left++;
}
while(left<right&&list[right]>=midNum){
right--;
}
swap(list,left,right);
}
if(list[left]>midNum) {
left--;
}
swap(list,left,mid);
return left;
}
public int partition3(int[] list,int left,int right){
int tmp = list[left];
while(left<right){
while(left<right&& list[right]>=tmp){
right--;
}
list[left]=list[right];
while(left<right&& list[left]<=tmp){
left++;
}
list[right]=list[left];
}
list[left]=tmp;
return left;
}
public void swap(int[] list,int i,int j){
int tmp = list[i];
list[i]=list[j];
list[j]=tmp;
}
}