数据结构与算法:快速排序

数据结构与算法:快速排序

    • 快速排序
      • 荷兰国旗问题
      • 霍尔版本
      • 递归优化
        • 小区间优化
      • PartSort优化
          • 三数取中
        • 挖坑法
        • 前后指针法
      • 非递归法


快速排序

荷兰国旗问题

想要理解快速排序,就先理解这个问题:

在这里插入图片描述
[LeetCode75.颜色分类]

荷兰国旗是由红白蓝三色组成的:
在这里插入图片描述
现在将其颜色打乱
在这里插入图片描述
然后根据一定的算法,将其复原为红白蓝三色,这就叫做荷兰国旗问题。

在LeetCode的题目中,其将荷兰国旗的三个颜色用0,1,2来表达,也就是说我们要把大于1的放到1的右边,小于1的放到1的左边,这就是解决这个问题的基本思路。

快排的基本思路也是如此:在一趟排序中,将一个值作为key值,然后将大于这个key值的数放到右边,小于key的放到key的左边。

比如这样个数组
在这里插入图片描述
假设我们将第一个值作为key值,那么高于这个红线的值都要到右边作为大于区,低于这个红线的值都要到左边作为小于区,然后再把key放到大于区和小于区的中间。

比如这样:
在这里插入图片描述

然后再把key值插入到中间:
在这里插入图片描述
这样左边的值都小于key,右边的值都大于key。

那么这要如何实现呢?我们先看到最古早的快速排序–霍尔版本


霍尔版本

我们的目的是:把大于key的值放到右边,小于key的值放到左边。

此时霍尔想到,安排两个变量leftright左边的变量left往右寻找大于这个key的值右边的变量right往左寻找小于这个key的值一旦left和right都找了值,交换left与right的值
示例:
一开始选定第一个值作为key值:
在这里插入图片描述
然后i变量往右移动,直到找到一个值大于key:
在这里插入图片描述
接着right往左移动,找到一个值小于key:
在这里插入图片描述
交换两个值:
在这里插入图片描述
循环以上步骤:
请添加图片描述
当我们完成这个过程后,数组状态如下:
在这里插入图片描述

红色区域是小于区,绿色区域是大于区
接下来就是要把key放到小于区大于区的中间,这个过程,只需要将key与小于区的最后一个元素交换位置即可

请添加图片描述
这样我们就完成了一趟排序。

一趟排序的代码如下:

	int left = 0, right = n;
	int keyi = 0;

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

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

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

以下是对代码的解释:

  1. 初始化变量leftright,分别表示数组的左右边界。left初始为0,right初始为n,其中n表示数组长度。
  2. 初始化变量keyi,表示选取的基准元素的下标,初始为0。
  3. 进入循环,循环条件是left < right,即左边界小于右边界。
  4. 第一个while循环:从右边开始向左遍历数组a,寻找第一个小于基准元素a[keyi]的元素。循环条件是left < right并且a[right]大于等于a[keyi],如果满足条件,说明当前元素不是要找的元素,则right减1,寻找下一个元素。
  5. 第二个while循环:从左边开始向右遍历数组a,寻找第一个大于基准元素a[keyi]的元素。循环条件是left < right并且a[left]小于等于a[keyi],如果满足条件,说明当前元素不是要找的元素,则left加1,寻找下一个元素。
  6. 当两个while循环结束后,说明找到了需要交换的两个元素。调用Swap函数,将a[left]a[right]交换位置。
  7. 继续下一轮循环,直到left >= right。
  8. 当循环结束,交换a[left]a[keyi],即把key值和小于区的最后一个元素交换位置,让key到达正确位置。

图示:
在这里插入图片描述
这个过程中,left左侧的值(紫色)都小于keyright右侧的值(绿色)都大于key,当相遇时,两者分别就维护了一段大于区和一段小于区,直接将key插入即可。

那么这一趟排序有什么用呢?虽然最后没有把整个数组变成有序,但是我们的key值找到了正确的位置
也就是说,经过这一趟排序,key的位置此后都不会改变了,key的位置就是完全有序状态下的位置。

这是为什么?
不妨想想一个完全有序的数组,取其中某一个值出来,不就是左边的所有值都小于等于这个元素,右边所有值大于等于这个元素吗?

那么我们要如何进行接下来的步骤,让整个数组有序?
答案是递归。
经过这一趟排序,我们将数组拆分为了三个区域:[小于区]:[key]:[大于区]

接下来我们不断将小于区和大于区拆分出来执行这个过程:

