归并排序,快排变形应用


前言

O(NlogN)的排序


一、数组中点防溢出

在取一个数组的中点时,传统写法为:

  • mid=(min+max)/2; 这样会发生(mn+max)溢出。
  • min+(max-min)/2; 可以改成这样。
  • mn+((max-min)>>1);也可以这样。

二、master公式

1.master公式的使用

T(N)=a*T(N/b)+O(N^d)
其中T(N)表示母问题规模,a表示子问题调用次数,T(N/b)表示子问题规模,O(N^d)表示其它规模。
必须在子问题规模一样的情况下
时间复杂度

  • logb^a<d —>O(N ^ d)
  • logb^a>d —> O(N ^logb ^a)
  • logb^a==d —> O(N ^d *logN)

归并排序

1.流程

将数组沿中点分成两部分,分别将左右两部分先排序,然后申请辅助空间,依次比较 左右两边的数,根据规则将较大或较小数copy到辅助空间,将这一侧往后移一位和另一侧不移动的数比较,以此类推,最终一边越界将另一边剩下的直接copy进辅助空间,就得到了有序数组。

T(N)=2T(N/2)+O(N)
归并排序的时间复杂度为:O(NlogN)
额外空间复杂度为:O(N)
代码如下

public class mergeSort {
    public static void process(int []arr,int L,int R){ // 递归
        if (L==R){
            return;
        }
        int mid=L+((R-L)>>1);// 取中点
        // 两次递归
        process(arr,L,mid);
        process(arr,mid+1,R);
        merge(arr,L,mid,R);
    }
    public static void merge(int []arr,int L,int M,int R){// 比较
        int[] help=new int[R-L+1];// 辅助空间
        int i=0;
        int p1=L; // 左指针
        int p2=M+1; // 右指针
        // O(N)
        while (p1 <= M && p2 <= R){// 比较
           help[i++]= arr[p1]<=arr[p2]?arr[p1++]: arr[p2++];
        }
        // 最终
        while (p1<=M){
            help[i++]=arr[p1++];
        }
        while (p2<=R){
            help[i++]=arr[p2++];
        }
        // O(N)
        for (int j=0;j<help.length;j++){
            arr[L+j]=help[j];
        }
    }
}

2.归并排序拓展

(1)求小和问题

描述:在一个数组中,每一个数左边比当前的数大累加起来,叫做这个数组的小和。求一个数组的小和。
例如:
[1,3,4,2,5],1左边比1小的数,没有;3左边比3小的数,1;4左边比4小的数,1、3;2左边比2小的数,1;5左边比5小的数,1、3、4、2;所以小和为1+1+3+1+1+3+4+2=16。

思路:转换问题,将求左边比当前数小改为求右边比当前数大的个数,再与该数相乘,最后相加得到小和。
例如:
1右边比1大的数有4个,就是14;3右边比3大的有2个,就是32;4右边比4大的有1个,就是41;2右边比2大的有1个,就是21;5右边没有比5大的数;所以小和为14+32+41+21=16。

利用归并找到小和,在左右两边都排序后,只要左边比右边的小,就要进行小和的累加,当相同时,先复制右边的,这样能更快找完但个数的小和。

public class smallSumProblem {
    public static int process(int []arr, int L, int R){ // 递归
        if (L==R){
            return 0;
        }
        int mid=L+((R-L)>>1);
        return process(arr,L,mid) +process(arr,mid+1,R)+merge(arr,L,mid,R); //总小和
    }
    public static int merge(int []arr,int L,int M,int R){// 比较
        int[] help=new int[R-L+1];// 辅助空间
        int i=0;
        int p1=L; // 左指针
        int p2=M+1; // 右指针
        int res =0;
        while (p1 <= M && p2 <= R){// 比较 求小和
            res+=arr[p1]<arr[p2]?(R-p2+1)*arr[p1]:0; // 左边小于右边时,计算小和数
            help[i++]= arr[p1]<arr[p2]?arr[p1++]: arr[p2++];// 左右两边相等时先拷贝右边的
        }
        // 最终
        while (p1<=M){
            help[i++]=arr[p1++];
        }
        while (p2<=R){
            help[i++]=arr[p2++];
        }
        for (int j=0;j<help.length;j++){
            arr[L+j]=help[j];
        }
        return res;
    }
}

