【排序】快速排序

news2025/1/21 15:32:47

基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

快速排序分为三种方法:

  • hoare法
  • 挖坑法
  • 前后指针法

而其又可以使用递归和非递归来实现,接下来将依次演示每种方法:

hoare法

单趟动图演示:

在这里插入图片描述

hoare法的快排分为以下步骤:

  1. 选出一个key,一般是第一个数,或者是最后一个数。
  2. 定义变量L和R,L从左走,R从右走。
  3. R先向前走,找到比key小的位置停下,再让L向后走,找到比key大的值停下。
  4. 交换L和R代表的数值。
  5. 继续遍历,同样让R先走,L后走,同上规则。
  6. 当L和R相遇的时候,把相遇位置的值与key位置的值交换,结束。

排完一趟要求(数据特点)如下:

  • 左边的值都比key小。
  • 右边的值都比key大。

那我们在排序过程中如何保证相遇位置的值比key小呢?

  • 不需要保证,此算法思想(右边先走)足矣解决。注意看本算法思想,它明确了每次让R先走,R找到比key小的值之后才会停下来,这时候才轮到L走,L要么找不到比key大的值就一直走直至相遇R,此时正好满足小于key,要么L找到比key大,交换L和R的值,但随后,R又会继续向前走,一直走,最坏刚好遇到L,因为L先前和R已经换过一次,也就是说这个L的值一定是比key小的,那么同样交换key的值,综上,此算法思想足矣解决。

如若key为最右边的值呢?排完一趟如何?

  • 思想和上面一样,唯一要改变的是此时是L先走,R后走,其余没有变。

动图演示:

在这里插入图片描述

  • 接下来,就先写下单趟排序:
//快排单趟排序
int PartSort(int* a, int left, int right)
{
	int keyi = left; //选左边作key
	while (left < right)
	{
		//右边先走,找小
		while (left < right && a[right] >= a[keyi]) //防止right找不到比keyi小的值直接飙出去,要加上left < right
		{
			right--;
		}
		//右边找到后,左边再走,找大
		while (left < right && a[left] <= a[keyi]) //同上,也要加上left < right
		{
			left++;
		}
		//右边找到小,左边找到大,就交换
		Swap(&a[left], &a[right]);
	}
	//此时left和right相遇,交换与key的值
	Swap(a[keyi], &a[left]);
	return left;
}
  • 写好了单趟排序,就要进行整体排序:

仔细观察上述单趟排序,有没有发现排完后,key已经排到了正确的位置,因为其左边的值均小于key,而右边的值均大于key,此时key的位置就是最终排序好后应该在的位置。那么如果左边有序,右边有序,那么整体就有序了,只需要用到递归+分治的思想即可。

  • 画图演示:

在这里插入图片描述

  • 总代码如下:
//hoare
//快排单趟排序
int PartSort(int* a, int left, int right)
{
	int keyi = left; //选左边作key
	while (left < right)
	{
		//右边先走,找小
		while (left < right && a[right] >= a[keyi]) //防止right找不到比keyi小的值直接飙出去,要加上left < right
		{
			right--;
		}
		//右边找到后,左边再走,找大
		while (left < right && a[left] <= a[keyi]) //同上,也要加上left < right
		{
			left++;
		}
		//右边找到小,左边找到大,就交换
		Swap(&a[left], &a[right]);
	}
	//此时left和right相遇,交换与key的值
	Swap(&a[keyi], &a[left]);
	return left;
}
 