1.选key
2.筛选新的小于区和大于区

在这里插入图片描述
当快速排序递归到底层时,每个子数组只含有一个元素或没有元素,这些子数组都可以看作是有序的。由于每次递归都确保了基准元素的左右两个部分分别有序,最终整个数组就会有序。

我们看一看快速排序的主体部分:

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
		
	//单趟排序
	
	QuickSort(a, begin, keyi - 1);//递归小于区间
	QuickSort(a, keyi + 1, end);//递归大于区间
}

函数QuickSort接受一个整型数组a以及两个整数beginend作为参数,表示对数组的子区间[begin, end]进行排序。如果begin >= end,即子区间为空或只有一个元素,那么不需要排序,直接返回。

在每次递归调用中,函数将主区间分为两个子区间:小于key的区间和大于key的区间。通过递归调用自身,对两个子区间进行排序。

通过递归调用QuickSort(a, begin, keyi - 1),对小于关键字的区间进行排序。递归调用QuickSort(a, keyi + 1, end)对大于关键字的区间进行排序。

经过多次递归调用后,整个数组将被划分为若干个有序的子区间,最后合并起来得到整体有序的数组。

完整代码:

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

//单趟排序开始---------------------------------------
	int left = begin, right = end;
	int keyi = begin;

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

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

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

	Swap(&a[left], &a[keyi]);
//单趟排序结束---------------------------------------

	keyi = left;
	QuickSort(a, begin, keyi - 1);//递归小于区间
	QuickSort(a, keyi + 1, end);//递归大于区间
}

其中,我们将单趟排序称为PartSort,递归主体就是快排主体,我们会如下拆分代码:

int PartSort(int* a, int begin, int end)
{
	int left = begin, right = end;
	int keyi = begin;

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

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

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

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

	return keyi;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
		
	int keyi = PartSort(a, begin, end);
	
	QuickSort(a, begin, keyi - 1);//递归小于区间
	QuickSort(a, keyi + 1, end);//递归大于区间
}

总效果图:
请添加图片描述

至此,我就为大家解释完了快排的基本思想,但是快排至此还不是完全体,后人对快排做了非常多的优化,使得快排性能一步一步提高。


递归优化

小区间优化

快速排序是基于递归的排序,其递归是会向左右区间递归,那么就会形成一个二叉树的结构。而二叉树的最后一层的节点占比可以高达50%。

假设满二叉树的深度为h,那么最后一层的节点数目为2(h-1)个,而满二叉树的总节点数目为2h - 1个。

因此,最后一层占所有节点的百分比为: (2(h-1))/(2h - 1) * 100%
当递归层数足够深,这个式子就可以看为: (2(h-1))/(2h ) * 100% = 50%

而当快速排序递归到最后一层,区间内只有一两个数字需要排序了,此时再递归是没必要的。我们可以选用其它的排序来进行最后几个数字的排序,这样可以大量节省递归的消耗,提高性能。

在此,我们可以利用beginend的差值来控制递归,当其差值小于一定值时,我们改用插入排序来收尾。因为快速排序到了最后几层,递归出来的小数组已经十分接近有序,而插入排序对有序数组的适应性较好,所以这里选用插入排序。

代码如下:

void QuickSort(int* a, int begin, int end) 
{
	int THRESHOLD = 10; // 设定阈值为10 
	if (begin >= end) 
	 return; 
	 
	if (end - begin + 1 <= THRESHOLD)
	{
		InsertSort(a, begin, end);// 使用插入排序对小区间进行排序
	}
	else
	{
		int keyi = PartSort(a, begin, end);
		QuickSort(a, begin, keyi - 1); // 递归小于区间
		QuickSort(a, keyi + 1, end); // 递归大于区间
	}
}

此处我们将阈值设为了10,也就是当递归划分数组的长度小于10时,就进行插入排序,这个阈值可以减少大约3-4层递归,最后一层递归就已经50%了,三四层递归可以节省80-90%的递归次数。

这个优化看似效率很高,但是其实现在的递归消耗并不高,哪怕节省了80-90%的递归次数,也许优化效果并没有那么好。


PartSort优化

三数取中

我们目前的快速排序仍然有一个缺点:对已经有序的数组适应性非常差

