全网最全的快速排序方法--Hoare快排 挖坑法快排 二路快排 三路快排 非递归快排

news2024/10/5 15:27:41

目录

一.快速排序

1.基本介绍

2.基本思想

二.Hoare快排

0.前情知识

1.交换数组中的两个元素

2.指定范围的插入排序

1.基本思路

2.代码实现

3.优化思路

三.挖坑法快排(校招中适用)

1.基本思路

2.代码实现

四.二路快排

1.基本思路

2.代码实现

3.优化思路

五.三路快排

1.基本思路

2.代码实现

六.非递归快排的实现

1.思路分析

2.代码实现


一.快速排序

1.基本介绍

快速排序(Quicksort)由英国计算机科学家Tony Hoare于1959年发明,是一种经典的排序算法,被广泛应用于计算机科学领域。快速排序(Quick Sort)是一种常见的基于比较的排序算法,也是最常用的排序算法之一。

快速排序是一种

排序方法
最好
平均
最坏
空间复杂度
稳定性
快速排序
O(n * log(n))
O(n * log(n)) O(n^2) O(log(n)) ~ O(n) 不稳定

稳定性:如果a原本在b前面,并且a=b,排序之后a仍然在b的前面,那么就成这个算法是稳定的,否则就是不稳定的;

2.基本思想

以下是快速排序的基本思路

  1. 选择一个基准元素(pivot)。

  2. 将序列中所有小于基准元素的元素放在基准元素的左侧,所有大于基准元素的元素放在右侧。

  3. 递归地对基准元素左侧和右侧的子序列进行排序。

  4. 递归结束后,整个序列就变得有序。

以下介绍的各种快排的思路都大致一样的,主要就是在划分的策略不一样,基本思路都是一样的

二.Hoare快排

0.前情知识

1.交换数组中的两个元素

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

2.指定范围的插入排序

    /**
     * 将区间[left,right]的元素进行插入排序
     *
     * @param arr
     * @param left
     * @param right
     */
    private static void insertSortInterval(int[] arr, int left, int right) {
        for (int i = left + 1; i <= right; ++i) {
            for (int j = i; j > left && arr[j] < arr[j - 1]; --j) {
                swap(arr, j, j - 1);
            }
        }
    }

1.基本思路

我们这里重点介绍分区的方法,因为这几个快速排序的区别主要就是在分区的方法,快速排序的基本思路我们在上面已经说出,在这里就不赘述了.

这里来讲解分区的方法,也就是partition部分的代码.

其实简而言之而很好理解,我们选取左端的值作为中轴值,定义两个指针i,j,  j从右端向左进行遍历,当遍历到比中轴值(pivot)小的数时候,停止,i从左端向右进行遍历,当遍历到比中轴值大的时候,停止,这个时候交换位置i和位置j的元素 ,当i==j的时候,停止循环,然后交换位置left和i(j)的元素,此时i左边的元素比arr[i]小,i右边的元素比arr[i]大,返回此时的位置i.

初始情况:

第一次交换

第二次交换

 结束循环

中轴值交换操作

此时pivot=6左边的元素都比6的值小,右边的值都比pivot大了,这样一次分区结束.

2.代码实现

    //Hoare版快排
    public static void quickSortHoare(int[] arr) {
        quickSortHoareInternal(arr, 0, arr.length - 1);
    }

    private static void quickSortHoareInternal(int[] arr, int left, int right) {
        if (left >= right) {
            return;
        }
        int pivotIndex = partitionHoare(arr, left, right);
        quickSortHoareInternal(arr, left, pivotIndex - 1);
        quickSortHoareInternal(arr, pivotIndex + 1, right);

    }

    private static int partitionHoare(int[] arr, int left, int right) {
        //选取最左边的元素作为中轴值
        int pivot = arr[left];
        int i = left, j = right;
        while (i < j) {
            //从右边找到比pivot小的元素
            while (i < j && arr[j] >= pivot) {
                j--;
            }

            //从左边找到比pivot大的元素
            while (i < j && arr[i] <= pivot) {
                i++;
            }
            swap(arr, i, j);

        }
        //将中轴值元素和i==j时的位置交换,此时i左边的元素都比pivot小,右边都比pivor大
        swap(arr,i,left);
        return i;

    }

