数据结构进阶篇 之 【交换排序】(冒泡排序,快速排序递归、非递归实现)详细讲解

在这里插入图片描述
当你觉的自己不行时,你就走到斑马线上,这样你就会成为一个行人

一、交换排序

1.冒泡排序 BubbleSort

1.1 基本思想

1.2 实现原理

1.3 代码实现

1.4 冒泡排序的特性总结

2.快速排序 QuickSort

2.1 基本思想

2.2 递归实现

2.2.1 hoare版
2.2.2 前后指针版本

2.3 快速排序优化

2.3.1 随机数选key
2.3.2 三数取中选key
2.3.3 递归到小的子区间使用插入排序

2.4 非递归实现

2.5 快速排序的特性总结

二、完结撒❀

前言:

所谓交换排序,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是(以升序为例):将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–

一、冒泡排序

对于冒泡这个初学者必学的排序,我想大家应该都不陌生,现在我们以排升序为例再简单学习回顾一下,为下面快速排序做铺垫。

1.1 基本思想

冒泡排序是从头开始让两个相邻位置的数进行大小比较,不符合升序的将两者进行交换,再继续向后比较两个数据大小,反复执行一轮上述操作,便可以将数组中最大数据排到队尾,再从头进行一轮上述操作便可以将数组中第二大的数据排到数组中倒数第二个位置,反复执行n-1次即可完成排序(n为数据总个数)

1.2 实现原理

在数组arr[n]中,开始将数组arr[0]与arr[1]进行大小比较,若arr[1]<arr[0]就将两者数值进行交换后继续进行arr[1]与arr[2]的大小比较,若arr[1]>arr[0]就继续向后进行arr[1]与arr[2]的大小比较,直到比较到arr[n-1]为止,这时arr[n-1]便是数组中最大的值,再执行一轮上述操作便可以将数组中第二大的数值存放到arr[n-2]当中,反复执行n-1次便完成数组的总排序

动态图解:
在这里插入图片描述

1.3 代码实现

//冒泡排序
void BubbleSort(int* a, int n)
{
	assert(a);

	for (int t = 1; t < n; t++)
	{
		int exchang = 0;//优化
		for (int i = 0; i < n - t; i++)//优化
		{
			if (a[i] > a[i + 1])
			{
				int tmp = a[i];
				a[i] = a[i + 1];
				a[i + 1] = tmp;
				exchang = 1;
			}
		}
		if (exchang == 0)
		{
			break;
		}
	}
}

代码中对冒泡排序进行了两处优化,大家认真品味,加深理解。

1.4 冒泡排序的特性总结

1. 冒泡排序是一种非常容易理解的排序

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1)

4. 稳定性:稳定

2.快速排序

2.1 基本思想

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

2.2 递归实现

2.2.1 Hoare版

Hoare版就是Hoare大佬自己创作的最原始的快速排序。

根据上述基本思想我们可以发现快速排序递归实现与二叉树的前序遍历规则非常像,在代码的实现中我们就可参照二叉树前序递归实现快速排序代码的递归框架。

我们先认真观察下面快速排序的动态图解:
在这里插入图片描述上面1动态图解表示的是快速排序的一次单趟过程:

以数组左边第一个值为key,R先向左走找小于key的数就停下来(5),接着L向右走找大于key的值就停下来(7),再将L和R对应位置数值进行交换,接着R继续向左走找小于key的值(4),找到后停下L向右走找大于key的值(9),之后停下再将两数值进行交换,继续重复上述操作直到L与R相遇,L与R相遇之后再将相遇下标对应的值与key下标对应的值进行交换,这样就完成了一次单趟快速排序,此时key的右边都为小于key的值,右边都是大于key的值。

快速排序单趟实现代码

void Swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}

//快速排序(单趟)
void PartSort(int* a, int left, int right)
{
	assert(a);

	int keyi = left;

	while (left < right)
	{
		while (left<right && a[right] >= a[keyi])
		{
			--right;
		}

		while (left<right && a[left] <= a[keyi])
		{
			++left;
		}

		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[keyi]);
	keyi = left;
}