比如这样一个数组:
在这里插入图片描述
我们对其进行快速排序,由于其已经有序,而我们每次选取key值又选的是第一个值,这就会导致一趟PartSort下来,小于区间为0,大于区间为n-1,接着我们递归到大于区间,不断循环上述过程:请添加图片描述
那么这样对于一个n个数字的数组,我们就需要递归n层。这样实在太浪费资源了。
我们是否有办法,减少在有序情况或者接近有序的情况的递归层数?

于是三数取中出现了,三数取中是指,每次选取key值时:

1.先挑出最左侧下标值,最右侧下标值以及中间下标值(依据下标选出三个值)
2.选取三个值中的中间值

那么为什么三数取中可以减少递归的层数?

我们看一个实例:
在这里插入图片描述
我先为大家解释此图:
每一层是一一层递归,红色值是本次递归的key值,蓝色是大于区,绿色是小于区。下一层递归是,蓝色区和绿色区分别递归,并重新筛选key值。

在选取第一个值作为key的图中,由于一直没有小于区出现,一次每层递归只能排序一次,而右侧由于选择了中间值作为key值,会尽可能保证大于区和小于区都存在,下一层递归就能处理更多的数据。

这样我们左侧递归了9层,而右侧只递归了4层。这就是三数取中带来的效率优化。一般来说,取第一个值最多需要递归n层,而三数取中一般为logn层。

我们先选一个三数取中的函数:

//三数取中
int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	//选begin end midi 的中位数
	if (a[begin] < a[midi])
	{
		if (a[midi] < a[end])
			return midi;
		else if (a[begin] > a[end])
			return begin;
		else
			return end;
	}
	else 
	{
		if (a[end] < a[midi])
			return midi;
		else if (a[end] > a[begin])
			return begin;
		else
			return end;
	}
}

这个函数就是实现三数取中:

1.先挑出最左侧下标值,最右侧下标值以及中间下标值(依据下标选出三个值)
2.选取三个值中的中间值

那么取出了中间值,要如何让快排将其作为key值?
我们先前的快排是把第一个值的位置作为key,如果取中间值作为key,那么这个值碍在中间,会不会影响left和right的移动判断?
所以我们选出中间数后,先将其与第一个值交换,把中间值放到第一个位置,防止它碍在中间。

代码如下:

//霍尔三数取中快排
int PartSort1(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);//将取出的中间值放在首位做key

	int left = begin, right = end;
	int keyi = begin;

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

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

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

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

	return keyi;
}

挖坑法

挖坑法是对霍尔版本的优化,霍尔版本中我们利用了left与right分别找大找小,然后将left与right的值进行交换。到了最后再把key和小于区的最后一个值交换。

挖坑法的思路是:

  1. 一开始将key值的位置空出来作为坑位
  2. 右边right找小,找到后直接放到key的位置(坑位),然后将此位置作为坑位
  3. 左边left找大,找到后放到坑位(刚刚右边空出来的位置),然后将此位置作为坑位
  4. 循环2,3步骤,直到leftright相遇
  5. 相遇后,将key值放到相遇点

图解如下:

请添加图片描述

代码如下:

// 快速排序挖坑法
int PartSort2(int* a, int begin, int end)
{
	//三数取中
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

	int key = a[begin];
	int hole = begin;

	while (begin < end)
	{
		//右边找小,填到右左边的坑
		while (begin < end && a[end] >= key)
		{
			end--;
		}

		a[hole] = a[end];//填坑
		hole = end;//换坑

		//左边找大,填到右边的坑
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}

		a[hole] = a[begin];//填坑
		hole = begin;//换坑
	}

	a[hole] = key;
	return hole;
}

代码解析如下:

  1. 首先,通过调用GetMidi函数,找到数组a中的三个元素(起始索引begin、终止索引end以及中间索引midi)的中值,并将中值与起始索引的元素交换位置。这样做是为了通过将中值作为基准值,降低快速排序的时间复杂度。

  1. 定义变量key为基准值,hole为开始时的坑的位置(即起始索引begin)。

  1. 进入一个循环,循环条件是开始索引小于结束索引。

  1. 在循环中,先从右向左扫描数组,找到第一个小于基准值的元素,将其填入坑中(即将a[end]赋值给a[hole]),然后将坑的位置更新为已填入元素的位置。

  1. 再从左向右扫描数组,找到第一个大于基准值的元素,将其填入右边的坑中(即将a[begin]赋值给a[hole]),然后将坑的位置更新为已填入元素的位置。

  1. 重复步骤4和步骤5,直到开始索引不小于结束索引为止。

  1. 将基准值填入最后一个坑中,此时基准值左边的元素都小于它,右边的元素都大于它。

  1. 返回基准值的索引。