//快速排序
void QuickSort(int* a, int begin, int end)
{
	//子区间相等只有一个值或者不存在那么就是递归结束的子问题
	if (begin >= end)
	{
		return;
	}
	int keyi = PartSort(a, begin, end);
	//分成左右两段区间递归
	// [begin, keyi-1] 和 [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

挖坑法

  • 动图演示单趟挖坑:

在这里插入图片描述

挖坑法的步骤如下:

  1. 把最左边的位置用key保存起来,此位置形成坑位。
  2. 定义变量L和R分别置于最左和最右。
  3. 让R先向前走,找到比key小的位置停下。
  4. 找到后,将该值放入坑位,自己形成新的坑位。
  5. 再让L向后走,找比key大的位置停下。
  6. 找到后,将该值放入坑位,自己形成新的坑位。
  7. 再让R走……。
  8. 当L和R相遇时,把key的值放到坑位,结束。

挖坑法相较于上面的hoare法并没有优化,本质上也没有区别,但是其思想更好理解:

  1. 不需要理解为什么最终相遇位置比key小。
  2. 不需要理解为什么左边做key,右边先走。
  • 总代码如下:
//挖坑法
int PartSort2(int* a, int left, int right)
{
	//把最左边的值用key保存起来
	int key = a[left]; 
	//把left位置设为坑位pit
	int pit = left;
	while (left < right) //当left小于right时就继续
	{
		//右边先走,找小于key的值
		while (left < right && a[right] >= key)
		{
			right--; //如若right的值>=key的值就继续
		}
		//找到小于key的值时就把此位置赋到坑位,并把自己置为新的坑位
		a[pit] = a[right];
		pit = right;
 
		//左边走,找大于key的值
		while (left < right && a[left] <= key)
		{
			left++;
		}
		//找到大于key的值就把此位置赋到坑位,并把自己置为新的坑位
		a[pit] = a[left];
		pit = left;
	}
	//此时L和R相遇,将key赋到坑位
	a[pit] = key;
	return pit;
}
 
//快速排序
void QuickSort(int* a, int begin, int end)
{
	//子区间相等只有一个值或者不存在那么就是递归结束的子问题
	if (begin >= end)
	{
		return;
	}
	int keyi = PartSort2(a, begin, end);
	//分成左右两段区间递归
	// [begin, keyi-1] 和 [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

前后指针法

  • 动图演示:

在这里插入图片描述

前后指针法的步骤如下:

  1. 把第一个位置的值设为key保存起来。
  2. 定义prev指针指向第一个位置,cur指向prev后一个位置。
  3. 若cur指向的数值小于key,prev和cur均后移。
  4. 当cur指向的数据大于key时,prev不动,cur继续后移。
  5. 当cur的值小于key时,prev后移一位,交换与cur的值,cur再++。
  6. 重复上述操作,当cur越界时,交换此时的prev和key的值,结束。

总的来说,cur是在找小,找到后就++prev,prev的值无论怎么走都是小于key的值的,当cur找到大与key时,cur的后面紧挨着的prev是小于key的,接下来让cur++到小于key的值,此过程间prev始终不动,唯有cur找到了小于key的值时,让prev再++,此时的prev就是大于key的值了,仔细揣摩这句话,随后交换cur和prev的值,上述操作相当于是把小于key的值甩在左边,大于key的值甩在右边。

  • 总代码如下:
//前后指针法
int PartSort3(int* a, int left, int right)
{
	int key = left;//注意不能写成 int key = a[left]
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (a[cur] < a[key] && a[++prev] != a[cur]) 
		{
			Swap(&a[prev], &a[cur]);//在cur的值小于key的值的前提下,并且prev后一个值不等于cur的值时交换,避免了交换两个小的(虽然也可以,但是没有意义)
		}
		cur++; //如若cur的值大于key,则cur++
	}
	Swap(&a[prev], &a[key]); //此时cur越界,直接交换key与prev位置的值
	return prev;
}
 
//快速排序
void QuickSort(int* a, int begin, int end)
{
	//子区间相等只有一个值或者不存在那么就是递归结束的子问题
	if (begin >= end)
	{
		return;
	}
	int keyi = PartSort2(a, begin, end);
	//分成左右两段区间递归
	// [begin, keyi-1] 和 [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

如若把key设定为最后一个数据呢?该如何控制?

  • 总的来说有三处发生变动:
  1. cur和prev初始时的位置:先前定义的prev是第一个数据,cur是prev的后一个,而现在,cur是第一个位置,而prev是cur的前一个,相当于是初始时整体后移一位。
  2. 停止的条件: 原先的cur有效范围是整个数组,现在的cur有效范围是前n-1个数组,省去最后一个定为key的值。
  3. 交换与key的值的条件: 先前是cur越界时,直接交换prev与key的值,现在是先++prev,再交换与key的值,(因为此时的prev值依旧小于key,要++后才大于key)。

除了这三处有所变动外,别的没有什么变动,交换的过程步骤都是一样的。

  • 动图演示:

在这里插入图片描述

  • 代码如下:
//前后指针法key在右边
int PartSort3(int* a, int left, int right)
{
	int key = right;
//变动1:	int prev = left - 1;  //先前 int prev =left;   int cur = left + 1;
	int cur = left;
//变动2:	while (cur < right)  //先前 while (cur <= right) 
	{
		if (a[cur] < a[key] && a[++prev] != a[cur]) 
		{
			Swap(&a[prev], &a[cur]); 
		}
		cur++;  
	}
//变动3:	Swap(&a[++prev], &a[key]); //先前Swap(&a[prev], &a[key]);
	return prev;
}
 
//快速排序
void QuickSort(int* a, int begin, int end)
{
	//子区间相等只有一个值或者不存在那么就是递归结束的子问题
	if (begin >= end)
	{
		return;
	}
	int keyi = PartSort3(a, begin, end);
	//分成左右两段区间递归
	// [begin, keyi-1] 和 [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

快排特性总结

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序。
  2. 稳定性:不稳定。
  3. 空间复杂度:O(logN)。
  4. 时间复杂度:O(N*logN)。

快排的时间复杂度分两种情况讨论:

  1. 最好:每次选key都是中位数,通俗讲是左边一半右边一半,具体看是key的左序列长度和右序列长度相同。时间复杂度O(N*logN)
  2. 最坏:每次选出最小的或者最大的作为key。时间复杂度O(N^2)

画图分析:

在这里插入图片描述

可能有人会觉着正常的数组怎么会次次都会选出最小的或者最大的作为key呢?这也太巧合了,但是仔细想想,当数组是有序或者接近有序时,不就是最坏的情况吗?更何况如若数据量再大一点,程序很有可能会因为数据量过多而递归次数过多以至于栈溢出,

综上我们需要深思:能否针对快排最坏的情况进行优化?看下文:

快排优化

就以最坏的情况为例:

在这里插入图片描述

三数取中

对于我们自己来说,是很清楚其是有序的,可计算机并不清楚,它依旧是选取最左边或者最右边作为key,如果key不是取最小或者最大的,取出的值是介于之间的,那么情况也会好很多,至此:引出三数取中

  • 规则:

取第一个数,最后一个数,中间那个数,在这三个数中选不是最大也不是最小的那个数作为key。此法针对有序瞬间从最坏变成最好,针对随机数,那么选出来的数也同样不是最大也不是最小,同样进行了优化。
三数取中其实针对hoare法,挖坑法,前后指针法都适用,这里我们就以前后指针法示例:

  • 总代码如下:
//快排
//三数曲中优化
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) / 2; // int mid = left + (right - left) / 2
	// left  mid  right
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right]) // left < mid < right
			return mid;
		else if (a[left] < a[right]) // left < right <mid
			return right;
		else  // right < left < mid
			return left; 
	}
	else // left > mid
	{
		if (a[right] > a[left]) // right > left > mid
			return left;
		else if (a[mid] > a[right])// left > mid > right
			return mid;
		else // left > right > mid
			return right;
	}
}
 
