二分判定+选插冒排序+归并快速堆希尔+计数排序

二分力扣题

一:搜索二维矩阵

74. 搜索二维矩阵

按照题意:直接利用二维数组转换成一维数组进行求解

在这里插入图片描述

方法一:普通等于的二分查找

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        this->m = matrix.size();
        this->n = matrix[0].size();
        int l=0;
        int r=m*n-1;  //(m-1)*n+(n-1)  r坐标:(m-1,n-1)
        int mid;
        while(l<=r){
            mid=(l+r)>>1;
            auto [i,j]=getIndex(matrix,mid);
            //三种分支
            if(matrix[i][j]<target) l=mid+1;
            else if(matrix[i][j]>target)  r=mid-1;
            else return true;//==
        }
        //没找到
        return false;
    }

private:
    int m, n;
    // 一维转换二维
    pair<int, int> getIndex(vector<vector<int>>& matrix, int index) {
        return {index / n, index % n};
    }
};

方法二:按照求最后一个等于

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        this->m = matrix.size();
        this->n = matrix[0].size();
        int l=0;
        int r=m*n-1;  //(m-1)*n+(n-1)  r坐标:(m-1,n-1)
        int mid;
        //最后一个等于(扩大l)
        while(l<=r){
            mid=(l+r)>>1;
            auto [i,j]=getIndex(matrix,mid);
            if(matrix[i][j]<=target){//最后一个<=
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
        //r要求>=0
        if(r<0) return false;
        auto [i,j]=getIndex(matrix,r);
        //下标已经合法了
        return matrix[i][j]==target?true:false;
    }

private:
    int m, n;
    // 一维转换二维
    pair<int, int> getIndex(vector<vector<int>>& matrix, int index) {
        return {index / n, index % n};
    }
};

也可以按照第一个等于,注意if条件是“>=“

//第一个等于(缩小r)
     while(l<=r){
         mid=(l+r)>>1;
         auto [i,j]=getIndex(matrix,mid);
         //合法就缩小r
         if(matrix[i][j]>=target){
             r=mid-1;
         }else{
             l=mid+1;
         }
     }
     //l要求<= ---(m*n-1)
     if(l>m*n-1) return false;
     auto [i,j]=getIndex(matrix,l);//注意是l
     //下标已经合法了
     return matrix[i][j]==target?true:false;

二:不重复元素找最小值

153. 寻找旋转排序数组中的最小值

不是只有单调序列才能二分查找,只要题目属于分段单调的,而且能够找到分界点就可以二分。

本题就是一个不含重复元素的单调序列,把它的后半部分移到了数组开头位置。

比如:[1,3,5,7,9]->[7,9 || 1,3,5]

序列具备:两段都是单调函数,而且前一段序列值一定大于后一半

在这里插入图片描述

想要找的最小值满足:

  • 它一定比最右端元素小
  • 而且是比最右端元素小的最小值

而且:比最右端元素大的值一定不是最小值

相当于:找第一个<=最右端元素的值

//6789
    //    1234
    //找到第一个<=最右端的数
    int findMin(vector<int>& nums) {
        int l=0;
        int r=nums.size()-1;
        while(l<r){
            int mid=(l+r)>>1;
            if(nums[mid]<=nums[r]){
                r=mid;//注意保留mid
            }else{
                l=mid+1;
            }
        }
        return nums[l];//注意返回的是下标对应的值
    }

也可以比较第一个值nums[0],但是要注意,如果[l,r]不存在转折,也就是满足递增提前判断

如果mid对应值>=nums[0],最低谷一定在mid右端,l=mid+1

如果mid对应值值<nums[0],最低谷可能是mid或mid左边,r=mid

在这里插入图片描述

int findMin(vector<int>& nums) {
        int l=0;
        int r=nums.size()-1;
        while(l<r){
            //如果[l,r]是一段递增
            if(nums[l]<=nums[r]){
                return nums[l];
            }
            int mid=(l+r)/2;
            //存在低谷
            if(nums[mid]>=nums[0]){
                l=mid+1;
            }else{
                r=mid;
            }
        }
        return nums[l];
    }

三:搜索旋转数组值

33. 搜索旋转排序数组

先确定mid再寻找值,确定mid与target无关,只跟两侧有关

在这里插入图片描述

本题的数组序列是递增数列的一部分移动到了前面

比如:1 3 5 7 9 -> 7 9 || 1 3 5 满足:两部分都递增,而且前一部分比后一部分大

解题时:

首先判断mid在哪一个递增分段上,有两种可能:

  1. 如果mid落在左分支,即:nums[mid]>=nums[l]

在这里插入图片描述

此时:只需要判断target在红色还是不在红色

nums[l]<=target<nums[mid]

  • 在红色,r=mid-1
  • 不在红色,l=mid+1
  1. 如果mid落在右分支,即:nums[mid]<nums[l]

在这里插入图片描述

同上面,判断target是否落在红色区间

nums[mid]<=target<nums[r]

  • 在红色,l=mid+1
  • 不在红色,r=mid-1