前后指针法

回顾一下霍尔法以及挖坑法的基本思路,其用left与right来拓展小于区和大于区,最后将key放在了中间。
像这样:
在这里插入图片描述
这个过程中,right右边的是大于的区域,left左边的是小于的区域。
当我们同时确定了大于的区域和小于的区域,再把key值插入。

那么我们一定要同时维护大于区和小于区吗
不妨想一想,我们只维护一段小于区,把所有小于等于key的值都放进小于区,剩下的值不就是大于key的值了吗?
也就是说我们可以只维护一段区域,剩下的区域会自动生成,这就是前后指针法的思路。

先看代码,后解析。
代码如下:

// 快速排序前后指针法 
int PartSort3(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

	int prev = begin;
	int cur = prev + 1;

	int keyi = begin;

	while (cur <= end)
	{
		if (a[cur] < a[keyi])//避免自己和自己交换
			Swap(&a[++prev], &a[cur]);

		cur++;
	}

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

	return keyi;
}

思路:

使用一个prev指针维护一段小于区,用cur指针在数组中查找小于key的值,找到后放进小于区,并且++prev,意味着小于区扩展了一位。最后把key放到小于区的后面,此时大于区已经自动生成在了key的右侧

代码详解:

首先,通过调用GetMidi函数获取中间元素的下标midi,然后将中间元素与起始元素交换位置,将中间元素作为基准元素。


接下来,定义两个指针prevcurprev指向基准元素的位置,cur指向prev的下一个位置。
然后,定义一个keyi变量来保存基准元素的位置。


进入循环,在循环内部首先判断cur所指向的元素是否小于基准元素,并且判断prevcur是否相等,避免自己和自己交换。如果满足条件,则将prev指向的元素与cur指向的元素交换位置,prev向后移动一位(此时相当于小于区扩大了一位)。


循环结束后,将prev所指向的元素与基准元素交换位置,将基准元素放在正确的位置上,然后返回基准元素的位置keyi

这样就完成了一次快速排序的分割操作。

示意图如下:
请添加图片描述

要注意,由于前两次交换,prev和cur还没有拉开差距,也就是说没有发现比key大的值,所以是自己和自己交换。


非递归法

那么也没有不递归的快速排序法呢?
也是有的,我们在递归排序时利用的思路是,每次递归key左侧的小于区以及右侧的大于区,把这个区域进行一次PartSort。

也就是说我们要想办法把下一次需要单趟排序的范围存储下来
这里可以用到一个栈结构,每次单趟排序,把排序的区间从栈中取出来,排序完后根据key划分区域,再把需要排序的子区域入栈。

代码如下:
此处不对栈这个结构的实现做详解,受C语言限制,这个栈需要手撕。

// 快速排序 非递归实现
void QuickSortNonR(int* a, int begin, int end)
{
	Stack s;
	StackInit(&s);//初始化栈
	StackPush(&s, end);//入栈数组尾部
	StackPush(&s, begin);//入栈数组头部

	while (!StackEmpty(&s))
	{
		//取出栈顶的左右元素,作为本次排序的范围
		int left = StackTop(&s);
		StackPop(&s);
		int right = StackTop(&s);
		StackPop(&s);

		int keyi = PartSort(a, left, right);//一趟排序
		if (left < keyi - 1)//将本次排序的子区间入栈
		{
			StackPush(&s, keyi - 1);
			StackPush(&s, left);
		}

		if (keyi + 1 < right)//将本次排序的子区间入栈
		{
			StackPush(&s, right);
			StackPush(&s, keyi + 1);
		}
	}
	StackDestroy(&s);//销毁栈
}

下面是对代码的详细解释:

  1. 定义了一个函数QuickSortNonR,参数为待排序数组a的起始位置begin和结束位置end

  1. 初始化一个栈s,用于保存待排序区间。调用StackInit函数初始化栈。

  1. 将数组的起始位置和结束位置分别入栈。即StackPush(&s, end)StackPush(&s, begin)

  1. 进入循环,判断栈是否为空。如果栈不为空,则继续执行排序操作;否则结束排序。

  1. 在循环内部,首先从栈顶取出左右边界,即leftright。分别将它们出栈。

  1. 调用PartSort函数进行一趟排序,将数组a中left到right之间的元素按照某个基准值进行划分,返回基准值的下标。

  1. 如果左边区间的起始位置left小于基准值的下标keyi-1,则将该区间入栈。即StackPush(&s, keyi-1)StackPush(&s, left)

  1. 如果右边区间的结束位置keyi+1小于right,则将该区间入栈。即StackPush(&s, right)StackPush(&s, keyi+1)

  1. 回到步骤5,继续下一次排序操作。

  1. 当栈为空时,表示所有的子区间都已经排序完成,结束循环。

  1. 调用StackDestroy函数销毁栈s,释放内存。

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

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