这里强调一下,一趟快速排序开始必须先让R往左边走,保证在最后L与R相遇时下标对应的值小于key下标对应的值

那么我们单趟排序便实现完成,下面就要开始展开研究递归问题。

递归就分为:1.递归子问题 2.最终子问题

我们对一串数组进行一次单趟的快速排序之后,数组并没有完全实现有序,还要“分割”key左右两边对其再分别进行快速排序,再将“分割”的一段再按照快排后key的位置在进行分割再进行单趟排序,直到分割为一个数据的时候就可以返回,也就是L = R,最后也可能为空

如图所示:
在这里插入图片描述
递归顺序我们就可以按照二叉树前序进行设计,所以代码实现如下:

//快速排序(霍尔递归)
void QuickSort1(int* a, int left, int right)
{
	assert(a);

	//递归子条件  区间只有一个值或者不存在
	if (left >= right)
	{
		return;
	}
	
	int begin = left;
    int end = right;

    int keyi = left;

    while (left < right)
    {
	    while (left < right && a[right] >= a[keyi])
	   {
		   --right;
	   }

	    while (left < right && a[left] <= a[keyi])
	   {
		   ++left;
	   }

	   Swap(&a[right], &a[left]);
   }
   Swap(&a[left], &a[keyi]);
   keyi = left;

  //区间划分[begin,key-1] [key] [key+1,end]
  QuickSort1(a, begin, keyi - 1);
  QuickSort1(a, keyi + 1, end);
}

我们可以试着用递归实现一组排序并画出递归展开图,有助于更深刻的理解递归。

2.2.2 前后指针版本

在Hoare大佬之后,有人在快速排序上增加其他实现玩法,前后指针实现快速排序就是其中u一种

大家可以先看一下下面递归图解进行学习:

在这里插入图片描述
可以看到,快慢指针最后也实现了key之前都小于key对应的值,key之后都大于key对应的值的效果

起初prev指向数组的开头,key也指向这个位置,cur指向prev前一个位置

判断cur指向的值是否大于key所指向的值

1.如果cur指向的值小于key指向的值,让prev加1向前移动一个位置,再交换cur和prev所指向的值,再将cur加1向前移动一个位置。
2.如果cur指向的值大于key指向的值,让cur+1向前移动一个位置。

持续循环判断上述逻辑,直到cur越界,最后再将prev和key所指向位置的对应值进行交换。

上面所讲的便是前后指针版实现一次单趟的整体逻辑。大家可以去尝试自行动手实现一下

前后指针单趟排完的效果与Hoare版的效果是一样的,所以我们只需要镶套上递归逻辑实现递归即可

代码如下:

//快速排序(双指针递归)
void QuickSort2(int* a, int left, int right)
{
	assert(a);

	if (left >= right)//递归终止条件
	{
		return;
	}

	int prev = left;
	int keyi = left;
	int cur = left + 1;
	//增加代码可读性
	int begin = left;
	int end = right;

	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)//这里排除了当prev与cur位置相同时的情况,在同一位置时不用交换
			Swap(&a[prev], &a[cur]);

		++cur;
	}
	Swap(&a[keyi], &a[prev]);
	keyi = prev;

	QuickSort2(a, begin, keyi - 1);//left
	QuickSort2(a, keyi + 1,end);//right
}

2.3 快速排序优化

这里我们先来计算一下快速排序的时间复杂度

对于上面所讲述的情况是以key最终取到的是中间位置然后在进行递归为例

相当于每次递归单趟排序只排好了数组中的一个数据

那么其时间复杂度就为O(N*logN)

在这里插入图片描述
而时间复杂度都是以最坏情况下计算的,那么快速排序在什么情况下是最坏的呢?

根据递归情况,当数组本身为有序的时候,对于快速排序来说的最坏的情况

在这里插入图片描述
此时每次都是以头部数据为key进行排序,那么递归深度为N,所以此时其时间复杂度为O(N^2)

有些同学可能就会好奇:“时间复杂度为N^2为什么效率会那么快”