代码:

 int search(vector<int>& nums, int target) {
        int l = 0;
        int r = nums.size() - 1;
        while (l <= r) {
            int mid = (l + r) >> 1;
            // 返回结果
            if (nums[mid] == target) {
                return mid;
            }
            if (nums[mid] >= nums[l]) {
                // 判断taget所在位置
                if (target >= nums[l] && target < nums[mid]) {
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            } else {
                if (target > nums[mid] && target <= nums[r]) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
        }
        // 没找到
        return -1;
    }

还可以判断<=

while(l<=r){
         int  mid=(l+r)>>1;
         if(nums[mid]==target) return mid;
         else if(nums[mid]<=nums[r]){//mid
             if(nums[mid]<target && target<=nums[r]){
                 l=mid+1;
             }else{
                 r=mid-1;
             }
         }else{
              if(target>=nums[l]&&nums[mid]>target){
                  r=mid-1;
             }else{
                  l=mid+1;
             }
         }
     }

也就是判断mid位置就两种

  • 大于等于nums[l]
  • 小于等于nums[r]

都只判断一个就可以确定mid

四:选举人

911. 在线选举

思路:

  • 既然是统计大小,一定会用到查找表(set或map)

  • 题目给出的是某一时刻投给了哪个人,那么TopVotedCandidate()计算:某一个时刻哪个人票数最多

  • 然后q(t)计算是任意t时间点的获胜人数,只有某一时间点获胜人数的信息,所以二分查找:

    —最后一个<=t的时间点,然后直接返回对应获胜者即可

  1. map统计频次,mp[人]=票数,用winners存储当前时间点获胜的人
class TopVotedCandidate {
public:
    TopVotedCandidate(vector<int>& persons, vector<int>& times) {
        this->times=times;
        int maxWinner=0;//当前最大票数
        for(auto& val:persons){
            mp[val]++;//对应人票数+1
            if(mp[val]>=mp[maxWinner]){
                //题目要求相等取最近获胜者,需要更新
                maxWinner=val;
            }
            winners.push_back(maxWinner);
        }
    }
    int q(int t) {
        //查找最后一个<=t的时刻
        int l=0;
        int r=times.size()-1;
        while(l<r){
            int mid=(l+r+1)>>1;//别忘了+1
            if(times[mid]<=t){
                l=mid;//扩大l
            }else{
                r=mid-1;
            }
        }
        return winners[l];
    }
private:
    unordered_map<int,int> mp;//mp[选举人]=票数
    vector<int> winners;//当前t时刻的获胜者
    vector<int> times;//为q(t)传参
};

或者也可以定义最大值记录当前获胜者的最大票数和得到最大票数的优胜者

int maxVotes=0;//当前最大票数
     int top=0;//要存入的获胜者
     for(auto& val:persons){
         mp[val]++;//对应人票数+1
         if(mp[val]>=maxVotes){
             //题目要求相等取最近获胜者,需要更新
             maxVotes=mp[val];
             top=val;//当前人
         }
         winners.push_back(top);
     }

方法二:

直接计算:mp[当前时间点]=获胜者,只是需要开辟新数组来记录最大值

class TopVotedCandidate {
public:
    TopVotedCandidate(vector<int>& persons, vector<int>& times) {
        int maxVotes=0;
        votes=vector<int>(times.size());
        for(int i=0;i<times.size();i++){
            votes[persons[i]]++;//persons[i]这个人的票数+1
            //因为出现i-1
            if(i==0){
                maxVotes=votes[persons[i]];
                mp[times[i]]=persons[i];
                continue;
            }
            if(votes[persons[i]]>=maxVotes){
                 maxVotes=votes[persons[i]];
                 //当前时刻的的票最多人
                 mp[times[i]]=persons[i];
            }else{
                mp[times[i]]=mp[times[i-1]];
            }
        }
    }
    
    int q(int t) {
        while(!mp.count(t)){
            t--;
        }
        return mp[t];
    }
private:
    //mp[time]=persons;
    unordered_map<int,int> mp;
    vector<int> votes;
};

/**
 * Your TopVotedCandidate object will be instantiated and called as such:
 * TopVotedCandidate* obj = new TopVotedCandidate(persons, times);
 * int param_1 = obj->q(t);
 */

三分查找

问题:

已知函数 𝑓(𝑥) 在区间 [l,r] 上单峰且连续,求 𝑓(𝑥) 在 [l,r]上的极值

也就是说:三分查找适用于存在局部极大值或局部极小值的函数来找到极值点

在这里插入图片描述

​ 如图:在定义域[L,R]内部取两点lmidrmid,整个函数被这两个点分成了三块区域,通过这两个值的大小,来舍弃某一些区间来缩小查找范围

查找过程

在这里插入图片描述

有高峰的函数,只要满足:nums[lmid]<nums[rmid],那么极大值一定在lmid的右边,也就是让l=lmid+1;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

同理:如果nums[rmid]<nums[lmid],极大值一定在rmid左边,也就是:r=rmid-1

寻找峰值

162. 寻找峰值

为了能保证每次取舍一半区间,让[l,r]内部取的lmid和rmid取值为:

  • lmid=(l+r)>>1,也就是中点
  • rmid=lmid+偏移量,可以是lmid+1

当然lmid和rmid也可以是三等分点

 int findPeakElement(vector<int>& nums) {
        //这里题目一定会有解,l和r直接取两端即可
        int l=0;
        int r=nums.size()-1;
        while(l<r){
            //在[l,r]内部取lmid和rmid
            int lmid=(l+r)>>1;
            int rmid=lmid+1;
            //进行区间取舍
            if(nums[lmid]<=nums[rmid]){
                l=lmid+1;
            }else{
                r=rmid-1;
            }
        }
        return l;//r
    }

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C24732%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20240426014557032.png&pos_id=img-x1OB1Skc-1715539346227

这里的等号取不取,对本题而言没有影响

二分判定枚举

二分法还有一个重要应用是 枚举答案,尤其是值域上的二分枚举。

注意:要先确定存在二段性,才可以用二分枚举答案

引例:猜数字

374. 猜数字大小

使用相加除二可能会溢出

mid = (l + r) / 2;

如果 l 和 r 足够大,mid值会导致int 数据类型越界,如果超出int的最大范围 那么结果变成负数

防溢出写法:使用减法r - l不会超出最大的整型范畴

mid = l + (r - l)/2;
 int guessNumber(int n) {
        //数字一定在1~n中
        int l=1;
        int r=n;
        //二分猜值
        while(l<=r){
            int mid=l+(r-l)/2;//防止溢出
            int ans=guess(mid);//调用接口
            //三种
            if(ans==-1){//要猜的更小
                r=mid-1;
            }else if(ans==1){
                l=mid+1;
            }else{//==
                return mid;
            }
        }
        return -1;
    }

可以选用第一个大于等于或最后一个小于等于,注意mid的向上和向下取值

int guessNumber(int n) {
     //数字一定在1~n中
     int l=1;
     int r=n;
     //二分猜值
     while(l<r){
         int mid=l+(r-l)/2;//防止溢出
         int ans=guess(mid);//调用接口
         //找第一个==
         if(ans<=0){
             r=mid;
         }else{
             l=mid+1;
         }
     }
     return l;
 }

int guessNumber(int n) {
     //数字一定在1~n中
     int l=1;
     int r=n;
     //二分猜值
     while(l<r){
         //向上取整
         int mid=l+1+(r-l)/2;//防止溢出
         int ans=guess(mid);//调用接口
         //找第一个==
         if(ans>=0){
             l=mid;
         }else{
             r=mid-1;
         }
     }
     return l;
 }

对于一个最优化问题:

  • 求解:求出这个最优解
  • 判定:判断这个解是否合法

p问题就是能够多项式时间求出问题的解

np问题就是能够多项式时间验证问题的解

二分答案法

如果解空间具有单调性,那么利用二分加上判定就相当于枚举整个问题的解,这种求出最优解的方法叫二分答案法

也就是通过判定算法枚举解空间,一样可以求出最优解

比如:

猜数问题,在解空间每次选择中间值通过判定猜的数是大了还是小了,一样可以得到最终结果

一:运送包裹能力

1011. 在 D 天内送达包裹的能力

也就是想找到一个值W,这个值能满足在days天顺序运送完货物,而且W是最小的

这道题满足单调性,W值越大,那么越能在days天运送完货物

思路:首先写出判定函数isValid(),该函数可以判定解的合法性,然后用二分查找结合该函数就能找到最小值

注意:

  1. W最小值就是所有货物取最大,也就是一下只运送一个货物,但是days会很长
  2. W最大值就是一次运送完所有货物,但是W值一定不是想要的最小值
  3. 判定函数:就是给定的W值能否满足:在days天内运送完所有货物。

说白了就是枚举W的所有可能,但是由于具备单调性,W能在days天运送完,那么W+1一定也能在days天内运完。所以可以利用二分枚举所有可能,然后来利用判定函数来取舍区间

解:

class Solution {
public:
    int shipWithinDays(vector<int>& weights, int days) {
        int l=0;
        int r=0;
        //初始化值,l取最小,r取最大
        for(auto val:weights){
            l=max(l,val);
            r+=val;
        }
        //二分给定W值
        while(l<r){
            int mid=(l+r)>>1;
            if(isValid(weights,days,mid)){
                r=mid;//缩小mid值
            }else{
                l=mid+1;
            }
        }
        return l;
    }
private:
    //给定值W,能否在days天内,完成所有物品运输
    bool isValid(vector<int>& weights,int days,int W){
        //尽量把货物塞满W,多了就下一天再搬(贪心)
        int sum=0;
        int time=1;//第一天
        for(int i=0;i<weights.size();i++){
            if(sum+weights[i]<=W){
                sum+=weights[i];
            }else{//这天超过了,开始下一天的搬运
                time+=1;
                sum=weights[i];
            }
        }
        return time<=days;//只要time比规定时间小就完成
    }
};
二:分割数组最大值

410. 分割数组的最大值

这道题其实和运送包裹完全一样。

思路:

  1. 给定一个用函数来判定:值不超过V的数字和划分一组,看组数是否小于等于k
  2. 然后利用二分来进行求解,取满足判定函数的最小mid值(缩小l)
class Solution {
public:
    int splitArray(vector<int>& nums, int k) {
        int l=0;
        int r=0;
        //最小l:每一个单组成组;最大r:只划分一组
        for(auto& val:nums){
            l=max(l,val);
            r+=val;
        }
        //二分
        while(l<r){
            int mid=(l+r)>>1;
            if(isValid(nums,k,mid)){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        return l;
    }
private:
    //将nums以<=V划分组,看划分的个数是否<=k
    bool isValid(vector<int>& nums,int k,int V){
        int sum=0;
        int time=1;//从第一组开始分
        for(int i=0;i<nums.size();i++){
            if(sum+nums[i]<=V){
                sum+=nums[i];
            }else{//重新划分新组
                time++;
                sum=nums[i];
            }
        }
        return time<=k;
    }
};

这里可以二分查找的原因:

解具备单调性,因为假设划分k组后某组最大值是V,那么V+1,V+2,V+N也一定满足题目的要求。

也就是说:可以用二分枚举V然后通过判定得到能符合要求的最小值。

假设划分k组后,最大值为V。那么划分k-n组一定满足最大值小于V。

题目要求:最大值最小,所以可以找到恰好符合的临界条件

三:制作花最少天数

1482. 制作 m 束花所需的最少天数

​ 题目的解满足单调性:

假定T天就能完成k组m束花的要求,那么T+1,T+2,…也一定可以

假设T天可以采摘超过m束花,那么最优解可能在T-1,T-2,…里面取得

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C24732%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20240430021933640.png&pos_id=img-LQ9vrNp4-1715539346228

class Solution {
public:
    int minDays(vector<int>& bloomDay, int m, int k) {
        int MAX=(1e+9)+1;
        int l=1;//第一天就全采摘了
        int r=MAX;//题目给定最大+1
        while(l<r){
            int mid=(l+r)>>1;
            if(isValid(bloomDay,m,k,mid)){
                r=mid;//缩小r,因为求最短T
            }else{
                l=mid+1;
            }
        }
        return l==MAX?-1:l;
    }
private:
    //给定时间T,问:能否在T天内采摘m束花,每束花必须满足连续相邻k朵
    bool isValid(vector<int>& bloomDay,int m,int k,int T){
         int continuous=0;//计算连续
         int time=0;//初始化不能采摘
         for(int i=0;i<bloomDay.size();i++){
            if(bloomDay[i]<=T){//开花了
                 continuous++;
                 //满足条件更新结果
                 if(continuous==k){
                    time++;
                    continuous=0;
                 }
            }else{
                continuous=0;//断了,重新计数
            }
         }
         return time>=m;//至少要采摘m
    }
};

四:能吃完香蕉的最少天数

875. 爱吃香蕉的珂珂

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

也就是定义好速度V,然后二分枚举求出整个最小V

题目中:

如果pile[i]<V,也就是1h能吃V根数量多于该堆数量,那么取时间一小时

所以相当于pile[i]/V向上取整

可以利用余数来选择是否加一,也可以:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

class Solution {
public:
    int minEatingSpeed(vector<int>& piles, int h) {
        int l=1;
        int r=1e9;
        while(l<r){
            int mid=(l+r)>>1;
            if(isValid(piles,h,mid)){
                r=mid;//缩小r
            }else{
                l=mid+1;
            }
        }
        return l;
    }
private:
    //给定速度V:在h小时内能否吃完所有香蕉
    bool isValid(vector<int>& piles,int h,int V){
        int hour=0;//千万别忘了初始值0
        for(int i=0;i<piles.size();i++){
             if(piles[i]%V==0){
                hour+=piles[i]/V;
             }else{
                hour+=piles[i]/V+1;
             }
        }
        return hour<=h;
    }
};

计算hour还可以:

for(auto pile:piles){
      hour+=pile/V+(pile%V==0?0:1);    
  }

也可以:

for(int i=0;i<piles.size();i++){
      hour+=(piles[i]+V-1)/V;// hour+=(piles[i]-1)/V+1;
  }

注意:

因为题目限制了珂珂一个小时之内只能选择一堆香蕉吃,因此速度最大值就是这几堆香蕉中,数量最多的那一堆。

int r=1;

for(auto val:piles) r=max(r,val);

排序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

基于比较的排序算法,时间复杂度:不能突破O(NlogN)

简单证明:

在这里插入图片描述

  • 比较排序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 非比较排序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

选插冒三种初级排序

选择排序
// 选择排序
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        for (int i = 0; i < n - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j < n; j++) {
                if (nums[j] < nums[minIndex]) {
                    minIndex = j;
                }
            }
            swap(nums[i], nums[minIndex]);
        }
        return nums;
    }
插入排序

在这里插入图片描述

  • 直接交换
//插入排序
    vector<int> sortArray(vector<int>& nums) {
        int n=nums.size();
        for(int i=1;i<n;i++){//i从1开始,默认第一个有序
            //结束条件是:j>0,因为访问j-1
            for(int j=i;j>0 && nums[j]<nums[j-1];j--){
                swap(nums[j],nums[j-1]);
            }
        }
        return nums;
    }
  • 优化

可以不直接交换,记住值,然后后一个值等于前一个值,停止时,值改成最先记录的

//插入排序
    vector<int> sortArray(vector<int>& nums) {
        int n=nums.size();
        for(int i=1;i<n;i++){//i从1开始,默认第一个有序
            int e=nums[i];
            int j;
            for(j=i;j>0 && nums[j-1]>e;j--){
                nums[j]=nums[j-1];
            }
            nums[j]=e;
        }
        return nums;
冒泡排序

把大的依次放到后面,每一次冒泡都会得到一个最大值,冒泡次数=数组长度-1

在这里插入图片描述

// 冒泡排序
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        for (int i = 0; i < n - 1; i++) {//冒泡长度-1次
            for (int j = 0; j < n - 1 - i; j++) {
                if (nums[j] > nums[j + 1]) { // 大的放后面
                    swap(nums[j], nums[j + 1]);
                }
            }
        }
        return nums;
    }

当然也可以从n-1开始,小的放前面

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果在一次遍历中没有进行任何交换,可以提前结束排序,因为这意味着数列已经排序完成。

优化:引入是否交换过的标志

 // 冒泡排序
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        bool flag=false;//是否交换过
        for(int i=0;i<n-1;i++){
            for(int j=0;j<n-1-i;j++){
                //大的后放
                if(nums[j]>nums[j+1]){
                    swap(nums[j],nums[j+1]);
                    flag=true;
                }
            }
            if(!flag) break;
        }
        return nums;
    }

归并排序

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        mergeSort(nums,0,nums.size()-1);
        return nums;
    }