结果:
在这里插入图片描述

(2)逆序对问题

描述:在一个数组中,左边的数如果比右边大,则这两个数构成一个逆序对,请打印所有逆序对。


public class reversePairProblem {
    public static void main(String[] args) {
        int[] arr = {5, 3, 4, 2, 1};
        process(arr, 0, 4);
    }

    public static void process(int[] arr, int L, int R) { // 递归
        if (L == R) {
            return;
        }
        int mid = L + ((R - L) >> 1);
         process(arr, L, mid);
         process(arr, mid + 1, R);
         merge(arr,L,mid,R);
    }
    public static void merge(int[] arr, int L, int M, int R) {// 比较
        int p1 = L; // 左指针
        int p2 = M + 1; // 右指针
        while (p1 <= M && p2 <= R) {// 比较
            if (arr[p1]>arr[p2]){
                System.out.println("["+arr[p1]+","+arr[p2]+"]");
                p1++;
            }else {
                p2++;
            }

        }
    }
}

结果:
在这里插入图片描述

3.荷兰国旗问题

(1)荷兰国旗问题一

描述:
给定一个数组arr,和一个数num,请把小于等于num的数放在数组左边,大于num的数放在数组的右边,要求额外空间复杂度O(1),时间复杂度O(N)。

思路:
他并不要求有序,值啊要将小于等于num的数放左边,大于num的数放右边就可以。先规定一个小于等于num的区域area,area起始是arr第一个数的左边,让指针i指向arr第一个数,让arr[i]与num比较,如果比num小或等,就让i+1,让area往右边扩一位,将arr[i]包含其中;如果arr[i]大于num时,i+1,area不变,接着比较下一位,知道有一位比num小时,交换这一位和area下一位数的位置,再将area往下扩一位,以此类推。

public class dutchFlagQuestionOne {
    public static void main(String[] args) {
        int []arr={3,8,5,3,8,1,2,9,7};
        int num=5;
        hollandOne(arr,num);
        System.out.println(Arrays.toString(arr));
    }
    public static void hollandOne(int []arr,int num){
        if (arr==null||arr.length<2){
            return;
        }
        partition(arr,num,0, arr.length-1);
    }
    public static void partition(int []arr,int num,int L,int R){
        int less=L-1;
        int more=R+1;
        while (L<more){
            if (arr[L]<=num){
                swap(arr,++less,L++);
            }else {
                swap(arr,--more,L);
            }
        }
    }
    //交换方法
    public static void swap(int []arr,int L,int R){
        int temp=arr[L];
        arr[L]=arr[R];
        arr[R]=temp;
    }
}

结果:
在这里插入图片描述

(2)荷兰国旗问题二

描述:
给定一个数组arr,和一个数num,请把小于num的数放在数组左边,等于num的数放在数组中间,大于num的数放在数组的右边,要求额外空间复杂度O(1),时间复杂度O(N)。

思路:
和上一个差不多,只不过多了一个等于的区域

  • [i]<num,[i]和小于区下一位交换,小于区右扩一位,i++
  • [i]=num,i++
  • [i]>num,[i]和大于区前一位交换,大于区左扩一位,i不变
public class dutchFlagQuestionOne {
    public static void main(String[] args) {
        int []arr={3,8,5,3,8,1,2,9,7};
        int num=5;
        hollandOne(arr,num);
        System.out.println(Arrays.toString(arr));
    }
    public static void hollandOne(int []arr,int num){
        if (arr==null||arr.length<2){
            return;
        }
        partition(arr,num,0, arr.length-1);
    }
    public static void partition(int []arr,int num,int L,int R){
        int less=L-1;
        int more=R+1;
        while (L<more){
            if (arr[L]<=num){
                swap(arr,++less,L++);
            }else {
                swap(arr,--more,L);
            }
        }
    }
    //交换方法
    public static void swap(int []arr,int L,int R){
        int temp=arr[L];
        arr[L]=arr[R];
        arr[R]=temp;
    }
}

结果:
在这里插入图片描述

4.快排

(1)快排1(荷兰国旗问题一的递归)

将最后一个数作为划分数进行判断,先判断第一个数与划分数的大小关系,如果小于等于划分数,与小于区后一位交换,小于区域右扩一位;如果大于划分数,与大于区前一位数交换,大于区左扩一位,返回小于区与大于区的交界值,进行递归,最终必定能得到一个有序数组。
时间复杂度为:O(N^2)
空间复杂度为:O(logN)

