左程云算法课—02课程笔记

文章详细介绍了递归行为的时间复杂度估算,通过master公式分析了不同情况下的时间复杂度。接着,讨论了归并排序的原理、时间复杂度以及其实现,包括小和问题和逆序对问题的解决方案。此外,还探讨了荷兰国旗问题的两种形式及解决策略,并对比了不改进和改进的快速排序算法,强调了随机化的重要性以保证平均时间复杂度为O(N*logN)。
摘要由CSDN通过智能技术生成

目录

一、剖析递归行为和递归行为时间复杂度的估算

master公式的使用(满足子问题等规模)

二、归并排序

归并排序的扩展

小和问题

逆序对问题

三、荷兰国旗问题

问题一

问题二

四、快速排序

不改进的快速排序

随机快速排序(改进的快速排序)


一、剖析递归行为和递归行为时间复杂度的估算

master公式的使用(满足子问题等规模)

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

        T(N/b)表示我调用子问题时规模是否等量

        a代表子问题是等规模下被调用了多少次

        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)

例子:求整个数组的最大值

代码:

public class A {
    public  static int getMax(int[] arr){
        return process(arr,0,arr.length-1);
    }
    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);
    }
}

上述过程由master公式得 T(N)=2*T(N/2)+O(1)->log(b,a)>d->复杂度为O(N^log(b,a))

二、归并排序

1)整体就是一个简单递归,左边排好序、右边排好序、让其整体有序

2)让其整体有序的过程里用了外排序方法

3)利用master公式来求解时间复杂度

4)归并排序的实质时间复杂度O(N*logN),额外空间复杂度O(N)

流程:l,r范围上排序,先求中点,先让左侧排好序,再让右侧排好序

例:数组为3,2,1,5,6,2 左侧排好序为1,2,3右侧排好序为2,5,6

左侧下标指向1,右侧下标指向2,左侧如果<=右侧先拷贝左侧的,相反则拷贝右侧的

准备一个辅助空间用来放拷贝的数字,然后将数组放回

代码为:

题目链接:归并排序

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int l=0;
        tmp.reserve(nums.size()+1);
        int r=nums.size()-1;
        possess(nums,l,r);
        return nums;
    }
    void possess(vector<int>&nums,int l,int r)
    {
        if(l>=r) return;
        int mid=(r+l)/2;
        possess(nums,l,mid);
        possess(nums,mid+1,r);
        merge(nums,l,mid,r);
    }
     vector<int>tmp;
    void merge(vector<int>&nums,int l,int mid,int r)
    {
        int p1=l;
        int p2=mid+1;
        int i=0;
       
        while(p1<=mid&&p2<=r)
        {
            if(nums[p1]<nums[p2]) tmp[i++]=nums[p1++];
            else tmp[i++]=nums[p2++];  
        }
        while(p1<=mid) tmp[i++]=nums[p1++];
        while(p2<=r) tmp[i++]=nums[p2++];
        int j = 0;
        for (i = l,j; i <= r; ++i, ++j) {
            nums[i] = tmp[j];
        }
    }
};

时间复杂度O(N*logN),额外空间复杂度O(N)

归并排序的扩展

小和问题和逆序对问题

小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。例子:[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*1的小和,以此类推,但要注意在左侧等于右侧时应该先将右侧值拷贝

代码为:

class Solution {
public:
    int reversePairs(vector<int>& nums) {
         if(!nums.size()) return 0;
        int l=0,r=nums.size()-1;
        tmp.reserve(nums.size()+1);
        return possess(nums,l,r);
    }
   int possess(vector<int>&nums,int l,int r)
    {
       if(r==l) return 0;
        int mid=l+((r-l)>>1);
        return possess(nums,l,mid)+possess(nums,mid+1,r)+merge(nums,l,mid,r);
    }
    vector<int>tmp;
    int merge(vector<int>&nums,int l,int mid,int r)
    {
        int p1=l;
        int p2=mid+1;
        int res=0,i=0;
        while(p1<=mid&&p2<=r)
        {
            if(nums[p1]<nums[p2]) tmp[i++]=nums[p1++],res+=(r-p2+1)*nums[p1];
            else tmp[i++]=nums[p2++];
        }
        while(p1<=mid)  tmp[i++]=nums[p1++];
        while(p2<=r)    tmp[i++]=nums[p2++];

        int j = 0;
        for (i = l,j; i <= r; ++i, ++j) {
            nums[i] = tmp[j];
        }
        return res;
    }
};

逆序对问题

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

题目链接:逆序对数量

代码为:

class Solution {
public:
    int reversePairs(vector<int>& nums) {
         if(!nums.size()) return 0;
        int l=0,r=nums.size()-1;
        tmp.reserve(nums.size()+1);
        return possess(nums,l,r);
    }
   int possess(vector<int>&nums,int l,int r)
    {
       if(r==l) return 0;
        int mid=l+((r-l)>>1);
        return possess(nums,l,mid)+possess(nums,mid+1,r)+merge(nums,l,mid,r);
    }
    vector<int>tmp;
    int merge(vector<int>&nums,int l,int mid,int r)
    {
        int p1=l;
        int p2=mid+1;
        int res=0,i=0;
        while(p1<=mid&&p2<=r)
        {
            if(nums[p1]>nums[p2]) tmp[i++]=nums[p1++],res+=(r-p2+1);
            else tmp[i++]=nums[p2++];
        }
        while(p1<=mid)  tmp[i++]=nums[p1++];
        while(p2<=r)    tmp[i++]=nums[p2++];

        int j = 0;
        for (i = l,j; i <= r; ++i, ++j) {
            nums[i] = tmp[j];
        }
        return res;
    }
};

三、荷兰国旗问题

问题一

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

 分析:设一个变量表示<=区域的右边界

            分两种情况1)当[i]<=num,把[i]和小于等于区域的下一个数做交换,然后小于等于区域向                                     右扩且i++

                               2)当[i]>num,则i++

问题二

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

分析:设两个变量,一个表示<区域的右边界,一个表示>区域的左边界

            分三种情况1)当[i]<num,把[i]和小于等于区域的下一个数做交换,然后小于等于区域向                                     右扩且i++

                               2)当[i]=num,则i++

                               3)当[i]>num,把[i]和大于区域的前一个做交换,>区域左扩且i不变(当前交换过去的数还没有验,所以i不变)

题目链接:荷兰国旗

代码为:

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int l=-1,r=nums.size();
        int ans=1;
        int i=0;
        while(i<r)
        {
            if(nums[i]<ans) 
            {
                int tmp=nums[i];
                nums[i]=nums[l+1];
                nums[l+1]=tmp;
                i++,l++;
            }
            else if(nums[i]==ans) i++;
            else 
            {
                int tmp=nums[i];
                nums[i]=nums[r-1];
                nums[r-1]=tmp;
                r--;
            }
        }
        
    }
};

四、快速排序

不改进的快速排序

1)把数组范围中的最后一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:左侧<划分值、中间==划分值、右侧>划分值

2)对左侧范围和右侧范围,递归执行

分析

1)划分值越靠近两侧,复杂度越高;划分值越靠近中间,复杂度越低

2)可以轻而易举的举出最差的例子,所以不改进的快速排序时间复杂度为O(N^2)

代码为:

随机快速排序(改进的快速排序)

1)在数组范围中,等概率随机选一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:左侧<划分值、中间==划分值、右侧>划分值

2)对左侧范围和右侧范围,递归执行

3)时间复杂度为O(N*logN)

代码为:

public class Code06_QuickSort {
 
    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) {
            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 i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

结束,撒花!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值