【排序算法】六、快速排序(C/C++)

「前言」文章内容是排序算法之快速排序的讲解。(所有文章已经分类好,放心食用)

「归属专栏」排序算法

「主页链接」个人主页

「笔者」枫叶先生(fy)

目录

  • 快速排序
    • 1.1 原理
    • 1.2 Hoare版本(单趟)
    • 1.3 快速排序完整代码(Hoare版)(递归实现)
    • 1.4 选择基准数key优化(三数取中)
    • 1.5 挖坑法(单趟)
    • 1.6 快速排序完整代码(挖坑法)(递归实现)
    • 1.7 前后指针版(单趟)
    • 1.8 快速排序完整代码(前后指针版)(递归实现)
    • 1.9 快速排序小区间优化
    • 1.9 快速排序非递归实现
    • 1.10 特性总结

快速排序

1.1 原理

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,一种高效的排序算法

基本思想:是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分小,然后再按照此方法对这两部分数据分别进行快速排序,以达到整个数据变成有序序列

具体步骤如下:

  1. 从数列中挑出一个元素作为基准元素
  2. 将比基准元素小的元素放在其左边,比基准元素大的元素放在其右边,基准元素所在位置即为最终位置
  3. 分别对左右两个子序列递归地进行快速排序

对于如何按照基准值将待排序列分为两个子序列(单趟),单趟常见的方式有:

  1. Hoare版本
  2. 挖坑法
  3. 前后指针法

下面先谈Hoare版本的

1.2 Hoare版本(单趟)

Hoare版本的单趟排序的动图如下:
在这里插入图片描述