相关文章

低代码平台,新型应用程序开发神器

目前低代码平台如火如荼。这一新兴技术为企业提供了一种高效、灵活、快速开发应用程序的方法&#xff0c;并在短时间内取得了巨大成功。然而&#xff0c;我们不得不面对低代码平台的优劣以及其所带来的挑战。本文将深入探讨低代码平台在不同情况下的优劣势&#xff0c;并与您分…

Qt 国产嵌入式操作系统实现文字转语音功能(TTS)

1.简介 本示例使用的CPU&#xff1a;rk3588。 操作系统&#xff1a;kylin V10 架构&#xff1a;aarch64 在Windows端&#xff0c;我们很容易想到使用Qt自带的类QTextToSpeech来实现文字转语音功能&#xff0c;Qt版本得在5.11.0以上才支持。但是在嵌入式平台&#xff0c;尤其…

HubSpot社交媒体整合的好处有哪些?

HubSpot的社交媒体整合提供了许多好处&#xff0c;有助于用户更好地管理和优化其社交媒体活动。以下是一些使用HubSpot社交媒体整合的好处&#xff1a; 集中管理&#xff1a; 用户可以在HubSpot平台上集中管理多个社交媒体账户&#xff0c;无需切换到不同的平台。这简化了社交媒…

同步、异步无障碍:Python异步装饰器指南

一、引言 Python异步开发已经非常流行了&#xff0c;一些主流的组件像MySQL、Redis、RabbitMQ等都提供了异步的客户端&#xff0c;再处理耗时的时候不会堵塞住主线程&#xff0c;不但可以提高并发能力&#xff0c;也能减少多线程带来的cpu上下文切换以及内存资源消耗。但在业务…

2023一带一路暨金砖国家技能发展与技术创新大赛“网络安全”赛项省选拔赛样题卷①

2023金砖国家职业技能竞赛"网络安全" 赛项省赛选拔赛样题 2023金砖国家职业技能竞赛 省赛选拔赛样题第一阶段&#xff1a;职业素养与理论技能项目1. 职业素养项目2. 网络安全项目3. 安全运营 第二阶段&#xff1a;安全运营项目1. 操作系统安全配置与加固任务一Linux …

基于信号完整性的一些PCB设计建议

最小化单根信号线质量的一些PCB设计建议 1. 使用受控阻抗线&#xff1b; 2. 理想情况下&#xff0c;所有信号都应该使用完整的电源或地平面作为其返回路径&#xff0c;关键信号则使用地平面作为返回路径&#xff1b; 3. 信号的返回参考面发生变化时&#xff0c;在尽可能接近…

欧盟产品安全新规来袭,亚马逊发出紧急提醒(GPSR)要求

欧盟产品安全新规来袭&#xff0c;亚马逊发出紧急提醒&#xff08;GPSR&#xff09;要求 一、发布新规 这世界上唯一不变的事&#xff0c;或许就是变化本身。 在跨境电商领域&#xff0c;这个道理再次得到验证。近日&#xff0c;不少卖家都收到了一封来自亚马逊的通知。通知中…

springboot下载图片的简单处理方式

参考地址 springboot&#xff1a;各种下载文件的方式_springboot下载文件-CSDN博客 开箱即用实战 GetMapping("/t1")public void down1(HttpServletResponse response) throws Exception {response.reset();response.setContentType("application/octet-strea…

在服务器上使用Docker运行SRS Stack,推拉直播流、多平台转播、本地录制、虚拟直播、直播转码、AI字幕、其他

SRS Stack | SRS (ossrs.net) Docker​ 推荐使用Docker运行SRS Stack&#xff1a; docker run --restart always -d -it --name srs-stack -v $HOME/data:/data \-p 2022:2022 -p 2443:2443 -p 1935:1935 -p 8000:8000/udp -p 10080:10080/udp \registry.cn-hangzhou.aliyun…

Linux 多个php版本选择需要的php的版本(修改环境变量)