public class quickRowOne {
    public static void main(String[] args) {
        int []arr={1,9,4,4,2,5};
        quickSort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void quickSort(int []arr){
        if (arr==null||arr.length<2){
            return;
        }
        quickSort(arr,0,arr.length-1);
    }
    public static void quickSort(int[]arr,int L,int R){
        if (L<R){
            int p=partition(arr,L,R);
            quickSort(arr,0,p-1);
            quickSort(arr,p,R);
        }
    }
    public static int partition(int []arr,int L,int R){
        int less=L-1; // 小于等于区右边界
        int more=R;// 大于区左边界
       while (L<more){
           if (arr[L]<=arr[R]){
               swap(arr,++less,L++);
           }else swap(arr,--more,L);
       }
        swap(arr,more,R);
       return more;
    }
    public static void swap(int []arr,int L,int R){
        int temp=arr[L];
        arr[L]=arr[R];
        arr[R]=temp;
    }
}

结果:
在这里插入图片描述

(2)快排2(荷兰国旗问题二的递归)

在一的基础上添加了=的区域,也就是说=区域的那部分在后面就不用递归排序了。
时间复杂度为:O(N^2)
空间复杂度为:O(logN)

public class quicksortTwo {
    public static void main(String[] args) {
        int []arr={1,9,4,4,2,5};
        quickSort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void quickSort(int []arr){
        if (arr==null||arr.length<2){
            return;
        }
        quickSort(arr,0,arr.length-1);
    }
    public static void quickSort(int[]arr,int L,int R){
        if (L<R){
            int p[]=partition(arr,L,R);
            quickSort(arr,0,p[0]-1);
            quickSort(arr,p[1]+1,R);
        }
    }
    public static int[] partition(int []arr,int L,int R){
        int less=L-1; // 小于等于区右边界
        int more=R;// 大于区左边界
        while (L<more){
            if (arr[L]<arr[R]){
                swap(arr,++less,L++);
            }else if (arr[L]>arr[R]){
                swap(arr,--more,L);
            }else L++;
        }
        swap(arr,more,R);
        return new int[]{less+1,more};
    }
    public static void swap(int []arr,int L,int R){
        int temp=arr[L];
        arr[L]=arr[R];
        arr[R]=temp;
    }
}

结果:
在这里插入图片描述

(3)快排3(随机选取数来划分)

选取划分数时,在列表中随机选取一个数,并人为的将其放到列表的最后一位。
时间复杂度为:O(N*logN)
空间复杂度为:O(logN)

public class quickRowThree {
    public static void main(String[] args) {
        int []arr={1,9,4,2,5};
        quickSort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void quickSort(int[]arr){
       if (arr==null||arr.length<2){
           return;
       }
       quickSort(arr,0,arr.length-1);
    }

    // 递归排序
    public static void quickSort(int[]arr,int L,int R){
        if (L<R){
            swap(arr,L+(int)(Math.random()*(R-L+1)),R); // 随机范围数,手动交换到最后一位
            int[]p=partition(arr,L,R);// 用来确定等于区域边界
            quickSort(arr,L,p[0]-1);// 小于区域
            quickSort(arr,p[1]+1,R);// 大于区域
        }
    }

    public static int[] partition(int[]arr, int L, int R){
        int less=L-1; // 小于取右边界
        int more=R;// 大于区左边界
        while (L<more){
            // [i]<num,[i]和小于区下一位交换,小于区右扩一位,i++
            // [i]=num,i++
            // [i]>num,[i]和大于区前一位交换,大于区左扩一位,i不变
            if (arr[L]<arr[R]){
                swap(arr,++less,L++);
            }else if (arr[L]>arr[R]){
                swap(arr,--more,L);
            }else {
                L++;
            }
        }
        swap(arr,more,R);
        return new int[]{less+1,more};
    }
    public static void swap(int[]arr,int L,int R){
        int temp=arr[L];
        arr[L]=arr[R];
        arr[R]=temp;
    }
}

结果:
在这里插入图片描述


总结

上文主要讲了master公式的使用,归并排序以及快排逻辑,以及递归解决问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天将降大任于我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值