当前位置: 首页 > news >正文

【挠头写算法系列】质疑分治,理解分治,到分治真香

前言

断更了好久的算法,原因是之前进度太慢,要是加上写博客更慢了,现在今天就继续更新后续的一些算法。

首先今天要讲的就是”分治“,什么是分治呢,这里就不把一长串的概念给大家复制出来了,我直接用通俗,直观的方式告诉大家,常见的分治就是我们之前听说过的“快速排序”,与”归并排序“。如果还是不太理解,没事,下面的文章我会更加详细的讲解什么是分治。但在这之前我们就需要先回顾快速排序,与归并排序。

快速排序

之前我在数据结构专栏讲过快速排序,而且还讲了许多优化方法,现在我们只讲朴素的快速排序,来理解分治

在快速排序中,我们就是不断重复一个操作,就是先选定一个区间中的任意一个元素key,然后将这段区间变成

左边小于key,右边大于key,这样就是一次操作,然后我们继续对这左右两边进行再次这样的操作,直到这自己选定的区间只有一个元素,这样我们就将一个数组排序完成了。

import java.util.Random;



class Solution {
    public int[] sortArray(int[] nums) {
        func(nums,0,nums.length-1);
        return nums;
    }

    public void func(int[] nums,int l,int r){
        if(l>=r) return;
        int n=nums[new Random().nextInt((r-l+1))+l];
        int left=l-1,right=r+1,i=l;
        while(i<right){
            if(nums[i]<n) swap(nums,i++,++left);
            else if(nums[i]==n) i++;
            else swap(nums,i,--right);
        }
        func(nums,l,left);
        func(nums,right,r);

    }

    public void swap(int[] nums,int i,int j){
        int t=nums[i];
        nums[i]=nums[j];
        nums[j]=t;

    }
}

归并排序

与快速排序既相似也可以说相反,因为归并排序是不断将一段区间进行对半分,直到这段区间长度为1,我们就可以返回去合并两段区间进行排序(此时合并的是两段已经有序的区间),是一种递归过程把,可以类似这样看:快速排序更像是一种前序遍历,在对半分之前,已经将这段区间变成左右两边性质不同的,即(大于key和小于key)。而我们的后续遍历就是无脑的将区间进行对半分,直至不能再分,才开始回退到前一次分割之前进行排序,直至回退到开始分割的时候,数组也就排序好了。

class Solution {
    public int[] sortArray(int[] nums) {
        func(nums,0,nums.length-1);
        return nums;
    }

    public void func(int[] nums,int l,int r){
        if(l>=r) return;
        int mid=(l+r)/2;
        func(nums,l,mid);
        func(nums,mid+1,r);
        func1(nums,l,r,mid);

       
    }

    public void func1(int[] nums,int l,int r,int mid){
        int i=l,j=mid+1,k=0;
        int[] arr= new int[r-l+1];
        while(i<=mid&&j<=r){
            if(nums[i]<=nums[j]) arr[k++]=nums[i++];
            else arr[k++]=nums[j++];
        }

        while(i<=mid){
            arr[k++]=nums[i++];
        }
        
        while(j<=r){
            arr[k++]=nums[j++];
        }

        for(i=0;i<r-l+1;i++){
            nums[l+i]=arr[i];
        }

       

    }

    public void swap(int[] nums,int i,int j){
        int t=nums[i];
        nums[i]=nums[j];
        nums[j]=t;

    }
}

分治真正的运用

难道分治就是仅仅只是用来排序的吗,当然不是。接下来我们就通过几个题目,真正了解分治的真正奥妙。

第k大

如上题目,很简单的一题,相信很多人都想到运用堆去解决这个问题,但其实这题,也可以运用我们的快速排序去解决,而且时间复杂度更优,也就是题目中所说的O(n)的时间复杂度。

首先我们要直到在快速排序中,其实我们已经对一段区间的元素进行大致的划分,而且题目中不是要求我们排序,而是找到第k个最大元素,我们将这段区间划分成 <key ==key >key 这三段区间,那么我们只需先看看这三段区间中哪个区间可能包含有那个第k大

我们将这三段区间的数量个数进行统计为 a b c

如果k<c,那么说明第k大就是在 >key区间找即可,就无需在理会剩下两个区间,