3.优化思路

优化一:学习过插入排序我们知道,对于小数组来说,插入排序的效率可谓是十分的高,对于长度小于64的小数组,我们不妨直接使用插入排序进行排序

    private static void quickSortHoareInternal(int[] arr, int left, int right) {
        if (right - left <= 64) {
            insertSortInterval(arr, left, right);
            return;
        }
        int pivotIndex = partitionHoare(arr, left, right);
        quickSortHoareInternal(arr, left, pivotIndex - 1);
        quickSortHoareInternal(arr, pivotIndex + 1, right);

    }

优化二:我们来考虑这样一个问题,当我们需要排序的数组基本有序的时候,我们每次还是选择数组的第一个元素作为中轴值,这样我们要进行递归O(n)的空间复杂度,这个时候快速排序就退化为了冒泡排序,时间复杂度为O(n^{2})

那我们该如何选取中轴值呢?

第一种方法:随机选取中轴值

生成一个范围为[left,right]的随机数生成下标为index,将index与left交换,之后就和我们之前的代码一模一样了.

    private static int partitionHoare(int[] arr, int left, int right) {
        //优化,选取随机值
        int index = ThreadLocalRandom.current().nextInt(left, right + 1);
        swap(arr, index, left);
        int pivot = arr[left];
        int i = left, j = right;
        while (i < j) {
            //从右边找到比pivot小的元素
            while (i < j && arr[j] >= pivot) {
                j--;
            }

            //从左边找到比pivot大的元素
            while (i < j && arr[i] <= pivot) {
                i++;
            }
            swap(arr, i, j);

        }
        swap(arr, i, left);
        return j;

    }

第二种方法:三数中值分割法

我们都希望我们选取的中轴值恰好为待排序数组的中值,这样递归的次数一定是最少的,因此我们可以使用三数取中的方法来进行估算中值(当然最坏情况也可能取到第二小的情况,但概率相对来说很小),我们通常选取左端,右端,中心位置上的三个元素的中值作为枢纽元素.来看代码实现

    public static void median(int[] arr, int left, int right) {
        //中间索引下标,相当于(left+right)/2
        int center = left + ((right - left) >> 1);
        if (arr[center] < arr[left]) {
            swap(arr, center, left);
        }
        if (arr[right] < arr[left]) {
            swap(arr, right, left);
        }
        if (arr[right] < arr[center]) {
            swap(arr, center, right);
        }
        swap(arr, left, center);//此时中值被交换到了最左边位置
    }

    private static int partitionHoare(int[] arr, int left, int right) {
        median(arr,left,right);
        int pivot = arr[left];
        int i = left, j = right;
        while (i < j) {
            //从右边找到比pivot小的元素
            while (i < j && arr[j] >= pivot) {
                j--;
            }

            //从左边找到比pivot大的元素
            while (i < j && arr[i] <= pivot) {
                i++;
            }
            swap(arr, i, j);

        }
        swap(arr, i, left);
        return j;

    }

三.挖坑法快排(校招中适用)

1.基本思路

挖坑法快排的基本思路:我们先把pivot的值保留下来,此时相当于在pivot位置(也就是left位置)挖了一个坑,然后我们开始循环,循环条件和Hoare快排一样,j从右端向左找到比pivot小的元素,将arr[j]的值填到之前挖的坑(也就是left位置),然后在j位置挖一个坑,i从左端开始寻找比pivot大的元素,找到后将它填到j位置(之前挖的坑),以此类推,最终i==j,这是挖的最后一个坑,将pivot值填入到arr[i]的位置,此时我们挖坑填坑操作完成,arr[i]左边的元素都比它小,右边的元素都比它大

可以看出来的是:相对于Hoare快排来说,交换的次数大大减少

 第一次挖坑填坑

 第二次挖坑填坑

 第三次挖坑填坑

 第四次挖坑填坑

 第五次挖坑填坑

最后一次arr[i]=pivot

2.代码实现