因为每次进行排序,要排的数据本身就为有序概率是很低的,也可以说快速排序的适应性比较强,所以排序一般效率都非常快

但是有序出现的概率小并不代表不会出现,并且如果出现数据过多并且还是有序的情况下,不仅效率低下而且还可能会造成栈溢出,那么我应该怎么对其进行优化来解决这个问题呢?

2.3.1 随机数选key

上面所述问题的根本原因就是因为我们每次进行单趟排序的时候所选的key都是头部所指的位置数据,因为是有序数组,所以头部数据一定是数组中最小或最大的数据,这就造成遇到有序数据效率骤然下降

所以我们可以在每次递归都改变头部位置所指向的数据再进行单趟排序。

大家可以先看一下实现代码:

void Swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}

//快速排序(霍尔递归)
void QuickSort1(int* a, int left, int right)
{
	assert(a);

	//递归子条件  区间只有一个值或者不存在
	if (left >= right)
	{
		return;
	}
	int begin = left;
    int end = right;

	//打破最坏情况有序---生成随机数
    int randi = rand() % (left - right);
    randi += left;
    Swap(&a[randi], &a[left]);
    int keyi = left;

	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}

		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[keyi]);
	keyi = left;

	//[begin,key-1] [key] [key+1,end]
	QuickSort1(a, begin, keyi - 1);
	QuickSort1(a, keyi + 1, end);
}

我们生成随机数来做为数组下标进行操作

那么生成的随机数肯定是需要在所要进行排序的数组范围内,我们将生成的随机数余上排序数组的队尾下标与队头下标之差,之后在加上对头下标,随机数的范围就一定是在所要排序的数组内。

再将随机数下标所对应的值与对头数据进行交换,那么key所对应的值就不是原来数组对头的值,这样就解决了有序数组的问题。

但这中也只是大大降低了在有序数组中key所对应的值为最值得概率,当随机数等于队尾与对头之差时余上便等于0,此时key指向得还是对头的值,所以有人觉得这种方法不妥,于是又有了下面一种方法。

2.3.2 三数取中选key

这里得三数是指队头数据,中间数据(队尾下标与对头下标之和除2)和队尾数据

在这三个数据中选出大小为中间的值做key,这样key对应的值就一定不是其数组中的最值了

实现代码:

int GetMidi(int* a,int left,int right)
{
	int midi = (left + right) / 2;

	if (a[midi] > a[left])
	{
		if (a[midi] < a[right])
		{
			return midi;
		}
		else //(a[midi]>a[right])
		{
			if (a[left] > a[right])
			{
				return left;
			}
			else
			{
				return right;
			}
		}
	}
	else //a[midi] < a[left]
	{
		if (a[midi] > a[right])
		{
			return midi;
		}
		else
		{
			if (a[left] > a[right])
			{
				return right;
			}
			else
			{
				return left;
			}
		}
	}
}

//快速排序(霍尔递归)
void QuickSort1(int* a, int left, int right)
{
	assert(a);

	//递归子条件  区间只有一个值或者不存在
	if (left >= right)
	{
		return;
	}
	
	int begin = left;
    int end = right;
    
   	//三数取中
		int midi = GetMidi(a, left, right);
		Swap(&a[midi], &a[left]);

		int keyi = left;

		while (left < right)
		{
			while (left < right && a[right] >= a[keyi])
			{
				--right;
			}

			while (left < right && a[left] <= a[keyi])
			{
				++left;
			}

			Swap(&a[right], &a[left]);
		}
		Swap(&a[left], &a[keyi]);
		keyi = left;

		//[begin,key-1] [key] [key+1,end]
		QuickSort1(a, begin, keyi - 1);
		QuickSort1(a, keyi + 1, end);
	}
}

这样就可以完全避免最坏情况的出现

2.3.3 递归到小的子区间使用插入排序

因为我们递归实现形成的是二叉树结构,而对于二叉树我们看下图:
在这里插入图片描述

二叉树递归最后一层递归开辟栈帧的空间次数是占总递归的50%,而倒数第二层占总空间的25%,所以最后的两次递归消耗都已经占总消耗的75%左右