Hoare版本的单趟排序的基本步骤如下:

  1. 从数组中选出一个key,一般是最左边或是最右边的,上面动图是选左边的
  2. 定义一个left: L和一个right: R。如果选的key是左边的,则需要R先走,然后L从左向右走,R从右向左走。(注意:若选择最右边的数据作为key,则需要L先走
  3. 选左:
  4. 选取最左边的基准值作为key,L和R的任务:(1)L:找比key大的;(2)R:找比key小的
  5. 选取最左边的基准值作为key 在走的过程中,若R遇到小于key的数,则停下,L开始走,直到L遇到一个大于key的数时,将L和R的内容交换,R再次开始走,如此进行下去,直到L和R最终相遇,相遇结束。此时将相遇点的内容与key交换即可
  6. 选右:
  7. 选取最左边的基准值作为key,L和R的任务:(1)L:找比key小的;(2)R:找比key大的
  8. 选取最右边的基准值作为key 在走的过程中,若L遇到小于key的数,则停下,R开始走,直到R遇到一个大于key的数时,将L和R的内容交换,L再次开始走,如此进行下去,直到L和R最终相遇,相遇结束。此时将相遇点的内容与key交换即可

经过一次单趟排序,最终使得key左边的数据全部都小于keykey右边的数据全部都大于key,并且key所在的位置就是最终的位置

相遇位置的值如何保证比key小,相遇点直接和key交换会不会出错,会不会把较大的值换到了左边?

  • 如果选取最左边的基准值作为key,R先走保证相遇位置的值保证比key
  • 如果R停下来,L走,L撞到R(相遇),相遇位置的值必定比key
  • 如果L停下来,R走,R撞到L(相遇),相遇位置的值必定比key
  • 同理,如果选取最右边的基准值作为key,L先走保证相遇位置的值保证比key

1.3 快速排序完整代码(Hoare版)(递归实现)

首先经过一次单趟排序,然后将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,停止操作,此时序列(或者子序列)已经有序

排序步骤演示:

类似于二叉树的前序遍历
在这里插入图片描述
代码如下(递归,Hoare版):(升序)

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// Hoare版本(单趟)
int PartQuickSort(int* arr, int left, int right)
{int keyi = left; // key的下标,选左边为keywhile (left < right) // left == right即相遇{// right先走,找比key小的值while (left < right && arr[right] >= arr[keyi]){--right;}// left后走,找比key大的值while (left < right && arr[left] <= arr[keyi]){++left;}// 找到一组值,交换:交换arr[left] 和 arr[right]if (left < right){Swap(&arr[left], &arr[right]);}}Swap(&arr[keyi], &arr[left]); // 交换 keyi 和相遇点位置的值,left==rightreturn left; // 返回相遇点的下标
}// 快速排序
void QuickSort(int* arr, int left, int right)
{// 1、区间不存在(left > right) 2、只有一个数数不需要再处理(left == right)if (left >= right){return;}// 单趟排序并获取基准值int keyi = PartQuickSort(arr, left, right);// 递归处理每一个子序列(左、右)QuickSort(arr, left , keyi - 1); // keyi 左序列进行排序QuickSort(arr, keyi + 1, right); // keyi 右序列进行排序
}

1.4 选择基准数key优化(三数取中)

快速排序的时间复杂度是O(NlogN),是我们在理想情况下计算的结果

在理想情况下,我们每次进行完单趟排序后,key的左序列与右序列的长度都相同
在这里插入图片描述
若每趟排序所选的key都正好是该序列的中间值,即单趟排序结束后key位于序列正中间,那么快速排序的时间复杂度就是O(NlogN)

可是谁能保证你每次选取的key都是正中间的那个数呢?

例如,以下数组:
在这里插入图片描述
如果基准值key是数组中最大或最小的数值(假设选左边),则快速排序的递归深度会非常深,排序效率会很低

若是一个有序数组使用快速排序,这种情况下,快速排序的时间复杂度退化为O(N^2)
在这里插入图片描述
对快速排序效率影响最大的就是选取的key,若选取的key越接近中间位置,则则效率越高

为了避免这种极端情况的发生,于是出现了三数取中

三数取中

三数取中的方法是从待排序数组中选择三个元素,分别是左端、右端和中间位置的元素,然后取这三个元素的中间值作为基准元素

代码如下:

// 三数取中
int GetMidIndex(int* arr, int left, int right)
{int mid = left + (right - left) / 2;if (arr[left] < arr[mid]) {if (arr[mid] < arr[right]) {return mid; // [mid]是中间值}else if (arr[left] > arr[right]){return left;}else{return right;}}else  // arr[left] >= arr[mid]{if (arr[mid] > arr[right]){return mid;}else if (arr[left] < arr[right]){return left;}else{return right;}}
}

需要注意,当大小居中的数不在序列的最左或是最右端时,我们不是就以居中数的位置作为key的位置,而是将key的值与最左端的值进行交换,这样key就还是位于最左端了,所写代码就无需改变,而只需在单趟排序代码开头加上以下两句代码即可:

如下:

// Hoare版本(单趟)
int PartQuickSort(int* arr, int left, int right)
{// 三数取中int midi = GetMidIndex(arr, left, right);Swap(&arr[left], &arr[midi]);int keyi = left; // key的下标,选左边为key// ...return left; // 返回相遇点的下标
}

1.5 挖坑法(单趟)

挖坑法是基于Hoare版本改良的(Hoare版本代码细节太多)

挖坑法的单趟排序的动图如下:
在这里插入图片描述
挖坑法的单趟排序的基本步骤如下:

  1. 选出一个数据(上图选最左为坑)存放在key变量中,在该数据位置形成一个坑
  2. 定义一个left: L和一个right: R,L从左向右走,R从右向左走(若在最左边挖坑,则需要R先走;若在最右边挖坑,则需要L先走)
  3. 选取最左边的作为坑位为例:
  4. L和R的任务:(1)L:找比key大的;(2)R:找比key小的
  5. R先走,在走的过程中,若R遇到小于key的数,则将该数抛入坑位,并在此处形成一个新的坑位,这时L再向后走,若遇到大于key的数,则将其抛入坑位,又形成一个坑位,如此循环下去,直到最终L和R相遇,这时将key抛入坑位即可

经过一次单趟排序,最终也使得key左边的数据全部都小于keykey右边的数据全部都大于key,并且key所在的位置就是最终的位置

挖坑法代码如下:(升序)

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 挖坑法(单趟)
int PartQuickSort2(int* arr, int left, int right)
{// 三数取中int midi = GetMidIndex(arr, left, right);Swap(&arr[left], &arr[midi]);int key = arr[left]; // 在最左形成一个坑位int hole = left; // 坑的下标while (left < right){// right先走,找比key小的while (left < right && arr[right] >= key){--right;}arr[hole] = arr[right]; // 填坑hole = right;// left后走,找比key大的while (left < right && arr[left] <= key){++left;}arr[hole] = arr[left]; // 填坑hole = left;}arr[hole] = key;return hole;
}

1.6 快速排序完整代码(挖坑法)(递归实现)

代码如下(递归,挖坑法):(升序)

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 挖坑法(单趟)
int PartQuickSort2(int* arr, int left, int right)
{// 三数取中int midi = GetMidIndex(arr, left, right);Swap(&arr[left], &arr[midi]);int key = arr[left]; // 在最左形成一个坑位int hole = left; // 坑的下标while (left < right){// right先走,找比key小的while (left < right && arr[right] >= key){--right;}arr[hole] = arr[right]; // 填坑hole = right;// left后走,找比key大的while (left < right && arr[left] <= key){++left;}arr[hole] = arr[left]; // 填坑hole = left;}arr[hole] = key;return hole; // 返回key的下标
}// 快速排序
void QuickSort(int* arr, int left, int right)
{// 1、区间不存在(left > right) 2、只有一个数数不需要再处理(left == right)if (left >= right){return;}// 单趟排序并获取基准值int keyi = PartQuickSort2(arr, left, right);// 递归处理每一个子序列(左、右)QuickSort(arr, left , keyi - 1); // keyi 左序列进行排序QuickSort(arr, keyi + 1, right); // keyi 右序列进行排序
}

1.7 前后指针版(单趟)

前后指针版的单趟排序的动图如下:
在这里插入图片描述
前后指针法的单趟排序的基本步骤如下:

  1. 选出一个key,最左边或是最右边的(上图选最左)
  2. 开始时,prev指向序列的左边,cur指向prev+1的位置
  3. 情况1:若cur指向的内容小于key,则prev向右后移动一位,然后交换prevcur指针指向的内容,然后cur指针++
  4. 情况2:若cur指向的内容大于key,则cur指针直接++。如此进行下去,直到cur指针越界,此时将keyprev指针指向的内容交换即可

前后指针版代码如下:(升序)

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 前后指针版(单趟)
int PartQuickSort3(int* arr, int left, int right)
{// 三数取中int midi = GetMidIndex(arr, left, right);Swap(&arr[left], &arr[midi]);int keyi = left;int prev = left;int cur = left + 1;while (cur <= right){if (arr[cur] < arr[keyi]) // cur指向的内容小于key{++prev;if(prev != cur)Swap(&arr[prev], &arr[cur]);}++cur;}Swap(&arr[keyi], &arr[prev]);return prev; // 返回key当前的下标
}

1.8 快速排序完整代码(前后指针版)(递归实现)

代码如下(递归,前后指针版):(升序)

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 前后指针版(单趟)
int PartQuickSort3(int* arr, int left, int right)
{// 三数取中int midi = GetMidIndex(arr, left, right);Swap(&arr[left], &arr[midi]);int keyi = left;int prev = left;int cur = left + 1;while (cur <= right){if (arr[cur] < arr[keyi]) // cur指向的内容小于key{++prev;if(prev != cur)Swap(&arr[prev], &arr[cur]);}++cur;}Swap(&arr[keyi], &arr[prev]);return prev; // 返回key当前的下标
}// 快速排序
void QuickSort(int* arr, int left, int right)
{// 1、区间不存在(left > right) 2、只有一个数数不需要再处理(left == right)if (left >= right){return;}// 单趟排序并获取基准值int keyi = PartQuickSort3(arr, left, right);// 递归处理每一个子序列(左、右)QuickSort(arr, left , keyi - 1); // keyi 左序列进行排序QuickSort(arr, keyi + 1, right); // keyi 右序列进行排序
}

1.9 快速排序小区间优化

快速排序的递归类似于二叉树的形式,深度越深待排数组的长度越短,但是数量也越多,调用函数的次数就越多,开辟函数栈帧的消耗越大,导致效率下降(每一层的递归次数会以2倍的形式快速增长,后三层的递归次数占据了总递归次数的70%以上)

优化的方式就是设置一个排序序列的长度大小,若是数组长度大于这个指定的数,则调用快速排序,若是数组长度小于这个指定的数,不再调用快速排序,而是调用插入排序或者其他排序

这个数可自行调整,代码如下:

// 快速排序(优化)
void QuickSort2(int* arr, int left, int right)
{// 1、区间不存在(left > right) 2、只有一个数数不需要再处理(left == right)if (left >= right){return;}if (right - left + 1 > 8) // 可自行调整{// 单趟排序并获取基准值int keyi = PartQuickSort3(arr, left, right);// 递归处理每一个子序列(左、右)QuickSort(arr, left, keyi - 1); // keyi 左序列进行排序QuickSort(arr, keyi + 1, right); // keyi 右序列进行排序}else // 调用其他排序{InsertSort(arr + left, right - left + 1);}
}

1.9 快速排序非递归实现

将一个用递归实现的算法改为非递归时,一般需要借用一个数据结构,那就是栈

C语言的库没有封装有stack,需要自己手搓一个,C++的STL库则封装有

如果是C语言,我就不贴代码了,代码链接:stack,下面简单介绍一下手搓stack的接口

// 初始化
void StackInit(ST* ps);
// 销毁栈
void StackDestroy(ST* ps);
// 入栈
void StackPush(ST* ps, STDataType x);
// 出栈
void StackPop(ST* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(ST* ps);
// 获取栈顶元素
STDataType StackTop(ST* ps);
// 获取栈中有效元素个数
int StackSize(ST* ps);

快速排序的非递归算法基本思路:

  1. 先将待排序列的第一个元素的下标和最后一个元素的下标入栈
  2. 当栈不为空时,读取栈中的信息(一次读取两个:一个是left,另一个是right),然后调用某一版本的单趟排序,排完后获得了key的下标,然后判断key的左序列和右序列是否还需要排序,若还需要排序,就将相应序列的left和right入栈;若不需排序了(序列只有一个元素或是区间不存在),就不需要将该序列区间的信息入栈
  3. 重复执行步骤2,直到栈为空为止

C语言代码如下:(升序)

void QuickSort3(int* arr, int left, int right)
{Stack st; // 创建栈StackInit(&st); // 初始化栈StackPush(&st, left); // 待排序列的leftStackPush(&st, right); // 待排序列的rightwhile (!StackEmpty(&st)) // 栈为空结束{int right = StackTop(&st); // 读取rightStackPop(&st); // 出栈int left = StackTop(&st); // 读取leftStackPop(&st); // 出栈// 判断左右区间是否合理,若不合理则跳过本次循环if (left >= right){continue;}int keyi = PartQuickSort2(arr, left, right); // 调用单趟的方法StackPush(&st, left); // 左序列的left入栈StackPush(&st, keyi - 1); // 左序列的right入栈StackPush(&st, keyi + 1); // 右序列的left入栈StackPush(&st, right); // 右序列的right入栈}
}

C++代码如下:(升序)

void QuickSort4(int* arr, int left, int right)
{stack<int> st; // 创建栈st.push(left); // 待排序列的leftst.push(right); // 待排序列的rightwhile (!st.empty()) // 栈为空结束{int right = st.top(); // 读取rightst.pop(); // 出栈int left = st.top(); // 读取leftst.pop(); //出栈//判断左右区间是否合理,若不合理则跳过本次循环if (left >= right){continue;}int keyi = PartQuickSort2(arr, left, right); // 调用单趟的方法st.push(left); // 左序列的left入栈st.push(keyi - 1); // 左序列的right入栈st.push(keyi + 1); // 右序列的left入栈st.push(right); // 右序列的right入栈}
}

1.10 特性总结

快速排序的特性总结如下:

  • 时间复杂度:平均情况下,快速排序的时间复杂度为O(nlogn)
  • 空间复杂度:O(1)
  • 稳定性:不稳定
  • 不适用于小规模数据,适用于大规模数据的排序

--------------------- END ----------------------

「 作者 」 枫叶先生
「 更新 」 2024.1.20
「 声明 」 余之才疏学浅,故所撰文疏漏难免,或有谬误或不准确之处,敬请读者批评指正。

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

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

相关文章

Excel 根据日期按月汇总公式

Excel 根据日期按月汇总公式 数据透视表日期那一列右击&#xff0c;选择“组合”&#xff0c;步长选择“月” 参考 Excel 根据日期按月汇总公式Excel如何按着日期来做每月求和

Linux内存管理:(九)内存规整

文章说明&#xff1a; Linux内核版本&#xff1a;5.0 架构&#xff1a;ARM64 参考资料及图片来源&#xff1a;《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址&#xff1a; zhangzihengya/LinuxSourceCode_v5.0_study (github.com) 1. 引言 伙伴系统以页面为单位来管…

leetcode:每日温度---单调栈

题目&#xff1a; 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 示例&…

天龙八部资源提取工具(提取+添加+修改+查看+教程)

可以提取&#xff0c;添加&#xff0c;修改&#xff0c;查看天龙八部里面的数据。非常好用。 天龙八部资源提取工具&#xff08;提取添加修改查看教程&#xff09; 下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1XOMJ1xvsbD-UUQOv3QfHPQ?pwd0kd0 提取码&…

赛车游戏简单单车C语言版

#include<stdio.h> #include<easyx.h> #include<time.h>#define WIDTH 512 #define HEIGHT 768//定义一个汽车类 struct FCar {//坐标float x, y;// 汽车种类int type;//汽车速度float speed; };//定义全局变量 图片坐标 IMAGE BG_IMG; //背景图片坐标 float…

logstack 日志技术栈-04-opensource 开源工具 SigNoz+Graylog

3. SigNoz SigNoz 是一个日志收集和分析工具&#xff0c;可以收集和管理来自各种来源的日志、指标、跟踪和异常。 它为使用 OpenTelemetry 检测应用程序提供本机支持&#xff0c;以防止供应商锁定&#xff0c;将收集到的数据存储在 ClickHouse 中&#xff0c;然后在用户友好的…

【实战】SpringBoot自定义 starter及使用

文章目录 前言技术积累SpringBoot starter简介starter的开发步骤 实战演示自定义starter的使用写在最后 前言 各位大佬在使用springboot或者springcloud的时候都会根据需求引入各种starter&#xff0c;比如gateway、feign、web、test等等的插件。当然&#xff0c;在实际的业务…

灵活扩展:深入理解MyBatis插件机制

第1章&#xff1a;MyBatis插件的重要性 大家好&#xff0c;我是小黑&#xff0c;咱们今天要聊的是MyBatis插件&#xff0c;MyBatis&#xff0c;大家都不陌生&#xff0c;它是一个ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;让咱们在操作数据库时能更加优雅。但今…

web漏洞总结大全(基础)

前言 本文章是和cike_y师傅一起写的&#xff0c;cike_y博客&#xff1a;https://blog.csdn.net/weixin_53912233?typeblog 也欢迎大家对本文章进行补充和指正&#xff0c;共同维护这个项目&#xff0c;本文的github项目地址&#xff1a; https://github.com/baimao-box/Sum…

Haxe-UnrealEngine5

Haxe-UnrealEngine5 结论 UE C header > External/**.hx.hx > .h/.cpp&#xff0c;和 UE C 一起编译使用 hxcpp 来调试 .hx good&#xff1a; 理论上不仅限反射代码走 UE C&#xff0c;无需维护 backend&#xff0c;比如 Lua Binding理论上接近 UE C 的性能 bad&…

六、Netty核心模块组件

目录 6.1 BootStrap&#xff0c;ServerBootStrap6.2 Future&#xff0c;ChannelFuture6.3 Channel6.4 Selector6.5 ChannelHandler 以及其实现类6.6 Pipeline 和 ChannelPipeline6.7 ChannelHandlerContext6.8 ChannelOption6.9 EventLoopGroup和其实现类 NioEventLoopGroup6.1…

激光无人机打击系统——光束控制和指向系统

激光无人机&#xff08;UAV&#xff09;打击系统中的光束控制和指向系统通常包括以下几个关键组件和技术&#xff1a; 激光发射器&#xff1a;这是系统的核心&#xff0c;负责生成高能量的激光束。常用的激光类型包括固体激光器、化学激光器、光纤激光器等&#xff0c;选择取决…

微软Microsoft推出针对学生的AI练习英语口语工具”阅读教练“:Reading Coach

阅读教练官网链接&#xff1a;https://coach.microsoft.com AI工具专区&#xff1a;AI工具-喜好儿aigc 学生可以通过选择角色和设定&#xff0c;利用AI生成独特的故事&#xff0c;从而激发阅读兴趣并提高阅读流畅度。语音转文本AI能够实时分析学生的阅读流利性&#xff0c;检测…

Golang 搭建 WebSocket 应用(八) - 完整代码

本文应该是本系列文章最后一篇了&#xff0c;前面留下的一些坑可能后面会再补充一下&#xff0c;但不在本系列文章中了。 整体架构 再来回顾一下我们的整体架构&#xff1a; 在我们的 demo 中&#xff0c;包含了以下几种角色&#xff1a; 客户端&#xff1a;一般是浏览器&am…

图论:最短路(dijkstra算法、bellman算法、spfa算法、floyd算法)详细版

终于是学完了&#xff0c;这个最短路我学了好几天&#xff0c;当然也学了别的算法啦&#xff0c;也是非常的累啊。 话不多说下面看看最短路问题吧。 最短路问题是有向图&#xff0c;要求的是图中一个点到起点的距离&#xff0c;其中我们要输入点和点之间的距离&#xff0c;来求…

day01.基础知识

目录 一.函数与语句 1.1进入C 1.1.1main( )头函数 1.1.2 注释 1.1.3头文件 1.1.4预处理 1.1.5命名空间 1.1.6输入与输出 1.1.7格式化 1.2语句 1.2.1声明语句与变量 1.2.2赋值语句 1.3函数 1.3.1使用有返回值的函数 一.函数与语句 1.1进入C 1.1.1main( )头函数 …

rk1126, 实现 yolov8 目标检测

基于 RKNN 1126 实现 yolov8 目标检测 Ⓜ️ RKNN 模型转换 ONNX yolo export model./weights/yolov8s.pt formatonnx导出 RKNN 这里选择输出 concat 输入两个节点 onnx::Concat_425 和 onnx::Concat_426 from rknn.api import RKNNONNX_MODEL ./weights/yolov8s.onnxRKNN_MOD…

MySQL 索引(下)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;重拾MySQL-进阶篇 &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出现…

推荐新版AI智能聊天系统网站源码ChatGPT NineAi

Nine AI.ChatGPT是基于ChatGPT开发的一个人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过学习和理解人类的语言来进行对话&#xff0c;还能根据聊天的上下文进行互动&#xff0c;真正像人类一样来聊天交流&#xff0c;甚至能完成撰写邮件、视频脚本、文案、翻译、代…

GoZero微服务个人探究之路(七)添加中间件、自定义中间件

说在前面 官方已经自己实现了很多中间件&#xff0c;我们可以方便的直接使用&#xff0c;不用重复造轮子了 开启方式可以看官方文档 中间件 | go-zero Documentation 实现自定义的中间件 在业务逻辑中&#xff0c;我们需要实现自定义功能的中间件 ------这里我们以实现跨源…