private:
    void merge(vector<int>& nums,int l,int mid,int r){
         int i=l;
         int j=mid+1;
         int k=0;
         //创建aux存储合并结果
         int* aux=new int[r-l+1];
         while( i<=mid && j<=r){
            if(nums[i]<nums[j]){
                aux[k++]=nums[i++];
            }else{
                aux[k++]=nums[j++];
            }
         }
         //判断while中是哪个条件不满足
         while(i<=mid){
            aux[k++]=nums[i++];
         }
         while(j<=r){
            aux[k++]=nums[j++];
         }
         //把aux结果赋值给nums
         //注意:aux:[0,r-l+1) nums:[l,r]
         /*for(int i=0;i<r-l+1;i++){
            nums[i+l]=aux[i];
         }*/
         for(int i=l;i<=r;i++){
            nums[i]=aux[i-l];
         }
    }
    void mergeSort(vector<int>& nums,int l,int r){
        if(l==r) return;
        int mid=(l+r)>>1;
        //分
        mergeSort(nums,l,mid);
        mergeSort(nums,mid+1,r);
        //治:合并两个结果
        merge(nums,l,mid,r);

    } 
};

也可以改写merge

条件1:i<=mid 条件2:j<=r

if(条件1不满足)

else if(条件2不满足)

else if (此时判断)//此时条件1和条件2一定满足了