//前后指针法
int PartSort3(int* a, int left, int right)
{
	//三数取中优化
	int midi = GetMidIndex(a, left, right);
	Swap(&a[midi], &a[left]);
 
	int key = left;//注意不能写成 int key = a[left]
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (a[cur] < a[key] && a[++prev] != a[cur]) 
		{
			Swap(&a[prev], &a[cur]); 
		}
		cur++;  
	}
	Swap(&a[prev], &a[key]);  
	return prev;
}
 
//快速排序
void QuickSort(int* a, int begin, int end)
{
	//子区间相等只有一个值或者不存在那么就是递归结束的子问题
	if (begin >= end)
	{
		return;
	}
	int keyi = PartSort3(a, begin, end);
	//分成左右两段区间递归
	// [begin, keyi-1] 和 [keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

小区间优化

假设快排每次递归的过程中,选出key,然后递归分成左边和右边,并且都是均匀的,如果是有序,每次选中间值,这个过程就像是二分,跟二叉树的样子差不多,正如上述画过的图:

在这里插入图片描述

快排递归调用的简化图其实就类似于一个二叉树,假设长度N为1000,那么递归调用就要走logN层也就是10层,假设其中一个递归到只有5个数了,那么还要递归3次,当然这只是左边的,右边还要递归3次,这么小的一块区间还要递归这么多次,小区间优化就是为了解决这一问题,针对最后的小区间进行其它的算法排序,就比如插入就很可以。

当递归到越小的区间时,递归次数就会越多,针对这一小区间采取插入排序更优,减少了大量的递归次数。

  • 代码如下:
//三数取中优化
int GetMidIndex(int* a, int left, int right)
{
    //……
}
 
//前后指针法
int PartSort3(int* a, int left, int right)
{
	//三数取中优化
	int midi = GetMidIndex(a, left, right);
	Swap(&a[midi], &a[left]);
    //……
}
 
//小区间优化
void QuickSort2(int* a, int begin, int end)
{
	//子区间相等只有一个值或者不存在那么就是递归结束的子问题
	if (begin >= end)
	{
		return;
	}
	//小区间直接插入排序控制有序
	if (end - begin + 1 <= 10)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort3(a, begin, end);
		// [begin, keyi-1] 和 [keyi+1, end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

快排非递归

先前的学习中,我们的快排都是用递归来实现的,但是要知道:递归也是有缺陷的。如果深度过大,可能会导致栈溢出,即使你用了快排优化可能也无法解决此问题,所以我们引出非递归的版本来解决栈溢出问题。

  • 规则:

在快排递归的过程中是要建立栈帧的,仔细看看每次递归时传的参数,有begin和end,其递归过程存储的是排序过程中要控制的区间,那我们用非递归模拟递归的过程中也要按照它这个存储方式进行,这就需要借助栈了,跟上篇博文的层序遍历一样利用到了栈。

在这里插入图片描述

  • 代码如下:
//快排非递归
void QuickSort3(int* a, int begin, int end)
{
	ST st;
	StackInit(&st);
	//先把第一块区间入栈
	StackPush(&st, begin);
	StackPush(&st, end);
	while (!StackEmpty(&st)) //栈不为空就继续
	{
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);
 
		//使用前后指针法进行排序
		int keyi = PartSort3(a, left, right); // keyi已经到了正确位置
 
		// [left, kryi-1]  [keyi+1, right]
		if (left < keyi - 1)//如若左区间不只一个数就入栈
		{
			StackPush(&st, left);
			StackPush(&st, keyi - 1);
		}
		if (keyi + 1 < right)//若右区间不只一个就入栈
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, right);
		}
	}
	StackDestory(&st);
}

