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交换函数。


这样一步一步优化下来,快排适用了任何数据模式,具有了稳定的时间复杂度。