void merge(vector<int>& nums,int l,int mid,int r){
      int i=l;
      int j=mid+1;
      //创建aux存储合并结果
      int* aux=new int[r-l+1];
      for(int k=0;k<r-l+1;k++){
         if(i>mid){
             aux[k]=nums[j++];
         }else if(j>r){
             aux[k]=nums[i++];
         }
         //i<=mid && j<=r
         else if(nums[i]<nums[j]){
             aux[k]=nums[i++];
         }else{
             aux[k]=nums[j++];
         }
      }
      //还原nums
      //for(int i=l;i<=r;i++){
       //  nums[i]=aux[i-l];
      //}
      for(int i=0;i<r-l+1;i++){
         nums[i+l]=aux[i];
      }
 }

for(int k=0;k<r-l+1;k++){
         if(i<=mid && j<=r){
             if(nums[i]<nums[j]){
                 aux[k]=nums[i++];
             }else{
                 aux[k]=nums[j++];
             }
         }
         else if(i<=mid){//j>r
             aux[k]=nums[i++];
         }else{
             aux[k]=nums[j++];
         }
      }

初级排序的优化排序

堆排序

简洁实现,直接利用stl

由于是从小到大,可以存值的负数的大根堆,然后赋值取负号

vector<int> sortArray(vector<int>& nums) {
        priority_queue<int> pq;
        //首先建堆
        for(int i=0;i<nums.size();i++){
            pq.push(-nums[i]);
        }
        //出堆
        for(int i=0;i<nums.size();i++){
            nums[i]=-pq.top();//负号
            pq.pop();
        }
        return nums;
    }