优化的地方和Hoare快排一样,这里直接给出优化之后的代码,这里采用了随机值选取中轴值的方法.

    //挖坑法快排
    public static void quickSortHole(int[] arr) {
        quickSortHoleInternal(arr, 0, arr.length - 1);
    }

    private static void quickSortHoleInternal(int[] arr, int left, int right) {
        if (right - left <= 64) {
            insertSortInterval(arr, left, right);
            return;
        }
        int pivotIndex = partitionByHole(arr, left, right);
        quickSortHoleInternal(arr, left, pivotIndex - 1);
        quickSortHoleInternal(arr, pivotIndex + 1, right);

    }

    private static int partitionByHole(int[] arr, int left, int right) {
        //优化,选取随机值
        int index = ThreadLocalRandom.current().nextInt(left, right + 1);
        swap(arr, index, left);
        int pivot = arr[left];
        int i = left, j = right;
        while (i < j) {
            //从右边找到比pivot小的元素
            while (i < j && arr[j] >= pivot) {
                j--;
            }
            //将这个小的元素放到左边
            arr[i] = arr[j];
            //从左边找到比pivot大的元素
            while (i < j && arr[i] <= pivot) {
                i++;
            }
            //将这个大的元素放到右边
            arr[j] = arr[i];

        }
        //最后一定是i==j退出
        arr[j] = pivot;
        return j;

    }

四.二路快排

1.基本思路

二路快排其实就是分区进行,也是国外教材上实现的快排的方法(算法4),总体的效率要比挖坑法和Hoare法要快一些.

主要就是维护两个分区,一个分区的值全比pivot的值小,另一个分区的值大于等于pivot的值,最终分区完毕之后的结果如下图所示:

维护的小于pivot的区间为[left+1,j]  维护的大于等于pivot的区间为[j+1,i-1] 其中i指向的是正在遍历到的元素位置,j指向的是小于pivot区间的最后位置,j+1是大于等于pivot的开始位置.白色的区域表示未遍历到的区域.

现在我们来讨论两种情况,一种是当arr[i]>=pivot的时候,这个时候我们只需要将i指向位置的变为黄色,也就是将i++处理,这是时候大于等于pivot的区间就相当于增加了一个元素

另一种是arr[i]<pivot的时候,这个时候我们只需要将arr[i]的元素和arr[j+1]的元素进行交换,并且要将j++,然后将i++,这样小于pivot的区间就增加了一个元素.

这样一种循环下去,直到i遍历完最后一个元素的时候,这个时候我们将j位置的元素和left位置的元素进行交换,这样就实现了pivot元素左边的元素小于pivot,pivot右边的元素大于等于pivot.

 具体的代码实现看下面

2.代码实现

    // 算法4的分区快排
    public static void quickSort(int[] arr) {
        quickSortInternal(arr, 0, arr.length - 1);
    }

    private static void quickSortInternal(int[] arr, int left, int right) {
        if (right - left <= 64) {
            insertSortInterval(arr, left, right);
            return;
        }
        int p = partition(arr, left, right);
        quickSortInternal(arr, left, p - 1);
        quickSortInternal(arr, p + 1, right);
    }

    //二路快排,左半部分小于pivot,右半部分大于等于pivot
    private static int partition(int[] arr, int left, int right) {
        int randomIndex = ThreadLocalRandom.current().nextInt(left, right + 1);
        swap(arr, left, randomIndex);
        int pivot = arr[left];
        // arr[l + 1..j] < pivot
        int j = left;
        // arr[j + 1..i) >= pivot
        for (int i = left + 1; i <= right; i++) {
            if (arr[i] < pivot) {
                swap(arr, j + 1, i);
                j++;
            }
        }
        swap(arr, j, left);
        return j;
    }

3.优化思路

当我们出现大量相同元素的时候,这个时候二路快排的其实效率并不是很快,因为要进行很多次无用的递归处理,这个时候我们是否可以考虑单独分成一个分区是等于pivot的呢?确实是可以实现的,这个时候我们采用三路快排的方式,可以很大程度上解决我们所说的问题

五.三路快排

1.基本思路