而对于我们快排也是如此,在递归到最后两层只剩下部分数据需要排序的话我们可以不选择使用快排,我们可以选择使用插入排序来进行解决

代码实现

int GetMidi(int* a,int left,int right)
{
	int midi = (left + right) / 2;

	if (a[midi] > a[left])
	{
		if (a[midi] < a[right])
		{
			return midi;
		}
		else //(a[midi]>a[right])
		{
			if (a[left] > a[right])
			{
				return left;
			}
			else
			{
				return right;
			}
		}
	}
	else //a[midi] < a[left]
	{
		if (a[midi] > a[right])
		{
			return midi;
		}
		else
		{
			if (a[left] > a[right])
			{
				return right;
			}
			else
			{
				return left;
			}
		}
	}
}

//快速排序(霍尔递归)
void QuickSort1(int* a, int left, int right)
{
	assert(a);

	//递归子条件  区间只有一个值或者不存在
	if (left >= right)
	{
		return;
	}

	//小区间可以走插入,能减少90%的递归空间消耗
	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);//a+left保证从每个递归区间进行排序
	}
	else
	{
		int begin = left;
		int end = right;

		//打破最坏情况有序---生成随机数
	/*int randi = rand() % (left - right);
	randi += left;
	Swap(&a[randi], &a[left]);*/

	//三数取中
		int midi = GetMidi(a, left, right);
		Swap(&a[midi], &a[left]);

		int keyi = left;

		while (left < right)
		{
			while (left < right && a[right] >= a[keyi])
			{
				--right;
			}

			while (left < right && a[left] <= a[keyi])
			{
				++left;
			}

			Swap(&a[right], &a[left]);
		}
		Swap(&a[left], &a[keyi]);
		keyi = left;

		//[begin,key-1] [key] [key+1,end]
		QuickSort1(a, begin, keyi - 1);
		QuickSort1(a, keyi + 1, end);
	}
}

这样就会减少大部分空间的开辟,减少消耗,减小空间复杂度

2.4 非递归实现

上面我们所讲述的都是利用递归进行实现快排的方法,那么大家可以想想如何不用递归来进行逻辑实现

非递归实现快排要用到栈来实现

我们想一想快排递归是如何实现,我们要怎样用栈来进行存储

递归中我们所传的参数有数组指针,队头下标和队尾下标,数组指针肯定不需要我们再往栈中进行存储,所以我们要想实现非递归我们就需要把每次递归所传的参数进行存储,将每次所传的参数进行单趟排序即可完成快速排序。

动态图解:

在这里插入图片描述
栈实现的是先进后出,上面动态图解中先往栈中入右子区间再入左区间那么实现逻辑就是先排左边再排右边,与二叉树前序遍历相符,当区间为1或是为空的时候便不在进行入栈操作最后实现效果与递归实现一样。

实现代码:

//利用栈 实现非递归快排
void QuickSortNonR(int* a, int left, int right)
{
	assert(a);

	ST st;
	STInit(&st);

	STPush(&st, right);
	STPush(&st, left);

	while (!STEmpty(&st))
	{
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);

		int prev = begin;
		int keyi = begin;
		int cur = begin + 1;
		//增加代码可读性

		while (cur <= end)
		{
			if (a[cur] < a[keyi] && ++prev != cur)//在同一位置不用交换
				Swap(&a[prev], &a[cur]);

			++cur;
		}
		Swap(&a[keyi], &a[prev]);
		keyi = prev;

		//[begin]~[keyi-1] [keyi+1]~[end]
		if (begin < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st,begin);
		}

		if (keyi + 1 < end)
		{
			STPush(&st, end);
			STPush(&st, keyi+1);
		}
	}

	STDestory(&st);
}

上面代码中含有栈的函数,不知道的可以去我将的栈与队列的博客中进行复制学习:栈与队列

2.5 快速排序的特性总结

1.快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2. 时间复杂度:O(N*logN)(优化后)
在这里插入图片描述
3. 空间复杂度:O(logN)

4. 稳定性:不稳定

二、完结撒❀