当然直接建立小根堆也行: priority_queue<int,vector<int>,greater<int>> pq;//大的沉底


定义采用向下调整函数

思路:

首先对数组原地调整成大顶堆

  • 注意,因为数组从0开始,k的左孩子k*2+1右孩子k *2+2
  • k的父节点:(k-1)/2
  • 从最后一个父节点不断向上调整至根节点就可以得到堆

接下来要从小到大输出:

  • 每次把堆顶元素往后放,然后从0开始重新调整堆
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n=nums.size();
        //从第一个非叶子结点向下调整,直至到根
        for(int i=(n-2)/2;i>=0;i--){
            shiftDown(nums,n,i);
        }
        //从小到大,把根顶后移
        for(int i=n-1;i>0;i--){
            swap(nums[0],nums[i]);
            shiftDown(nums,i,0);//注意:[0,n-2],所以传n-1
        }
        return nums;
    }
private:
    //向下调整---数组长度,从k开始向下调整
    void shiftDown(vector<int>& nums,int n,int k){
        //注意:k*2+1是左孩子,k*2+2是右孩子
        while(k*2+1<n){
            int j=k*2+1;
            if(j+1<n && nums[j+1]>nums[j]) j++;
            //比较父节点和儿子最大结点j
            if(nums[k]<nums[j]){
                swap(nums[k],nums[j]);
            }else{
                break;
            }
            k=j;//k指向交换后的位置
        }
    }
};

优化:记录值,赋值代替交换

void shiftDown(vector<int>& nums,int n,int k){
        //注意:k*2+1是左孩子,k*2+2是右孩子
        int e=nums[k];
        while(k*2+1<n){
            int j=k*2+1;
            if(j+1<n && nums[j+1]>nums[j]) j++;
            //比较父节点和儿子最大结点j
            if(nums[j]>e){
                nums[k]=nums[j];//父=儿子
            }else{
                break;
            }
            k=j;//k指向交换后的位置
        }
        nums[k]=e;
    }
希尔排序(不常用)
  • 利用增量分组对插入排序进行优化

这里最后的0如果选用插入排序会跨越整个数组,影响算法性能

希尔排序的做法:

​ 先将元素进行分组,每次先在组内进行排序,尽量让元素可以在早期尽量多地移动。

选择分组的跨度是5,跨度是数组长度的一半。

相同颜色的元素为一组

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 在组内进行插入排序

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 接着将跨度缩小一半,从5变成2,接着重复上述逻辑

在这里插入图片描述

  • 最后,跨度设为1,总体进行插入排序,得到结果。
vector<int> sortArray(vector<int>& nums) {
        int n=nums.size();
        int h=n/2;//假设以一半为增量
        while(h>0){
            for(int i=h;i<n;i++){//从增量往前插入
                for(int j=i;j>=h && nums[j]<nums[j-h];j-=h){
                    swap(nums[j],nums[j-h]);
                }
            }
            h=h>>1;//更新h
        }
        return nums;
    }

这里增量先选取数组长度一半,然后每次缩小一半,直至为1

快速排序
  • 快速排序也是一个基于分治的算法

从数组中选取一个中轴元素pivot

  • 把小元素放在pivot左边
  • 把大元素放在pivot右边

然后继续对左边和右边进行快速排序

归并排序:每次对半排序数组,排序好左右数组后,再合并成有序数组

快速排序:先把左右数组调配出,然后对左右数组进行排序

快速排序的分治:

在这里插入图片描述

也就是说:先把元素分成左右两部分后,然后再接着对左右两部分继续使用快排

对于:[L,R]的数组而言,当 L>=R停止递归

方法一:双指针法

这种方法:先移动i或者先移动j都可以

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        quickSort(nums,0,nums.size()-1);
        return nums;
    }
private:
    //数组分成:  (比基准值小的序列  基准  比基准值大的序列)
    //返回int,基准值不参与比较
    int quickDetail(vector<int>& nums,int l,int r){
        //随机选择基准元素,并放在nums[l]位置
        swap(nums[l],nums[rand()%(r-l+1)+l]);
        int e=nums[l];
        //双指针
        int i=l+1;
        int j=r;
        while(1){
            while(i<=j && nums[i]<=e) i++;
            while(i<=j && nums[j]>=e) j--;
            if(i>j) break;
            swap(nums[i++],nums[j--]);
        }
        //j停在小的位置
        swap(nums[l],nums[j]);
        return j;
    }
    //分治
    void quickSort(vector<int>& nums,int l,int r){
        if(l>=r) return;//注意是大于等于
        //先划分数组
        int p=quickDetail(nums,l,r);
        //再对左右两部分进行分治
        quickSort(nums,l,p-1);
        quickSort(nums,p+1,r);
    }
};

