快速排序 && 归并排序

1.快速排序
思想
1)在待排序区间中,找到一个基准值(常见的可以取区间的第一个元素或者最后一个元素)
2)以基准值为中心,把整个区间整理成三个部分,左侧部分的元素都小于等于基准值,右侧部分都大于等于基准值
3)再次针对左侧整理好的区间和右侧整理好的区间,进一步进行递归,重复刚才的整理过程
步骤
1)取最右侧元素为基准值
2)从左往右找到一个大于基准值的元素
3)从右往左找到一个小于基准值的元素
4)交换left和right的位置的元素
5)循环刚才的动作直至right和left重合
递归的整理左侧区间和右侧区间
注意
1)快速排序的效率和基准值取的好坏密切相关,基准值是中位数的时候效率最高,反之,基准值若是最大值或者最小值,快速排序的效率就比较低
2)如果取最左侧元素为基准值,则首先从右往左找,载从左往右找
3)如果数组正好反序,此时快排就变成慢排
此时快速排序效率很低,时间复杂度是O(N^2)

    private static void quickSort(int[] array){
    
    
        //辅助完成递归过程
        //为了代码简单,区间设定成前闭后闭
        quickSortHelper(array,0,array.length - 1);
        System.out.println(Arrays.toString(array));
    }

    private static void quickSortHelper(int[] array, int left, int right) {
    
    
        if(left >= right){
    
    
            //区间中有0个或者一个元素,此时不需要排序
            return;
        }
        //针对[left,right)区间进行整理
        //index返回值就是整理完毕后left和right的重合位置,知道了这个位置
        //才能进一步进行递归,
        int index = partition(array,left,right);
        quickSortHelper(array,left,index - 1);
        quickSortHelper(array,index + 1,right);
    }

    private static int partition(int[] array, int left, int right) {
    
    
        int i = left;
        int j = right;
        //取最右侧元素为基准值
        int base = array[right];
        while(i < j){
    
    
            //从左往右找到比基准值大的元素
            while(i < j && array[i] <= base){
    
    
                i++;
            }
            //当上面的循环结束时,i要么和j重合要么就指向一个大于base的值
            //从右往左找比基准值小的元素
            while(i < j && array[j] >= base){
    
    
                j--;
            }
            //当上面的循环结束时,i要么和j重合要么就指向一个小于base的值
            //交换i和j的值
            swap(array,i,j);
        }
        //当i和j重合的时候,最后一步,要把重合位置的元素和基准值交换
        swap(array,i,right);
        return i;

    }

    private static void swap(int[] array, int i, int j) {
    
    
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

快速排序的优化
1)优化基准值的取法,三个元素取中(最左侧元素,最右侧元素,取中间值作为基准值,把确认的基准值swap到数组末尾开始,为了后面的整理动作做铺垫)
2)当区间已经比较小的时候,再去递归其实效率就不高了,不再继续进行递归,而是直接进行插入排序
3)如果区间特别大,递归区间也会非常深,当递归深度达到一定程度时的时候,把当前区间的排序使用堆排序来进行优化
非递归实现

   private static void quickSortByLoop(int[] array){
    
    
        //借助栈,模拟递归过程
        //stack用来存放数组下标,通过下标来表示接下来要处理的区间是啥
        Stack<Integer> stack = new Stack<>();
        //初始情况下,先把右侧边界下标入栈,再把左侧下标入栈,左右下标
        //仍然构成前闭后闭区间
        stack.push(array.length - 1);
        stack.push(0);
        while(!stack.isEmpty()){
    
    
            //这个取元素的顺序要和push的顺序正好相反
            int left = stack.pop();
            int right = stack.pop();
            if(left >= right){
    
    
                //区间中只有一个或0个元素,不需要整理
                return;
            }
            //通过partition把区间整理成以基准值为中心,左侧元素
            //小于等于基准值,右侧元素大于等于基准值的形式
            int index = partition(array,left,right);
            //准备处理下个区间
            //[index + 1,right]基准值右侧区间
            stack.push(right);
            stack.push(index + 1);
            //[left,index - 1]基准值左侧区间
            stack.push(index - 1);
            stack.push(left);
        }


    }

2.归并排序
特点
可以适用于外部排序(数据存在磁盘上),也可以使用于链表排序(希尔,堆排序,快速排序 依赖随机访问能力,所以就不适合用链表排序)
思想
来源于一个经典问题(把两个有序链表/数组合并成一个)

    //[low,mid)有序区间
    //[mid,high)有序区间
    //把这两个有序区间合并成一个有序区间
    public static void merge(int[] array,int low,int mid,int high){
    
    
        int[] output = new int[high - low];
        int outputIndex = 0;//记录当前output数组中放入多少个元素了
        int cur1 = low;
        int cur2 = mid;
        while(cur1 < mid && cur2 < high){
    
    
            //这里写成小于等于才能保证稳定性
            if(array[cur1] <= array[cur2]){
    
    
                output[outputIndex] = array[cur1];
                outputIndex++;
                cur1++;
            }else{
    
    
                output[outputIndex] = array[cur2];
                outputIndex++;
                cur2++;
            }
        }
        //上面的循环结束的时候,肯定是cur1或者cur2有一个先到达末尾,另一个还剩下一些内容
        while(cur1 < mid){
    
    
            output[outputIndex] = array[cur1];
            outputIndex++;
            cur1++;
        }
        while(cur2 < high){
    
    
            output[outputIndex] = array[cur2];
            outputIndex++;
            cur2++;
        }
        //把output中的元素再搬回原来的数组
        for(int i = 0;i < high - low;i++){
    
    
            array[low + 1] = output[i];
        }
    }
    private static void mergeSort(int[] array){
    
    
        mergeSortHelper(array,0,array.length);
    }
    //[low,high)前闭后开区间,两者差值小于等于1,区间中就只有一个或者0个元素
    private static void mergeSortHelper(int[] array, int low, int high) {
    
    
        if(high - low <= 1){
    
    
            return;
        }
        int mid = (low + high) / 2;
        //这个方法执行完,就认为low,mid已经排序OK
        mergeSortHelper(array,low,mid);
        //这个方法执行完,就认为mid,high也已经排序OK
        mergeSortHelper(array,mid,high);
        //当把左右区间已经归并完了,说明左右去家已经是有序区间了
        //接下来就可以针对两个有序区间进行合并了
        merge(array,low,mid,high);

    }

非递归实现

    private static void mergeSortByLoop(int[] array){
    
    
        //引入一个gap变量进行分组
        //当gap为1的时候,[0] [1]进行合并,[2] [3]进行合并,[4] [5]进行合并,[6] [7]进行合并
        //当gap为2的时候,[0,1]进行合并,[2,3]进行合并,[4,5]进行合并和[6,7]进行合并
        //当gap为4的时候,[0,1,2,3]和[4,5,6,7]进行合并
        for(int gap = 1;gap < array.length;gap *= 2){
    
    
            //接下来进行具体合并
            //下面的循环执行一次,就完成了一次相邻两个组的合并
            for(int i = 0;i < array.length;i += 2*gap){
    
    
                //当前相邻组
                //[beg,mid)
                //[mid,end)
                //beg => i
                //mid => i + gap
                //end => i + 2 * gap
                int beg = i;
                int mid = i + gap;
                int end = i + 2 * gap;
                if(mid > array.length){
    
    
                    mid = array.length;
                }
                if(end > array.length){
    
    
                    end = array.length;
                }
                merge(array,beg,mid,end);
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/liyuuhuvnjjv/article/details/105815052