上述代码恰好巧妙的实现了递归的过程,仔细观察上述代码,一开始我们入栈了下标为begin和end的值,如下:

在这里插入图片描述

随后,取出这两个值,并用right和left分别保存起来,随后对区间[left,right]这块区间进行单趟排序,取出keyi的值为5,此时a[keyi]也就排到了正确的位置了,接下来就是效仿递归的关键了,以keyi为分界线,将数组分为两块区间:【left,keyi-1】和【keyi+1,right】,此时再把这两块区间的入栈:

在这里插入图片描述

接下来进入第二趟while循环,同样是再次出栈里的两个数据6和9,并再次传入单趟排序,算出keyi的值为8,也就意味着a[keyi]到了正确的位置,再以keyi为分界线,将右区间的数组分为【6,7】和【9,8】以此类推……一直排下去。

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

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

相关文章

视频讲解:优化柱状图

你好&#xff0c;我是郭震 AI数据可视化 第三集&#xff1a;美化柱状图&#xff0c;完整视频如下所示&#xff1a; 美化后效果前后对比&#xff0c;前&#xff1a; 后&#xff1a; 附完整案例源码&#xff1a; util.py文件 import platformdef get_os():os_name platform.syst…

STM32 寄存器操作 GPIO 与中断

一、如何使用stm32寄存器点灯&#xff1f; 1.1 寄存器映射表 寄存器本质就是一个开关&#xff0c;当我们把芯片寄存器配置指定的状态时即可使用芯片的硬件能力。 寄存器映射表则是开关的地址说明。对于我们希望点亮 GPIO_B 的一个灯来说&#xff0c;需要关注以下的两个寄存器…

Linux---网络套接字

端口号 端口号 端口号是一个2字节16位的整数; 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理; IP地址 端口号能够标识网络上的某一台主机的某一个进程; 一个端口号只能被一个进程占用 在公网上&#xff0c;IP地址能表示唯一的一台主机&…

大模型激活函数知识

FFN块 计算公式 在Transformer模型中&#xff0c;FFN&#xff08;Feed-Forward Network&#xff09;块通常指的是在编码器&#xff08;Encoder&#xff09;和解码器&#xff08;Decoder&#xff09;中的一个全连接前馈网络子结构。FFN块位于自注意力层&#xff08;Self-Attent…

C语言第二十三弹---指针(七)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 指针 1、sizeof和strlen的对比 1.1、sizeof 1.2、strlen 1.3、sizeof 和 strlen的对比 2、数组和指针笔试题解析 2.1、⼀维数组 2.2、二维数组 总结 1、si…

Java 基于微信小程序的私家车位共享系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

WebSocket原理详解

目录 1.引言 1.1.使用HTTP不断轮询 1.2.长轮询 2.websocket 2.1.概述 2.2.websocket建立过程 2.3.抓包分析 2.4.websocket的消息格式 3.使用场景 4.总结 1.引言 平时我们打开网页&#xff0c;比如购物网站某宝。都是点一下列表商品&#xff0c;跳转一下网页就到了商品…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Marquee组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Marquee组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Marquee组件 跑马灯组件&#xff0c;用于滚动展示一段单行文本&#xff0c;仅当…

【Effective Objective - C 2.0】——读书笔记(三)