也可以按照符合要求的判断

int quickDetail(vector<int>& nums,int l,int r){
     //随机选择基准元素,并放在nums[l]位置
     swap(nums[l],nums[rand()%(r-l+1)+l]);
     int e=nums[l];
     //双指针
     int i=l+1;
     int j=r;
     while(i<=j){
         //先j后i也行
         while(i<=j && nums[j]>=e) j--;
         while(i<=j && nums[i]<=e) i++;
         if(i<=j)
         swap(nums[i++],nums[j--]);
     }
     //j停在小的位置
     swap(nums[l],nums[j]);
     return j;
 }
方法二:大于跳过

思路:i指向小于等于初始值的位置,j不断右移

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 遇到>基准值的右移,i不动,j右移
  • 遇到<=基准值的,把j指向值第一个>基准值的位置交换,也就是i后面的第一个元素

----所以:i的位置指向的是最后一个<=基准值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

//数组:[l,r]
    int partition(vector<int>& nums,int l,int r){
        swap(nums[l],nums[rand()%(r-l+1)+l]);
        int e=nums[l];
        int i=l;
        for(int j=i+1;j<=r;j++){
            if(nums[j]<=e){//这里可以不带等号
                swap(nums[j],nums[++i]);
            }
        }
        swap(nums[l],nums[i]);
        return i;
    }

注意等号无所谓,可以大于等于跳过,也可以只大于就跳过

这种方法存在致命缺陷,就是等于元素过多,会让左右两部分极度不平衡

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

方法三:三路快排

[l+1,lt]维护的是<10的部分[gt,r]维护的>10的部分,从而[lt+1,gt-1]维护等于

i==gt程序停止

1:这是i值>10情况:交换i值和gt-1位置的值,i不变,gt-1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2:这是i值<10的情况:交换该值和lt+1的位置的值,lt+1,i右移

在这里插入图片描述

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        threeWayQuick(nums,0,nums.size()-1);
        return nums;
    }
private:
    void threeWayQuick(vector<int>& nums,int l,int r){
        if(l>=r) return;
        //初始化lt,gt使得:[l+1,lt],[gt,r]为空
        int lt=l;
        int gt=r+1;
        int e=nums[l];
        int i=l+1;
        while(i<gt){
            if(nums[i]>e){
                swap(nums[i],nums[--gt]);
            }else if(nums[i]<e){
                swap(nums[i],nums[++lt]);
                i++;
            }else{//==e,直接右移
                i++;
            }
        }
        swap(nums[l],nums[lt]);
        //分治
        threeWayQuick(nums,l,lt-1);
        threeWayQuick(nums,gt,r);
    }
};

这里等于不需要传值,所以需要两个值,把分治和划分情况写成一个函数

也可以传入pair

pair<int,int> partition(vector<int>& nums,int l,int r){
        int lt=l;
        int gt=r+1;
        int e=nums[l];
        int i=l+1;
        while(i<gt){
            if(nums[i]>e){
                swap(nums[i],nums[--gt]);
            }else if(nums[i]<e){
                swap(nums[i],nums[++lt]);
                i++;
            }else{//==e,直接右移
                i++;
            }
        }
        swap(nums[l],nums[lt]);
        return {lt-1,gt};
    }
    void threeWayQuick(vector<int>& nums,int l,int r){
        if(l>=r) return;
        //初始化lt,gt使得:[l+1,lt],[gt,r]为空
        auto [k,v]=partition(nums,l,r);
        //分治
        threeWayQuick(nums,l,k);
        threeWayQuick(nums,v,r);
    }

算法的稳定性

若经过排序,这些相同元素相对次序保持不变,则称这种排序算法是稳定的;否则称为不稳定的。

在原序列中,A1=A2,且A1在A2之前,经过排序后,A1仍在A2之前。

稳定性意义:

​ 要排序的内容是一个具备多个数字属性的复杂对象,且原本的初始顺序存在意义,需要在二次排序的基础上保持原有排序的意义。


各排序算法的稳定性:

1、堆排序、快速排序、希尔排序、选择排序是不稳定的排序算法;

2、基数排序、冒泡排序、插入排序、归并排序是稳定的排序算法

非比较类排序

计数排序
  • 简单版—不考虑稳定性

    把数组值大小作为count的下标,统计频次

    然后赋值回给原数组,注意i值是大小

在这里插入图片描述

void simpleCountSort(vector<int>& nums){
    int n = nums.size();
    int sz = 0;
    //数组元素会作为下标,计算出数组长度
    for(auto val:nums){
        sz = max(sz, val);
    }
    vector<int> count(sz + 1);//数组标号从0开始
    //遍历数组,统计次数
    for(auto val:nums){
        count[val]++;
    }
    //最后赋回给nums
    int j = 0;
    for (int i = 0; i <= sz;i++){//遍历count
        if(count[i]!=0){
            while(count[i]-->0){
                nums[j++] = i;
            }
        }
    }
}
  • 考虑稳定性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

计算前缀和,这样:元素为nums[i],则count[nums[i]]-1对应的位置就是存放的正确位置

数组赋值后,把对应频次-1

vector<int> countSort(vector<int>& nums){
    /*初始化count频次数组*/
    //1:计算大小
    int sz = 0;
    for(auto val:nums){
        sz = max(sz, val);
    }
    vector<int> count(sz + 1);
    /*计算count前缀和*/
    //1:遍历数组
    for (auto val:nums){
        count[val]++;
    }
    //2:更新前缀
    for (int i = 0; i < sz;i++){
        count[i + 1] += count[i];
    }
    /*存放结果*/
    //倒着赋值
    vector<int> res(nums.size());
    for (int i = nums.size() - 1;i>=0; i--) {
        res[count[nums[i]] - 1] = nums[i];
        count[nums[i]]--;//别忘了频次-1
    }
    return res;
}
桶排序(了解)—BucketSort
  • 在桶排序里的“桶”是指一种数据结构,代表一个区间范围,用于临时存储排序元素
  • 桶排序是一种分布式排序算法,将待排序数组元素分到有限数量的桶里,每个桶里的数据分别进行排序, 按照桶的顺序将元素合并成有序数组。

