二、认识O(Nlog N)的排序(上):master公式、归并排序、快速排序

认识O(Nlog N)的排序

一、master公式求递归算法的时间复杂度

$$
T(N)=a*T(N/b)+O(N^d)\\

(1)log_b a>d =>时间复杂度为O(N^{log_b a})\
(2)log_b a=d =>时间复杂度为O(N^d*logN)\
(3)log_b a<d =>时间复杂度为O(N^d)\
$$

1、例题 :求arr[L...R]范围上的最大值

//代码:求arr[L...R]范围上的最大值
public class findMax {
    public static int process(int[] arr,int L,int R){
        if(L==R) return arr[L];   
        int mid = L+((R-L)>>1);    //求中点
        int leftMax = process(arr,L,mid);   //左边的最大值
        int rightMax = process(arr,mid+1,R);  //右边的最大值
        return Math.max(leftMax,rightMax);   //返回最大值
    }
    public static void main(String[] args) {
        int[] arr={4,6,2,3,8,9,3,6,7,8};
        int ans = process(arr, 0, arr.length - 1);
        System.out.println(ans);
    }
}

2、分析:图解

在这里插入图片描述

  • int mid = L+((R-L)>>1); 防止溢出写法,等同于(L+R)/2 --> L+(R-L)/2

3、对例题使用master公式

在这里插入图片描述

二、归并排序

1、归并排序的实质(分治法)

  • 归并排序的整体就是一个简单递归,左边排好序,右边排好序,最后整体有序
  • 将待排序的数组层层分割到不可再分,再排序,然后再层层组合,最后得到有序数组
  • 时间复杂度O(N*log N),额外空间复杂度O(N)

2、归并排序图解

在这里插入图片描述

3、归并排序经典代码

//代码:经典归并排序的代码
public class mergeSort {
    //排序过程
    public static void merge(int[] arr,int left,int mid,int right){
        int[] help = new int[right-left+1];  //创建一个辅助数组
        int i=0;
        int p1=left,p2=mid+1;  //双指针,一个指向左部分的最左边,一个指向右部分的最左边
        //数组双方都没有越界
        //将p1和p2指向的元素中较小的一个拷贝进help中
        while (p1<=mid&&p2<=right)    help[i++] = arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
        //p2已越界但p1没有越界
        while (p1<=mid)  help[i++] = arr[p1++];
        //p1已越界但p2没有越界
        while (p2<=right)   help[i++]=arr[p2++];
        //将help中的元素反拷贝进arr中
        /**
         * 说明:加入现在传进来的部分是arr[]上的(4...6)部分
         *      则help[]的长度为3,到这一步的时候help[]有序
         *      将help[]反拷贝进arr[]中,从小标为4开始,
         *      也就是从left[left]=help[0]
         *            left[left+1]=help[1]
         *            ...
         *      最后arr[4...6]上有序
         */
        for(i=0;i<help.length;i++)
            arr[left+i]=help[i];
    }

   //拆分过程
    public static void process(int[] arr,int left,int right){
        //拆到元素单位为1,不可以再拆也无需排序,回到上一层
        if(left==right)   return;
        int mid = left+((right-left)>>1);  //拿到中点
        process(arr, left, mid);  //拆出来的左边部分
        process(arr, mid+1, right);   //拆出来的右边部分
        //排序
        merge(arr,left,mid,right);
    }