文章目录 十五、用前缀避免命名空间冲突十六、提供全能初始化方法十七、实现description方法十八、尽量使用不可变对象十九、使用清晰而协调的命名方式二十、为私有方法名加前缀二十一、理解Objective-C错误模型二十二、理解NSCopying协议 十五、用前缀避免命名空间冲突 OC语言…

WSL外部SSH连接有效方法

前言 wsl作为windows下使用linux平台有效的手段之一&#xff0c;本文可以让win作为工作站&#xff0c;外部系统用来连接win下的wsl系统。 自动启动服务脚本 https://zhuanlan.zhihu.com/p/47733615 开机自启端口转发 wslname "Ubuntu-20.04" 要转发端口的Linux…

不懂编程?节点包来凑——Dynamo常用节点包推荐(上)

由于篇幅有限&#xff0c;本次文章我们分上、下两篇&#xff0c;来分享给大家。 Dynamo作为一款辅助三维设计工具&#xff0c;他可以通过图形化的编程&#xff0c;帮我们解决很多在设计或者建模过程中遇到的小问题&#xff1b;同时他作为一款可视化编程软件&#xff0c;学起来…

Spring Boot 笔记 006 创建接口_注册

1.1 由于返回数据都是以下这种格式&#xff0c;那么久再编写一个result实体类 报错了&#xff0c;原因是没有构造方法 可以使用lombok的注解自动生成&#xff0c;添加无参的构造器和全参的构造器 package com.geji.pojo;import lombok.AllArgsConstructor; import lombok.NoArg…

【C语言】【力扣】7.整数反转和9.回文数

一、整数反转 1.1 个人思考过程 初解&#xff1a;出现ERROR&#xff0c;数据溢出的情况下应该返回0。&#xff08;错误&#xff09; int reverse(int x){int y0;while(x!0){yy*10x%10;x/10; }return y; } 再解&#xff1a;加上数据溢出判断条件。&#xff08;正确&#…

JS逆向进阶篇【去哪儿旅行登录】【上篇】

目标url: aHR0cHM6Ly91c2VyLnF1bmFyLmNvbS9wYXNzcG9ydC9sb2dpbi5qc3A 实现难点&#xff1a; 逆向滑块请求发送短信登录 目录 每篇前言&#xff1a;0、前置技术栈&#xff08;1&#xff09;JS实现页面滑动&#xff08;2&#xff09;JS实现记录滑动轨迹&#xff08;3&#xff…

二、ClickHouse简介

ClickHouse简介 前言一、行式存储二、DBMS功能三、多样化引擎四、高吞吐写入能力五、数据分区与线程级并行六、场景七、特定版本 前言 ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的列式存储数据库&#xff08;DBMS&#xff09;&#xff0c;使用 C 语言编写&#xff0c;主要…

2024年教师资格证认定报名完整流程

&#x1f49a;网上报名流程概览 一、进入教资认定网报入口&#xff1b; 二、进行实名核验&#xff1b; 三、申请网报时间查询&#xff1b; 四、个人信息维护&#xff1b; 五、认定申请报名&#xff1b; &#x1f49a;认定所需材料 1、 身份证&#xff1b; 2 、户口本/居住证…

【Java程序设计】【C00249】基于Springboot的私人健身与教练预约管理系统(有论文)

基于Springboot的私人健身与教练预约管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的私人健身与教练预约管理系统 本系统分为系统功能模块、管理员功能模块、教练功能模块以及用户功能模块。 系统功能模…

计算机网络——08应用层原理

应用层原理 创建一个新的网络 编程 在不同的端系统上运行通过网络基础设施提供的服务&#xff0c;应用进程批次通信如Web Web服务器软件与浏览器软件通信 网络核心中没有应用层软件 网络核心没有应用层功能网络应用只能在端系统上存在 快速网络应用开发和部署 网络应用…

ChatGPT4 教你如何完成SQL的实践应用

对数据库的各项应用与操作都离不开SQL来对数据进行增删改查。 例如 &#xff1a; 有一张某公司职员信息表如下&#xff1a; 需求1&#xff1a;在公司职员信息表中&#xff0c;请统计各部门&#xff0c;各岗位下的员工人数。 如果这个SQL语句不会写或者不知道怎么操作可以交给…

【C基础刷题】第九讲

本系列博客为个人刷题思路分享&#xff0c;有需要借鉴即可。 1.目录大纲&#xff1a; 2.题目链接&#xff1a; 统计成绩 00&#xff1a;00&#xff1a;00⸺00&#xff1a;09&#xff1a;00题号&#xff1a;BC33 链接&#xff1a;https://www.nowcoder.com/practice/ cad8d94…