桶排序的工作原理可以拆分为 3 步:

  1. 初始化 m 个桶,将 n 个元素分配到 m 个桶中
  2. 对每个桶内的数据分别进行排序,这里可以借助任意排序算法
  3. 按照桶的从大到小的顺序,将所有元素合并成有序数组

例子:

  1. 假设使用的待排序元素整除规则是 nums[i] / 10,分别将待排序数组的每个元素分配到每个桶中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 对每个桶内的数据分别进行排序,桶内的排序可以借助任意排序算法,比如插入排序、快速排序等。

在这里插入图片描述

  1. 按照桶的从大到小的顺序,将所有元素合并成有序数组。

在这里插入图片描述

习题:颜色分类

75. 颜色分类

采用不稳定的计数排序就行

void countSort(vector<int>& nums){
        //只有0,1,2,所以key最大2
        vector<int> count(3);
        //频次统计
        for(auto val:nums){
            count[val]++;
        }
        //赋值回去
        int j=0;
        for(int i=0;i<=2;i++){
            if(count[i]!=0){
                while(count[i]-->0){
                    nums[j++]=i;
                }
            }
        }
    }

排序力扣题

一:数组的相对排序

1122. 数组的相对排序

方法一:计数排序

用count数组统计arr1的频次,然后遍历arr2输出元素,最后遍历count输出余下元素

 //计数排序
    vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
        //先统计arr1的元素频次
        vector<int> count(1001);
        for(auto &val:arr1){
            count[val]++;
        }
        //先把arr2的元素输出
        int j=0;
        for(auto &val:arr2){
            while(count[val]-->0){
                arr1[j++]=val;
            }
        }
        //再把剩余元素输出
        for(int i=0;i<count.size();i++){
            while(count[i]-->0){
                arr1[j++]=i;
            }
        }
        return arr1;
    }

方法二:直接比较

其实就是优先输出arr2顺序的元素,然后然后比较顺序输出

----先用map记录arr2的次序,map[arr2[i]]=i,然后进行比较:

  • 如果两个元素都在arr2中,比较数组次序
  • 如果一个元素在arr2中,输出这个元素
  • 如果都不在arr2中,那么正常比较大小
 vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
        //用map记录arr2相对次序:mp[arr2[i]]=i
        unordered_map<int,int> mp;
        for(int i=0;i<arr2.size();i++){
            mp[arr2[i]]=i;
        }
        //对arr1用自定义比较
        sort(arr1.begin(),arr1.end(),[&](int x,int y)->bool{
              if(mp.count(x) && mp.count(y)){
                return mp[x]<mp[y];
              }else if(mp.count(x)){
                return true;//要x<y的x
              }else if(mp.count(y)){
                return false;//x<y
              }else{
                return x<y;
              }
        });
        return arr1;
    }

当然也可以这样写sort

 //对arr1用自定义比较
        sort(arr1.begin(),arr1.end(),[&](int x,int y)->bool{
              if(mp.count(x)){
                 return mp.count(y)?mp[x]<mp[y]:true;
              }else{//x不再
                return mp.count(y)?false:x<y;
              }
        });

二:数组中第k大的元素

215. 数组中的第K个最大元素

方法一:堆排序

注意:最大值是逆序的,第一个最大n-1,…

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int n=nums.size();
        for(int i=(n-2)/2;i>=0;i--){
            shiftDown(nums,n,i);
        }
        for(int j=n-1;j>=0;j--){
            swap(nums[0],nums[j]);
            shiftDown(nums,j,0);
        }
        return nums[n-k];//n-1 n-2
    }
private:
    void shiftDown(vector<int>& nums,int n,int m){
        int e=nums[m];
        while(m*2+1<n){
           int j=m*2+1;
           //注意if和while别用错了
           if(j+1<n && nums[j+1]>nums[j]) j+=1;
           if(nums[j]>e){
               nums[m]=nums[j];
           }else{
            break;
           }
           m=j;
        }
        nums[m]=e;
    }
};

方法二:快速排序


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/619262.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Shell编程之循环语甸与函数

for 遍历循环 1&#xff09;for 变量 in 取值列表 for i in $(seq 1 10) do 命令序列 .... done 2&#xff09;for ((变量初始值; 变量范围; 变量的迭代方式)) for ((i1; i<10; i)) do 命令序列 .... done IFS for循环取值列表分隔符 set | grep IFS …

SSH常用功能介绍-高级功能

一、介绍 SSH&#xff08;Secure Shell&#xff09;是一种用于远程登录和执行命令的网络协议&#xff0c;它提供了加密的连接&#xff0c;保证了数据的安全性。除了基本的远程登录功能外&#xff0c;SSH还提供了许多高级功能&#xff0c;以下是一些常用的高级功能介绍&#xf…

Redis集群安装

将Redis安装包分别上传到3个文件夹&#xff0c;并解压缩 #编译并指定安装目录 cd /root/usr/local/redis-cluster/redis-7001/redis-6.2.6/ make make PREFIX/root/usr/local/redis-cluster/redis-7001 install # cd /root/usr/local/redis-cluster/redis-7002/redis-6.2.6/ m…

FreeRTOS二值信号量

目录 一、信号量的概念 1、信号量的基本概念 2、信号量的分类 二、二值信号量简介 三、二值信号量相关API 1、创建二值信号量 2、释放二值信号量 3、获取二值信号量 四、二值信号量实操 1、实验需求 2、CubeMX配置 3、代码实现 一、信号量的概念 1、信号量的基本概…

使用 CloudFlare 后如何才能不影响搜索引擎蜘蛛爬虫