三路快排的实现思路和二路快排的实现思路基本一样,只不过多维护一个等于pivot的区间,这样我们进行递归的时候,就没必要递归的进行等于pivot的区间部分,大大提高了快速排序的效率.

以下为三路快排分区的格局:

维护小于pivot的区间为[left,lt],维护等于pivot的区间为[lt+1,i-1],维护大于pivot的区间为[gt,right]. i表示正在遍历到的位置索引,当i>=gt的时候遍历结束白色的区域表示未遍历到的区域.

三路快排有三种情况需要考虑

1.当arr[i]>pivot的时候,我们可以将arr[i]与arr[lt+1]进行交换,并且将i++,这个时候小于pivot的区间增加

2.当arr[i]==pivot的时候,我们直接将i++即可,这个时候等于pivot的区间增加,处理结束

3.当arr[i]>pivot的时候,我们可以将arr[i]与arr[gt-1]进行交换,并且gt--,表示大于pivot的区间增加.这个时候i不需要增加,因为交换的区域是白色的区域,没有遍历到的,我们需要下一次进行判断

 最终当i==gt的时候循环结束,我们将arr[left]和arr[lt]的元素进行交换

递归的条件现在为left-->lt-1      gt-->right

2.代码实现

    //三路快排(对于处理含有重复元素的数组很有效)
    public static void quickSort3(int[] arr) {
        quickSort3Internal(arr, 0, arr.length - 1);
    }

    private static void quickSort3Internal(int[] arr, int left, int right) {
        if (right - left <= 64) {
            insertSortInterval(arr, left, right);
            return;
        }
        int radomIndex = ThreadLocalRandom.current().nextInt(left, right + 1);
        //此时left位置为中轴值
        swap(arr, left, radomIndex);
        int pivot = arr[left];
        //区间[left+1,lt]的值<pivot
        int lt = left;
        //区间[gt,right]的值>pivot
        int gt = right + 1;
        //区间(lt,gt)的元素==pivot
        //目前遍历到的位置
        int i = left + 1;
        //终止条件i与gt重合
        while (i < gt) {
            if (arr[i] < pivot) {
                swap(arr, i, lt + 1);
                lt++;
                i++;
            } else if (arr[i] > pivot) {
                swap(arr, i, gt - 1);
                gt--;
            } else {//相等的情况
                i++;

            }
        }
        swap(arr, left, lt);
        quickSort3Internal(arr, left, lt - 1);
        quickSort3Internal(arr, gt, right);

    }

六.非递归快排的实现

1.思路分析

其实非递归的快排也是很好实现的,我们仔细观察之前的代码可以发现,其实快速排序的代码是很像二叉树的前序遍历的,都是先处理,然后向左进行递归处理,向右进行递归处理:对二叉树递归和迭代实现不清楚的可以看这篇博客:树的遍历方式(前中后,层序遍历,递归,迭代,Morris遍历)

我们在实现迭代二叉树前序遍历的时候采用了栈这种结构,我们在这里也要借用栈来实现快速排序的非递归实现.那我们在栈中要保存什么信息呢,我们在进行递归实现快速排序的时候,每一次递归的都是left和right进行改变的递归,这里我们肯定是要保存left和right的,然后取出来left和right进行分区(partition)处理,但是我们需要注意的是保存的顺序应该时反向的,也就是先保存right,再保存left,因为栈性质:先进后出,这样才能保证和递归的顺序是一致的.

具体看代码的实现.