这两天遇到了个问题&#xff0c; 原本服务器的php版本是7.3.13&#xff0c;经过一些操作之后不知道怎么了变成了5.6 #php版本查看 php -v然后我就对 5.6版本进行了升级&#xff0c;升级到了7.3.33&#xff0c; 这个时候 php -v 是7.3.33&#xff0c; 神奇的一幕出现了&#xf…

把握现货黄金的基本操作技巧

在投资市场这个大舞台上&#xff0c;有各种各样的投资产品供投资者选择&#xff0c;其中黄金作为一种重要的投资资产&#xff0c;一直受到广大投资者的青睐。然而&#xff0c;黄金交易并非易事&#xff0c;需要掌握一定的操作技巧。那么&#xff0c;如何才能把握住现货黄金的操…

XS2180四通道,兼容 IEEE 802.3at/af以太网供电 PSE 控制器 V1.0

XS2180 是一个四通道、供电设备&#xff08; PSE &#xff09;电源控制 器&#xff0c;设计用于 IEEE 802.3at/af 兼容 PSE 。器件提供用 电设备&#xff08; PD &#xff09;检测、分级、限流以及负载断开检测。器 件支持自动工作和软件编程。器件还支持最新二事件分…

删除运行框中的文件打开历史记录

当我们使用everything、百度、迅雷等软件&#xff0c;在列表中右键选中打开文件夹时。 当使用 winR 快捷键等方式打开运行时&#xff0c;输入盘符会出现之前打开过的文件夹&#xff0c; 一方面展示的特别多会比较混乱&#xff0c;另一方面 记得在之前的window版本中&#xff08…

数据库系列:InnoDB下实现高并发控制

1 介绍 并发控制是为了防止多用户并发使用数据库时造成数据错误和程序运行错误&#xff0c;保证数据的完整性。当多个事务并发地存取数据库时&#xff0c;就会产生同时读取和/或修改同一数据的情况。若对并发操作不加控制就可能会存取和存储不正确的数据&#xff0c;破坏数据库…

第27集《佛法修学概要》

丁二、天乘分三&#xff1a;戊一、十善业。戊二、四禅定。戊三、四空定 请大家打开讲义第七十七页&#xff0c;这是五乘里面的第二个法门&#xff0c;天乘法门。 大乘佛教把我们众生生命的现象分成了三个部分&#xff1a;第一个部分&#xff0c;叫作 本来清净&#xff1b;第…

CSS 之 跑马灯边框

一、简介 ​ 之前有做过渐变色边框的样式&#xff0c;然后某天刷抖&#x1f3b5;&#xff0c;看到某个老师在讲跑马灯的样式效果&#xff0c;于是就自己尝试实现一下跑马灯样式的边框&#xff0c;虽然这种样式并不常见&#xff08;基本没卵用&#xff09;&#xff0c;其使用场…

git的分支的使用,创建分支,合并分支,删除分支,合并冲突,分支管理策略,bug分支,强制删除分支

GIT | 分支 文章目录 GIT | 分支创建分支合并分支删除分支合并冲突分支管理策略bug分支强制删除分支 创建分支 查看当前本地仓库中有哪些分支 git branchHEAD所指向的分支就是当前正在工作的分支 cat .git/HEAD创建一个分支 git branch dev创建好了&#xff0c;但是目前还是…

Redis基础系列-哨兵模式

Redis基础系列-哨兵模式 文章目录 Redis基础系列-哨兵模式1. 引言2. 什么是哨兵模式&#xff1f;3. 哨兵模式的配置4. 哨兵模式的启动和验证4.1 主master宕机&#xff0c;看会出现什么问题4.2 重启6379主机 5. 哨兵模式的工作原理和选举原理5.1. SDown主观下线&#xff08;Subj…

Vue.js轻量级框架:快速搭建可扩展的管理系统

一、前言 在项目实战开发中&#xff0c;尤其是大平台系统的搭建&#xff0c;针对不同业务场景&#xff0c;需要为用户多次编写用于录入、修改、展示操作的相应表单页面。一旦表单需求过多&#xff0c;对于开发人员来说&#xff0c;算是一种重复开发&#xff0c;甚至是繁杂的工作…

RFID技术在汽车装备中的应用:提升安全性与效率

RFID技术在汽车装备中的应用&#xff1a;提升安全性与效率 射频识别&#xff08;RFID&#xff09;技术是一种非接触式的自动识别技术&#xff0c;它利用射频信号及其空间耦合和传输特性&#xff0c;实现对目标对象的信息读写。随着汽车工业的不断发展&#xff0c;汽车装备的技…