今天,明月给大家再次详细讲解一下,明月在使用 CloudFlare 后如何才能不影响搜索引擎蜘蛛爬虫对站点的抓取,因为这是很多首次使用 CloudFlare 的站长们容易忽略和触犯的问题,并不是 CloudFlare 不友好,而是 CloudFlare 的防火墙(WAF)实在是太给力。其实在【CloudFlare 如…

IDEA及Maven配置代理及Maven中央仓库配置详解

一、配置代理 首先&#xff0c;需要本地开启代理入口&#xff0c;如图。 这个跟你使用代理软件有关。像我使用的是qv2ray。 其次&#xff0c;idea配置代理&#xff0c;如图。 1.1 idea配置代理 打开Settings&#xff0c;如图 1.2 maven配置代理 maven配置代理&#xff0c;修…

2024湖南理工学院程序设计竞赛(同步赛) G. 区间递减(思维题 分类讨论 ST表)

题目 https://ac.nowcoder.com/acm/contest/82672/G 思路来源 出题人 涼風青葉7代码 题解 注意到三种情况即可&#xff0c; 第一种情况&#xff0c;10 9 8 1 2&#xff0c;保留1 第二种情况&#xff0c;6 5 10 9 4 4&#xff0c;保留5 4 4 第三种情况&#xff0c;6 5 4&…

基于Springboot的校园疫情防控管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园疫情防控管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

Python 整数类型(int)详解:无限范围与多种进制

引言 在编程中&#xff0c;整数是最基本的数据类型之一。不同编程语言对整数的处理方式各不相同&#xff0c;这往往影响到程序的性能和开发者的选择。本文将深入探讨 Python 中的整数类型&#xff08;int&#xff09;&#xff0c;其独特的处理方式&#xff0c;以及它在日常编程…

【数据结构】环状链表OJ题

✨✨✨专栏&#xff1a;数据结构 &#x1f9d1;‍&#x1f393;个人主页&#xff1a;SWsunlight 一、OJ 环形链表&#xff1a; 快慢指针即可解决问题: 2情况&#xff1a; 快指针走到结尾&#xff08;不是环&#xff09;快指针和尾指针相遇&#xff08;是环的&#xff09; …

C语言——文件缓冲区

一、用户缓冲区和系统缓冲区 缓冲区的概念确实可以分为多个层次&#xff0c;其中最常见的两个层次是用户缓冲区和系统缓冲区。 这里的用户缓冲区和系统缓冲区都包括输入输出缓冲区。 1、用户缓冲区&#xff08;User-space Buffer&#xff09; 用户缓冲区是指由用户程序&…

09.zabbix自定义模块并使用

zabbix自定义模块并使用 根据tcp的11中状态获取值&#xff0c;进行批量配置监控项 [rootyunlong66 ~]# cat /etc/zabbix/zabbix_agentd.d/tcp.conf UserParameterESTABLISHED,netstat -antp |grep -c ESTABLISHED UserParameterSYN_SENT,netstat -antp |grep -c SYN_SENT Use…

免费思维13招之七:空间型思维

免费思维13招之七:空间型思维 本篇给你带来的是空间型思维。 空间型思维,具体分为内部空间型思维和外部空间型思维。 什么叫内部空间型思维呢? 内部空间型就是充分利用现有空间或资源为社会提供免费服务,积累人气,增加流量,从而带动消费。 为什么你生意不好?为什么你…

银河麒麟服务器操作系统扩展磁盘容量方法(非LVM)

此方法的使用场景为&#xff1a;对普通的分区扩容&#xff0c;分区格式为xfs&#xff0c;不适用于lvm逻辑卷的扩容。 注意&#xff1a;扩展磁盘空间的操作风险较高&#xff0c;最好先做好备份&#xff0c;或在实验环境下操作成功后&#xff0c;再对目标系统进行扩容操作&#…

当代 Qt 正确的 安装方法 及 多版本切换

此文写于 20240511 首先去网站Index of /official_releases/online_installers下载一个安装器 安装器有什么用? 可以浏览安装版本 安装组件 安装器版本越能 能装的东西越多 现在只能选Qt5 和 Qt6 至于你公司用的Qt4 我也没招 见招时再拆招 安装器 默认国外源 可以换国内…

栈的讲解

栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底&#xff08;因为先进后出&#xff09;。栈中的数据元素遵守后进先出LIFO&#xff08;Last In Firs…

18 【Aseprite 作图】工具栏介绍

1 在没有输入法的情况下&#xff0c; 按住Shift 大写的N&#xff0c;就可以快速新建图层 ctrl z 撤回这个图层 2 双击图层&#xff0c;可以修改图层名称和属性 3 按住图层&#xff0c;拖动图层&#xff0c;可以把图层拉到 组&#xff0c;就可以方便一组一组管理图层 4 保存的…

Redis—图文详解高可用原因

本文不会讲解Redis的用途&#xff0c;关于用途会发另一片文章讲解&#xff0c;本文主要讲的是高可用的原理。 Redis高可用主要有以下三个原因&#xff1a;主从模式(上一篇讲Kafka的文章里有涉及到)&#xff0c;哨兵模式&#xff0c;Redis-Cluster(Redis集群)。 什么是主从模式…

消息队列——Kafka

1、什么是消息队列&#xff0c;什么是Kafka&#xff1f; 我们通常说的消息队列&#xff0c;简称MQ&#xff08;Message Queue&#xff09;&#xff0c;它其实就指消息中间件&#xff0c;比较流行的开源消息中间件有&#xff1a;Kafka、RabbitMQ、RocketMQ等。今天我们要介绍的…

基于yolov8+gradio目标检测演示系统设计

YOLOv8与Gradio&#xff1a;开启目标检测的可视化新篇章 随着人工智能技术的飞速发展&#xff0c;目标检测作为计算机视觉领域的重要分支&#xff0c;已经广泛应用于安防监控、自动驾驶、医疗影像等多个领域。而YOLO&#xff08;You Only Look Once&#xff09;系列算法作为目…