    public static void main(String[] args) {
        int[] arr = {3,2,1,5,6,2};
        //数组长度为0或者为1,直接返回
        if(arr==null||arr.length < 2)   return;
        //数组长度大于等于2,进入排序
        process(arr,0,arr.length-1);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

三、归并排序的拓展

1、小和问题

1.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.2 过程分析
问题可以转变为:
    1:右边比1大的数有4-->1*4
    3:右边比3大的数有2-->3*2
    4:右边比4大的数有1-->4*1
    2:右边比2大的数有1-->2*1
    5:右边比5大的数有0个
    最后:1*4+3*2+4*1+2*1=16
1.3 代码实现
public class mergeSortExample {
    //排序过程
    public static int merge01(int[] arr,int left,int mid,int right){
        //创建一个辅助数组
        int[] help = new int[right-left+1];
        int i=0;
        int p1=left,p2=mid+1;  //双指针,一个指向左部分的最左边,一个指向右部分的最左边
        int res=0;
        //数组双方都没有越界
        //将p1和p2指向的元素中较小的一个拷贝进help中
        while (p1<=mid&&p2<=right){
            //求小和
            res+=arr[p1]<arr[p2]?(right-p2+1)*arr[p1]:0;
            help[i++] = arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
        }
        //p2已越界但p1没有越界
        while (p1<=mid)  help[i++] = arr[p1++];
        //p1已越界但p2没有越界
        while (p2<=right)   help[i++]=arr[p2++];
        for(i=0;i<help.length;i++)
            arr[left+i]=help[i];
        return res;
    }

   //拆分过程
    public static int process01(int[] arr,int left,int right){
        //拆到元素单位为1,不可以再拆也无需排序,回到上一层
        if(left==right)   return 0;
        int mid = left+((right-left)>>1);  //拿到中点
        //小和累加
        return process01(arr, left, mid)
                +process01(arr, mid+1, right)
                +merge01(arr,left,mid,right);
    }

    public static void main(String[] args) {
        int[] arr = {1,3,4,2,5};
        //数组长度为0或者为1,直接返回
        if(arr==null||arr.length < 2)   return;
        //数组长度大于等于2,进入排序
        int res = process01(arr, 0, arr.length - 1);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println(res);
    }
}

2、逆序对问题

2.1 问题描述

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

和求小和是同源问题,只不过现在是求右边有多少个数比当前数小

四、快速排序和荷兰国旗问题

1、快速排序问题引入—荷兰国旗问题

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

升级–>荷兰国旗问题:给定一个数组arr,和一个数num,请把小于num的数放在数组的 左边,等于num的数放在数组的中间,大于num的数放在数组的 右边。要求额外空间复杂度0(1),时间复杂度ON

2、问题分析

在这里插入图片描述
在这里插入图片描述

3、快速排序思想

荷兰国旗问题并不要求数组有序,只需要划分出三个区域。

快速排序可以看成在荷兰国旗问题的基础上进行递归。

荷兰国旗只需要一层,分出三个区域。那么在此基础上,对荷兰国旗分出的三个区域中的左右区域再进行分层,直到不能分层,这就成了快速排序。

4、快速排序代码实现

//代码:快速排序最终版
//时间复杂度O(N*log N)   空间复杂度O(log N)
//交换
    public static void swap(int[]arr,int pos,int r){
        int temp = arr[pos];
        arr[pos]=arr[r];
        arr[r]=temp;
    }
    //快速排序
    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);
        }
    }
    //处理arr[l...r]
    //默认以arr[r]做划分
    //返回等语区域的左右边界
    public static int[] partition(int[] arr,int l,int r){
        int less = l-1; //小于区域的右边界 
        int more = r;  //大于区域的左边界
        //l表示当前数的位置,没有撞上大于区域时则进入循环
        while(l<more){
            //当前位置小于划分值,应该在左边
            if(arr[l]<arr[r]){
                //当前位置与小于区域的下一个做交换,然后当前位置前进
                swap(arr,++less,l++);
            } else if (arr[l] > arr[r]) {
                //当前位置大于划分值,应该在右边
                //当前位置与大于区域的前一个做交换,当前位置原地不动
                swap(arr,--more,l);
            }else{
                //相等的话直接++
                l++;
            }
        }
        //将排在最后的划分值arr[r]换回中间
        swap(arr,more,r);
        //返回左右边界
        return new int[]{less+1,more};
    }
    public static void main(String[] args) {
        int[] arr = {2,5,3,7,9,1,3,4,5,8};
        QuickSort(arr,0,arr.length-1);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值