如果以上内容对你有帮助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步。
最后我想讲的是,据说点赞的都能找到漂亮女朋友❤
在这里插入图片描述

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

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

相关文章

ros小问题之rosdep update time out问题

在另外一篇ROS 2边学边练系列的文章里有写碰到这种问题的解决方法&#xff08;主要参考了其他博主的文章&#xff0c;只是针对ROS 2做了些修改调整&#xff09;&#xff0c;此处单拎出来方便查找。 在ROS 2中执行rosdep update时&#xff0c;报出如下错误&#xff1a; 其实原因…

数字乡村创新实践探索:科技赋能农业现代化与乡村治理体系现代化同步推进

随着信息技术的飞速发展&#xff0c;数字乡村作为乡村振兴的重要战略方向&#xff0c;正日益成为推动农业现代化和乡村治理体系现代化的关键力量。科技赋能下的数字乡村&#xff0c;不仅提高了农业生产的效率和品质&#xff0c;也为乡村治理带来了新的机遇和挑战。本文旨在探讨…

2024年腾讯云4核8G服务器性能可以满足哪些使用场景?

腾讯云4核8G服务器多少钱&#xff1f;腾讯云4核8G轻量应用服务器12M带宽租用价格646元15个月&#xff0c;活动页面 txybk.com/go/txy 活动链接打开如下图所示&#xff1a; 腾讯云4核8G服务器优惠价格 这台4核8G服务器是轻量应用服务器&#xff0c;详细配置为&#xff1a;轻量4核…

基于Python3的数据结构与算法 - 22 贪心算法

一、贪心算法 贪心算法&#xff08;又称贪婪算法&#xff09;是指&#xff0c;在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;它所做出的是在某种意义上的局部最优解。贪心算法并不会保证会得到最优解&a…

Scala第二十章节(Akka并发编程框架、Akka入门案例、Akka定时任务代码实现、两个进程间通信的案例以及简易版spark通信框架案例)

Scala第二十章节 章节目标 理解Akka并发编程框架简介掌握Akka入门案例掌握Akka定时任务代码实现掌握两个进程间通信的案例掌握简易版spark通信框架案例 1. Akka并发编程框架简介 1.1 Akka概述 Akka是一个用于构建高并发、分布式和可扩展的基于事件驱动的应用工具包。Akka是…

深入浅出 -- 系统架构之微服务架构

1.1 微服务的架构特征&#xff1a; 单一职责&#xff1a;微服务拆分粒度更小&#xff0c;每一个服务都对应唯一的业务能力&#xff0c;做到单一职责 自治&#xff1a;团队独立、技术独立、数据独立&#xff0c;独立部署和交付 面向服务&#xff1a;服务提供统一标准的接口&…

【C语言】【Leetcode】【递归】22. 括号生成

文章目录 题目思路代码实现 题目 链接: https://leetcode.cn/problems/generate-parentheses/description/ 思路 我们可以通过回溯递归算法求解 如果左括号数量不大于n&#xff0c;我们可以放一个左括号。 如果右括号数量小于左括号的数量&#xff0c;我们可以放一个右括号…

数据库的介绍分类作用特点

目录 1.概述 2.分类 2.1.关系型数据库 2.2.非关系型数据库 2.3.分布式数据库 ​​​​​​​2.4.云数据库 3.作用 4.特点 5.应用举例 5.1.MySQL ​​​​​​​5.1.1.作用 ​​​​​​​5.1.2.特点 ​​​​​​​5.1.3.应用案例 ​​​​​​​5.2.达梦 ​​​…

十分钟掌握在 PyTorch 中构建一个深度神经网络,基本组件、步骤和代码实现,从导入模块和定义网络结构到训练和评估网络性能。

🍉 CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 深度神经网络(Deep Neural Networks, DNNs),也被称为人工神经网络(Artificial Neural Networks,ANNs),已成为当今机器学习任务中最流行、最成功的方法之一。这些网络能够表示数据中的复杂关系,并在图像分类、自然…

攻防世界 wife_wife

