分治——快速排序、归并排序

目录

快速排序

单向扫描分区的方法实现partition:

双向扫描分区的方法实现partition:


摘要

  1. 分治法, 问题“逐步生成”的思想----将大问题分解,分成几个与他结构相同的小问题,不断分解,直至问题自然解决或者提高算法效率;
  2. 分治法的三步骤:分解,解决,合并
  3. 归并排序和快速排序很类似,都涉及到了分治的思想。但归并分解容易合并难,注重合并;快排分解困难,不用合并(快排在原数组上排列,而归并需建立辅助helper数组)
  4. 快排的“解决”部分, 在每次分解的时候就发别完成了排序, 原理类似左右大小两辆车相向而来,之间的距离就是未排好的数据,定义两个left和right指针不断运动,left>right时停止.

快速排序

1. 原理: 类似撞车,左边的“小车”和右边的“大车”相向而来,之间的距离就是未排好的数值,大于参考主元pivot(参考数)的就丢到大车这边, 小于的就丢到小车这边, 辆车开过了,交叉的时候就代表排序结束, 交换跑到小的这边的arr[right], 并把right当中间数下标传出去。

2. 解决步骤: 这里用问题“逐步生成”的思想,功能分作了两部分、两个方法:

        * 1. partition: 分割成两栏, 左小右大, 返回一个下标给quick_sort方法做中位数. 分割成一边大一边小、再返回主元pivot(中间数)下标的;

        * 2. quick_sort: 拆分, 递归向下拆分数组至只剩两个, 递归后向上回溯合并,变得有序 (每次都分割时都调整为左小右大,当向下细分到只有两个时, 全部就排好了 );

单向扫描分区的方法实现partition:

  单项扫描(一边主动走一边被动移)的思路:
索引0为主元(参考数),定义一个未扫描数据的最左边界指针left、最右边界值指针right,只有最左边界指针在扫描、在动;
遇到大于主元的值全都丢到右边,left指针和right指针此时指向的值交换,left不动继续比较,右边界指针right前进一位。
    public static void quick_sort(int[] arr, int prior, int row){
        if(prior<row) { //结束条件:prior==row,自然结束
            int mid = partition01(arr, prior, row);
            quick_sort(arr, prior, mid - 1);
            quick_sort(arr, mid + 1, row); //这里的递归不用出栈(回溯)
        }
    }


    private static int partition01(int[] arr, int prior, int row) {
        int pivot=arr[prior]; //定义一个参考的主元
        int left=prior+1; //定义一个未扫描数据的最左边界指针
        int right=row; //最右边界指针
        while(left<=right) { // 当二指针重合时,其实单项扫描工作已经完成了,之后right交错指向了一个小于主元pivot的值
            if (arr[left] <= pivot)
                left++;
            else {
                util.util.swap(arr,left,right); //注意,这里的left与right是指针,交换的是指向的值
                right--;
            }
        }
        util.util.swap(arr,prior,right); //right与left交错指向了一个小于pivot值,可以随意与主元pivot交换
        return right;//right此时就成了中位数
    }

双向扫描分区的方法实现partition:

双向扫描(两边都在主动走)的思路是:
头尾指针往中间扫描,从左找到大于主元的元素,从右找到小于等于主元的元素二者交换,
继续扫描,直到左侧无大元素,右侧无小元素。 边界值的问题考虑较多,所以还是使用单项扫描比较容易理解。
public static int partition02(int[] arr, int prior, int row){
        int pivot=arr[prior]; //定义一个参考的主元
        int left=prior+1; 
        int right=row; 
        while(left<=right) { // 指针交错,停止迭代
            //得考虑极端边界情况,如果主元pivot恰好最大/最小,边界值指针会冲出去越界
            while (left<=right && arr[left]<=pivot) left++;
            while (left<=right && arr[right]>=pivot) right--;

            if(left<right) //如果过界了就不能交换了
                util.util.swap(arr,left,right); 
        }
        util.util.swap(arr,right,prior); 
        return right; //中位数
    }

归并排序

原理步骤:

* 1. merge: 合并左右有序数组, 创建辅助盘,不断比较左右两边最小的值,决定谁先入栈(因为这个方法需要左右分别都有序)
 * 2. merge_sort: 拆分数组, 再递归向下拆分两个子数组; 结束递推, 合并回溯回来的两个子数组

/**
 * 1. merge: 合并左右有序数组, 创建辅助盘,不断比较左右两边最小的值,决定谁先入栈(因为这个方法需要左右分别都有序)
 * 2. merge_sort: 拆分数组, 再递归向下拆分两个子数组; 结束递推,合并回溯回来的两个子数组
 */
public class MergeSort {
    public static void merge_sort(int[] arr,int left,int right){
        if (left<right){ //1. 退出条件特性解:最后只剩下了一个数不用排了
            //2. 划分左右栏
            int mid=(left+right)/2;
            merge_sort(arr,left,mid);
            merge_sort(arr,mid+1,right);
            //3. 融合数组
            merge(arr,left,mid,right);
        }
    }

    private static void merge(int[] arr, int low, int mid, int high) {
        //1. 深拷贝创建一个辅助数组,已排序好的左右两栏,比较左右最小值,较小的数据先覆盖原数组
        int[] helper= Arrays.copyOfRange(arr,0,arr.length); //区间左闭右开

        int current=low; //原数组栈顶指针
        int left=low; //左边最小的首指针
        int right=mid+1;
        //2. 比较左右栏的最小值谁先入栈
        while(left<=mid&&right<=high){
            if (helper[left]<=helper[right]) {
                arr[current] = helper[left];
                left++;
                current++;
            }else {
                arr[current] = helper[right];
                right++;
                current++;
            }
        }
        //3. 若某一边数值先入栈完时,右栏倒是不用处理了(辅助盘拷贝原数组,剩下的几个大数字的位置-->||刚好是一样的),左边就得单独入栈
        while(left<=mid){
            arr[current]=helper[left];
            left++;
            current++;
        }
    }

    public static void main(String[] args) {
        int[] arg=new int[8];
        for (int i = 0; i < arg.length; i++) {
            arg[i]=(int)(Math.random()*20);
        }
        merge_sort(arg,0,arg.length-1);
        System.out.println(Arrays.toString(arg));

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值