2.代码实现

    //非递归快排(挖坑)
    public static void quickSortNoRecursion(int[] arr) {
        LinkedList<Integer> stack = new LinkedList<>();
        stack.push(arr.length - 1);
        stack.push(0);
        while (!stack.isEmpty()) {
            Integer left = stack.pop();
            Integer right = stack.pop();
            if (right - left <= 64) {
                insertSortInterval(arr, left, right);
                continue;
            }
            int pivotIndex = partitionByHole(arr, left, right);
            stack.push(right);
            stack.push(pivotIndex + 1);
            stack.push(pivotIndex - 1);
            stack.push(left);

        }


    }
    //这里使用的挖坑法快排的划分方式,也可以使用别的快排的划分方式
    private static int partitionByHole(int[] arr, int left, int right) {
        //优化,选取随机值
        int index = ThreadLocalRandom.current().nextInt(left, right + 1);
        swap(arr, index, left);
        int pivot = arr[left];
        int i = left, j = right;
        while (i < j) {
            //从右边找到比pivot小的元素
            while (i < j && arr[j] >= pivot) {
                j--;
            }
            //将这个小的元素放到左边
            arr[i] = arr[j];
            //从左边找到比pivot大的元素
            while (i < j && arr[i] <= pivot) {
                i++;
            }
            //将这个大的元素放到右边
            arr[j] = arr[i];

        }
        //最后一定是i==j退出
        arr[j] = pivot;
        return j;

    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅谈ChatGPT(人工智能)

带你了解ChatGPT 1.ChatGPT是什么2.ChatGPT的特点3.ChatGPT的用途4.ChatGPT出现给社会带来的影响5.ChatGPT存在的问题6.ChatGPT的未来发展趋势7.总结 1.ChatGPT是什么 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;是美…

如何通过开源项目搭建私有云平台--第三步:部署镜像仓库

第三步 部署镜像仓库 采用开源的harbor来进行部署&#xff0c;分别在两台服务器进行部署&#xff0c;然后实现两个镜像仓库数据同步 具体部署环境如下&#xff1a; 10.10.10.3 主harbor 操作系统&#xff1a; centos 8 10.10.10.4 备用harbor 操作系统&#xff1a;cen…

【使用者手册】手动改善IntelliJ IDEA和Scala插件性能

IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是超常的。 在…

解决webassembly pthread 子线程调用主线程js问题

解决webassembly pthread 子线程调用主线程js问题 背景&#xff1a; web端项目做了一段时间后&#xff0c;我们需求是加载工程是异步的&#xff0c;主线程会调用wasm方法&#xff0c;wasm内部用pthread创建出来线程&#xff0c;然后在这个线程里边处理任务&#xff0c;处理完…

园区智慧导览地图软件,智慧工厂导航定位怎么解决方案的

智慧工厂导航定位怎么解决方案的地图新基建是行业的核心数字基础需求之一&#xff0c;行业内中已构建了较为完整的城市级地理信息系统。园区管理涉及众多方面&#xff0c;因此园区的智慧信息化建设至关重要&#xff0c;需求越来越广泛。在智慧园区中&#xff0c;基于园区的电子…

【C++类和对象】类和对象(上){初识面向对象,类的引入,类的定义,类的访问限定符,封装,类的作用域,类的实例化,类对象模型,this指针}

一、面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间的交互完成。…

瑞吉外卖:项目介绍和环境搭建

文章目录 软件开发基础软件开发流程角色分工软件环境 瑞吉外卖项目介绍项目介绍开发流程技术选型功能架构角色 环境搭建 软件开发基础 软件开发流程 需求分析&#xff1a;产品原型&#xff08;大体结构、页面、功能等&#xff09;和需求规格说明书设计&#xff1a;产品文档、…

TCP/UDP的头部字段细节

目录 TCP头部字段 一、源端口目的端口&#xff08;各占2字节&#xff09; 二、序列号&#xff08;4字节&#xff09; 三、确认号&#xff08;4字节&#xff09; 四、数据偏移&#xff08;4位&#xff09; 五、保留位&#xff08;6位&#xff09; 六、六个控制位&#xff…

『pyqt5 从0基础开始项目实战』09.本地数据配置文件的保存与读取之txt类型(保姆级图文)

目录 导包和框架代码绑定按钮点击事件在dialog中编写代理配置弹窗UI和功能实现代理配置弹窗UI方法完整代码main.pythreads.pydialog.py 总结 欢迎关注 『pyqt5 从0基础开始项目实战』 专栏&#xff0c;持续更新中 欢迎关注 『pyqt5 从0基础开始项目实战』 专栏&#xff0c;持续…

Jenkins 实现自动化部署

安装 windows下安装&#xff1a;https://blog.csdn.net/u014641168/article/details/130286547 linux下安装&#xff1a;https://blog.csdn.net/u014641168/article/details/130282439 Jenkins支持JDK1.8对应版本说明&#xff1a;https://blog.csdn.net/u014641168/article/…

STM32 学习笔记_3 程序编写基础;arm 内核架构

程序编写基础 Keil 编辑器设置 抛开 tab 和空格哪个好看不谈&#xff0c;不同编译器设置格式不同&#xff0c;空格比较保险。 用户关键字&#xff1a;打出来的时候会高亮。 代码提示&#xff1a;&#xff08;symbols after 是几个字符后开始提示关键字的意思&#xff09; 以上…

6、索引的数据结构

3.3 常见的索引概念 索引按照物理实现方式&#xff0c;索引可以分为 2 种&#xff1a;聚簇和非聚簇索引 1、聚簇索引 5、索引的代价 空间上的代价 每建立一个索引都要为它建立一棵B树&#xff0c;每一棵B树的每一个节点都是一个数据页&#xff0c;一个页默认会占用 16KB 的存…

jsp827+java心理测评管理系统dzkfB4程序j2EE+mysql

现代社会的大学生是21世纪的主人&#xff0c;他们不仅需要具有为社会做贡献的真才实学&#xff0c;更需要健康的心理。大学生的身心健康&#xff0c;人格健全和谐发展是他们学习的需要&#xff0c;是社会对未来参与者素质的要求。 1&#xff0e;系统登录&#xff1a;系统登录是…

thinkphp:插入数据到数组

一、插入数据到数组首位&#xff08;array_unshift&#xff09;&#xff0c;这里全用空值进行插入 $array [a,b,c]; //在首位加入一个空值 array_unshift($array ,); //将数组输出 print_r($array); 输出结果&#xff1a; 二、插入数据到数组末尾&#xff08;array_push&…

docker安装kafka,并集成springboot进行测试

大家好&#xff0c;今天我们开始学习kafka中间件&#xff0c;今天我们改变一下策略&#xff0c;不刷视频学习&#xff0c;改为实践学习&#xff0c;在网上找一些案例功能去做&#xff0c;来达到学习实践的目的。 首先&#xff0c;是安装相关组件。 1. docker安装安装 1.1 yu…

深度学习中的各种不变性

不变性 平移不变性&#xff08;Translation Invariance&#xff09;旋转不变性&#xff08;Ratation Invariance&#xff09;尺度不变性&#xff08;Size Invariance&#xff09;光照不变性&#xff08;Illumination Invariance&#xff09;仿射不变性&#xff08;Affine Invar…

PS学习记录-图像【像素】与【分辨率】的说明

我们经常能在图片的属性中看到 1920像素x1080像素 &#xff08;老司机在视频文件中也经常看到~&#xff09; 这就是我们常说的图片分辨率&#xff0c;以下是我学习整理的关于像素、分辨率的资料。 注意&#xff1a; 图像分辨率是针对【位图】的&#xff0c;图片分辨率决定了…

记录-JS简单实现购物车图片局部放大预览效果

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 一、实现效果 二、代码实现 代码不多&#xff0c;先看一下 HTML 里面结构很简单&#xff0c;初始化 MagnifyingGlass 对象来关联一个 IMG 标签来实现放大。 <!DOCTYPE html> <html> <h…

做一个网站需要多少个技术人员?

作为互联网从业者&#xff0c;这么多年来经常会碰到一个灵魂拷问&#xff0c;那就是“为什么一个网站需要那么多技术人员&#xff1f;”&#xff0c;尤其是提问者如果再追问一下“听说几个相关专业的学生一个课程的作业就是开发一个网站或者app&#xff0c;那为什么现在主流的网…

C++ | 认识标准库string和vector

本文概要 本篇文章主要介绍C的标准库类型string和vector&#xff0c;文中描述和代码示例很详细&#xff0c;看完即可掌握&#xff0c;感兴趣的小伙伴快来一起学习吧。 &#x1f31f;&#x1f31f;&#x1f31f;个人简介 &#x1f31f;&#x1f31f;&#x1f31f; ☀️大家好&a…