如果不是上面那种情况我们并且 b+c>k, 这就说明我们要找到的第k大就是在b这段区间内,因为跳过了上面那种情况说明不在区间c中,那么只能在区间b中,所以他就是key

如果上面两种情况都不在,那肯定是在区间a中,那么我们只需要在区间a中找第k-(b+c)大即可。

class Solution {
    public int findKthLargest(int[] nums, int k) {
        return pation(nums,0,nums.length-1,k);
    }

    public int pation(int[] nums,int l,int r,int k){
        if(l>=r) return nums[l];
        int left=l-1,right=r+1,i=l;
        int key=nums[(r+l)/2];
        while(i<right){
            if(nums[i]<key) swap(nums,i++,++left);
            else if(nums[i]==key) i++;
            else swap(nums,i,--right);
        }

        if((r-right+1)>=k) return pation(nums,right,r,k);
        else if(k<=(r-left)) return key;
        else return pation(nums,l,left,k-r+left);

    }

     public void swap(int[] nums,int i,int j){
        int t=nums[i];
        nums[i]=nums[j];
        nums[j]=t;

    }
}

逆序对

在归并排序合中向上返回并两个区间时,这两个区间就已经是拍好序得了,假设两个区间都是升序

那么在合并过程中,如果我们能找到右边区间的某个元素right小于左边某个元素left,那么就可以确定,

在left-mid的元素也都比right大,这不就刚好算出有mid-right+1,逆序对了吗,这就时归并排序的奥妙之处,能在合并排序过程中一边利用排序好的性质一边对逆序对进行运算。

class Solution {
    public int reversePairs(int[] record) {
       
        return  mer(record,0,record.length-1);
    }

    public int mer(int[] nums,int l,int r){
        if(l>=r) return 0;
        int ret=0;
        int mid=(l+r)/2;
        ret+=mer(nums,l,mid);
        ret+=mer(nums,mid+1,r);
        int[] tmp=new int[r-l+1];
        int cur1=l,cur2=mid+1,i=0;
        while(cur1<=mid&&cur2<=r){
            if(nums[cur1]<=nums[cur2]) tmp[i++]=nums[cur1++];
            else{
                tmp[i++]=nums[cur2++];
                ret+=mid-cur1+1;
               
            }
        }

        while(cur1<=mid) tmp[i++]=nums[cur1++];
        while(cur2<=r) tmp[i++]=nums[cur2++];

        for(int j=0;j<(r-l+1);j++) nums[l+j]=tmp[j];
        return ret;

    }
}

相关文章:

  • STL之迭代器(iterator)
  • case客户续保预测中用到的特征工程、回归分析和决策树分析的总结
  • 监控相关信息 - 留档备查
  • 计算机体系结构之指令体系结构
  • Ubuntu虚拟机Linux系统入门
  • 从能量守恒的角度理解自然现象与社会现象
  • 【C语言】结构体 (深入)
  • python | tracemalloc模块,跟踪内存分配情况
  • 【时时三省】(C语言基础)选择结构程序综合举例2
  • 浅淡红黑树以及其在Java中的实际应用
  • 【ACM MM会议-2024工业异常检测】FiLo++:融合细粒度描述和形变定位的零样本/少样本异常检测
  • IO多路复用沉浸式体验
  • OpenAI Gym 提供了丰富的强化学习测试环境
  • 并发阻塞队列原理分析
  • 用户自定义函数(UDF)开发与应用(二)
  • 快速幂运算
  • 阅读论文 smart pretrain,搭配MAE一起食用
  • Elasticsearch 性能优化:从原理到实践的全面指南
  • Elasticsearch入门指南(三) 之 高级篇
  • 2025蓝桥杯JavaB组真题解析
  • 欧盟宣布解除对叙利亚的经济制裁
  • 当文徵明“相遇”莫奈:苏博将展“从拙政园到莫奈花园”
  • 俄乌上周在土耳其直接谈判,外交部回应
  • 武汉警方通报一起故意伤害案件:1人死亡,嫌疑人已被抓获
  • 常州新型碳材料集群产值近二千亿,请看《浪尖周报》第24期
  • 纽约市长称墨西哥海军帆船撞桥事故已致2人死亡