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

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

    • 快速排序
      • 荷兰国旗问题
      • 霍尔版本
      • 递归优化
        • 小区间优化
      • 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;elsereturn end;}else {if (a[end] < a[midi])return midi;else if (a[end] > a[begin])return begin;elsereturn 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]);//将取出的中间值放在首位做keyint 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.mzph.cn/news/626550.shtml

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

相关文章

10.云原生之在线开发调试

云原生专栏大纲 文章目录 vscode-server介绍VSCode Server 和云开发结合vscode-server安装code-server安装插件在线安装插件离线安装插件安装中文插件 配置开发环境在容器中安装开放环境Dockerfile制作镜像 git拉取项目 vscode-server介绍 VSCode Server&#xff08;Visual S…

动态内存面试的经典题目

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

js includes方法的使用

JavaScript中的数组includes()方法用于判断数组中是否包含指定的元素&#xff0c;并返回一个布尔值。 includes()方法的语法如下&#xff1a; array.includes(searchElement[, fromIndex]) 其中&#xff0c;array是要进行搜索的数组&#xff0c;searchElement是要搜索的元素…

网络安全中的“三高一弱”和“两高一弱”是什么?

大家在一些网络安全检查中&#xff0c;可能经常会遇到“三高一弱”这个说法。那么&#xff0c;三高一弱指的是什么呢&#xff1f; 三高&#xff1a;高危漏洞、高危端口、高风险外连 一弱&#xff1a;弱口令 一共是4个网络安全风险&#xff0c;其中的“高危漏洞、高危端口、弱…

电脑怎么录制屏幕?看这一篇就够了

在数字时代&#xff0c;电脑屏幕录制已经成为人们日常生活中一个越来越重要的工具。无论是录制在线课程、游戏精彩时刻&#xff0c;还是远程会议、软件演示&#xff0c;屏幕录制都可以帮助我们更好地保存和分享这些信息。可是您知道电脑怎么录制屏幕吗&#xff1f;本文将介绍两…

GATHER_DATABASE_STATS 的常见问题及 权限 ANALYZE ANY

​​​​​​光有执行DBMS_STATS的权限还是不够的 DBMS_STATS.GATHER_DATABASE_STATS (ESTIMATE_PERCENT > 30); * ERROR at line 1: ORA-20000: Insufficient privileges to analyze an object in Database ORA-06512: at "SYS.DBMS_STATS", line 13323 ORA-0651…

旧电脑追加内存条

内存条基本知识 DDR4 2666 DDR&#xff08;Double Data Rate&#xff09;双倍速率 4 第四代 2666 内存主频2666MHz 内存时序 内存的延迟时间 传输带宽 MB/s 内存和CPU之间的传输速度 针脚数 数字-PIn 288-PIN就是288个针脚 选购内存条 …

【国产mcu填坑篇】华大单片机(小华半导体)一、SPI的DMA应用(发送主机)HC32L136

最近需要用华大的hc32l136的硬件SPIDMA传输&#xff0c;瞎写很久没调好&#xff0c;看参考手册&#xff0c;瞎碰一天搞通了。。。 先说下我之前犯的错误&#xff0c;也是最宝贵的经验&#xff0c;供参考 没多看参考手册直接写&#xff08;即使有点烂仍然提供了最高的参考价值。…

iis配置asp网站

1.安装IIS的ASP win7和win10都是一样的 下安装IIS时ASP一般被默认不选中的状态&#xff0c;因此需要打开IIS检查功能视图栏中是否存在ASP选项&#xff0c;若没有则需要从控制面板->程序和 功能->打开或关闭Windows功能->Internet信息服务->万维网服务->应用程序…

数环通更新动态|新增连接器抖店自建、叮当OKR、千易ERP、货拉拉

更新快速预览 新增连接器4个 抖店自建 叮当OKR 千易ERP 货拉拉 应用更新2个 百度统计&#xff08;2&#xff09; 旺店通&#xff08;1&#xff09; 应用连接器 新增连接器 1.抖店自建 抖店是抖音官方打造的电商商家实现一站式经营平台&#xff0c;为商家提供全链路服务&#xf…

Selenium定位元素的方法css和xpath的区别

selenium是一种自动化测试工具&#xff0c;它可以通过不同的定位方式来识别网页上的元素&#xff0c;如id、name、class、tag、link text、partial link text、css和xpath。 css和xpath是两种常用的定位方式&#xff0c;它们都可以通过元素的属性或者层级关系来定位元素&#…

MacOS环境下Kali Linux安装及使用指导

Kali Linux是一个开源的、基于Debian的Linux发行版&#xff0c;面向各种信息安全任务&#xff0c;如渗透测试、安全研究、计算机取证和逆向工程&#xff0c;是最先进的渗透测试发行版&#xff0c;它的前身是BackTrack。 1. 我们为什么要用Kali Linux 由于Kali Linux具有以下特…

【脑筋急转弯系列】乒乓球称重问题

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

SpringBoot+Vue实现一个系统登录功能

1. 创建一个Spring Boot项目&#xff0c;添加Web和Security依赖。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency>2. 创建一个User实体类&#xff0c;用…

vue render函数的使用场景

render函数使用和详解_render函数如何使用-CSDN博客 上面链接写的很详细&#xff0c;有不懂的地方可以留言互相交流哈&#xff01;~~~~~~

蓝桥杯简介

蓝桥杯全国软件和信息技术专业人才大赛是由工业和信息化部人才交流中心举办的全国性IT学科赛事。这个比赛在全国范围内有着广泛的影响力&#xff0c;参赛人数已经累计超过40万人&#xff0c;涵盖了全国1200余所高校。在高校教育教学改革和创新人才培养方面&#xff0c;蓝桥杯大…

FDTD2018a安装问题记录

FDTD2018a安装问题记录 目录问题解决方案 目录 问题 解决方案 电脑名字如果是中文改成英文

编程笔记 html5cssjs 036 CSS概述

编程笔记 html5&css&js 036 CSS概述 一、什么是 CSS?二、样式非常重要三、CSS 语法四、CSS 注释五、示例 CSS (Cascading Style Sheets&#xff0c;层叠样式表&#xff09;&#xff0c;是一种用来为结构化文档&#xff08;如 HTML 文档或 XML 应用&#xff09;添加样式…

全局唯一ID实现方案——雪花算法

全局唯一ID实现方案——雪花算法 雪花算法原理 Snowflake&#xff0c;雪花算法是由Twitter开源的分布式ID生成算法&#xff0c;以划分命名空间的方式将 64-bit位分割成多个部分&#xff0c;每个部分代表不同的含义。而 Java中64bit的整数是Long类型&#xff0c;所以在 Java 中…

数据加密过程和数据加密的方法有哪些?

在信息日益发达的今天&#xff0c;数据安全已成为越来越受到关注的问题。数据加密技术作为保护信息安全的重要手段&#xff0c;对于维护个人隐私、企业机密以及国家安全具有重要意义。希望通过本文的学习&#xff0c;您能够对数据加密有一个全面的认识&#xff0c;并在实际应用…