在这个 JavaScript 示例中&#xff0c;有两个对象&#xff1a;baseUser 和 user。 baseUser 对象定义如下&#xff1a; baseUser { a: 1 } 这个对象有一个属性 a&#xff0c;其值为 1&#xff0c;没有显式指定原型对象&#xff0c;因此它将默认继承 Object.prototype。 …

【Linux】vim 编辑器

Linux 系统自带了 gedit 和 vi 编辑器&#xff0c;gedit 是图形化界面的操作&#xff0c;而 vi 由比较难用&#xff0c;所以建议安装 vim 编辑器&#xff0c;vim 是从 vi 发展出来的一个文本编辑器&#xff0c;相当于增强版的 vi &#xff0c;其代码补完、编译及错误跳转等功能…

关闭PyCharm中因双击Shift而跳出的搜索框

有时候老是多次按到shift而跳出一个搜索框&#xff0c;本来在编写代码&#xff0c;怎么突然就开始搜索了&#xff0c;非常的烦人。 其实这个搜索框叫做“随处搜索”。 关闭步骤 1、打开PyCharm的设置。 2、在设置-高级设置中勾选-禁用双击修改键快捷键即可。

ES6: class类

类 class 面相对象class关键字创建类关于类的继承 面相对象 一切皆对象。 举例&#xff1a; 操作浏览器要使用window对象&#xff1b;操作网页要使用document对象&#xff1b;操作控制台要使用console对象&#xff1b; ES6中增加了类的概念&#xff0c;其实ES5中已经可以实现类…

vulhub中Apache Solr Velocity 注入远程命令执行漏洞复现 (CVE-2019-17558)

Apache Solr 是一个开源的搜索服务器。 在其 5.0.0 到 8.3.1版本中&#xff0c;用户可以注入自定义模板&#xff0c;通过Velocity模板语言执行任意命令。 访问http://your-ip:8983即可查看到一个无需权限的Apache Solr服务。 1.默认情况下params.resource.loader.enabled配置…

一点点金融 4

一点点金融 4 第一性原理&#xff1a;关键事件前后&#xff0c;市场会从不确定性转变为确定性弹簧板、天花板&#xff1a;作为止损、换策略的依据怎么判断弹簧板、天花板&#xff1f; 第一性原理&#xff1a;关键事件前后&#xff0c;市场会从不确定性转变为确定性 在关键事件…

idea改vm参数后没法重启

背景 Idea2023修改了编译器compiler内存&#xff0c;maven的run time内存&#xff0c;idea安装目录下idea64.exe.vmoptions选项的jvm内存参数后导致idea启动时没有任何反应&#xff0c;也没有任何日志输出 idea2023没法重启 导致idea2023没法重启的操作步骤如下 1.修改idea的…

基于SSM的教材管理系统的设计与实现(论文+源码)_kaic

基于SSM的教材管理系统的设计与实现 摘 要 当下&#xff0c;正处于信息化的时代&#xff0c;许多行业顺应时代的变化&#xff0c;结合使用计算机技术向数字化、信息化建设迈进。以前学校对于教材信息的管理和控制&#xff0c;采用人工登记的方式保存相关数据&#xff0c;这种以…

STM32的CAN外设

我们的CAN控制器支持最高的通讯速率为1Mb/s&#xff0c;可以自动地接收和发送CAN报文&#xff0c;支持使用标准ID和扩展ID地报文&#xff0c;外设中具有3个发送邮箱&#xff0c;发送报文的优先级可以使用软件控制&#xff0c;还可以记录发送的时间&#xff0c;具有两个3级深度的…

Mac下使用homebrew管理多版本mysql同时

Mac下使用homebrew管理多版本mysql同时启动 思路 给每个版本分配不同的数据目录和配置文件即可 本文尝试了使用 brew 安装管理多个MySQL版本&#xff0c;同时运行、直接切换 安装 如果已有数据文件请自行备份以及使用 安装 mysql 5.7 brew install mysql5.7在 /opt/home…

Github 2024-04-04 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-04-04统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目5TypeScript项目2Go项目1Jupyter Notebook项目1Java项目1C++项目1非开发语言项目1Vue项目1编程面试大学:成为软件工程师的全面学习计…