数据结构复盘——第八章:排序

news2025/1/16 19:56:42

文章目录

    • 第一部分:各种排序方法的比较
    • 第二部分:插入排序
      • 1、直接插入排序
      • 2、折半插入排序
      • 3、希尔排序
    • 第三部分:交换排序
      • 1、冒泡排序
      • 2、快速排序
    • 第四部分:选择排序
      • 1、简单选择排序
      • 2、堆排序
        • 2.1 堆的概念
        • 2.2 堆的调整算法
        • 2.3 堆的构造
        • 2.4 堆排序的流程
        • 2.5 插入和删除元素
    • 第五部分:归并排序
    • 第六部分:基数排序
    • 第一到六部分小结

第一部分:各种排序方法的比较

在下面几种较常用的排序方法中,具有共同性质的可以用如下口诀记忆:(“些”就是希尔排序中“希”的谐音;“队”即“堆”的谐音)

  • 不稳定性:(情绪不稳定的时候)快些选好友来聊天
    • 算法的稳定性是指当待排序表存在两个关键字相同的元素时,排序前后两者的位置没有发生变化,那么就说这个排序算法是稳定的。它是算法的性质之一,并不能衡量算法的优劣,如果题目里要求了待排序表不能出现重复元素,那么该性质就无关紧要。
    • 快速排序、希尔排序、选择排序、堆排序都是不稳定的排序。
  • 时间快:(速度快就)快些归队
    • 快速排序、希尔排序、归并排序、堆排序都是平均时间复杂度较低的排序。
      各种排序方法汇总

第二部分:插入排序

在第一部分的表中所列的插入排序是指直接插入排序,在插入排序的大类下有三种排序算法:直接插入排序、折半插入排序、希尔排序。

1、直接插入排序

直接插入排序方法,概括一下就是:待排序的元素一个一个往前移,插到已排序元素中

  • 上面是从小到大排序(从大到小把下面的大于小于条件互换一下),使用顺序表时要预留下标为0的位置用于存储哨兵;
  • 对于还没有排序的元素,首先与它的前一个元素(属于已排序元素)进行比较:
    • 如果小于前一个元素的关键字,就将其复制为哨兵;然后在已经排序的元素中,从后往前比较找出插入的位置,将插入位置往后的元素(已经排序的元素,即被比较元素的前一个元素)全部后移一位,空出插入位后将哨兵位置保存的元素插入,之后继续对下一个元素重复上一级的比较操作;
    • 如果大于前一个元素的关键字,那么久让其保持在原位,继续对下一个元素重复上一级的比较操作。

直接插入排序适用于顺序表和链表,如果是使用链表时可以比较方便的执行插入操作,也不需要预留哨兵位置,直接修改指针即可。

2、折半插入排序

折半插入排序的步骤和直接插入排序是一样的,唯一的区别在于查找插入位置的过程采用折半查找,而不是按顺序逐个查找。

  • 在直接插入排序中,查找与移动操作是合并在一起的(一个循环里),所以在折半插入排序中首先将两个操作分离,同时采用折半查找加快查找速度;
  • 由于折半查找只适用于顺序表,所以折半插入排序也只适用于顺序表
  • 其平均时间复杂度和直接插入排序是一样的,同时也是稳定的排序方法,所以表格内并没有单独列举两种排序,而是合并称“插入排序”

3、希尔排序

希尔排序和折半插入排序一样,都是对直接插入排序的改进
我们在对直接插入排序的分析中发现,当待排序表基本有序数据量不大时,时间复杂度将会显著下降。为此希尔排序的基本思想就是:

① 先有规律的取出待排序表中的部分数据进行直接插入排序,利用其适合数据量不大的待排序表的特性;
② 在多次执行部分排序后待排序表已经变得基本有序,又满足了另一个适用条件,就可以再对整个待排序表使用一次直接插入排序。

  • 通常我们通过相隔某个“增量”来取数据组成子表,然后对子表进行直接插入排序后放回原表中;
  • 随后减小这个“增量”再重复上面的步骤,直到这个“增量”变为 1,此时相当于对整个待排序表进行一次直接插入排序;
  • 到目前为止,尚未求得一个最好的“增量序列”,常用的有: d i = { 5 , 3 , 1 } d_i=\{5,3,1\} di={531}

① 我们在选取的时候只需要注意“初始增量”不能大于表长
② 每趟排序都会操作到所有元素,且产生的子表个数等于“增量”值,在上图中“增量”为 5 时,{44}、{15}、{36}、{26}都单独构成一个子表
③ 如果某个子表中只含有一个元素,那么本趟排序这个元素的位置将保持不动,因为没有其他元素可以和它换位置。


第三部分:交换排序

在第一部分的表中所列的冒泡排序和快速排序,其同属于交换排序这一大类。两种交换排序法均适用于顺序表和链表。

1、冒泡排序

冒泡排序方法,简单概况一下就是:相邻两个数逐一比较

  • 从第一个元素开始,相邻的两个元素比较大小,如果逆序就进行交换操作:
    • 如果从小到大排序,那么前一个元素更大就是逆序;
    • 如果从大到小排序,那么后一个元素更大就是逆序;
  • 在每一趟排序结束后,当前序列的最后一个元素就是本趟的最大/最小元素,因此不再参与下一趟排序;
  • 待排序元素少于两个时,排序结束;换句话说,当待排序列中只剩最后两个逆序元素时,本趟排序是最后一次排序。

我们会发现,冒泡排序需要执行的趟数,在从小到大的排序中取决于最小元素在序列中的位置;在从大到小的排序中取决于最大元素在序列中的位置。这是因为通常这个元素是最后一个被交换到正确位置上的。

2、快速排序

快速排序基于分治法的思想,在待排序表中选出一个元素作为枢纽,将比它小/大的元素置于一侧,比它大/小的元素置于另一侧,这一过程就是在“分”;形成两个子表后,再分别对两个子表进行快速排序,这一过程就是在“治”。不难看出快速排序中包含了递归操作

  • 依据严蔚敏老师的《数据结构》,我们通常选择当前表的第一个元素作为枢纽;
  • 每一次划分操作完成后,需要返回划分完成的待排序表以及枢纽的位置(顺序表的数组下标或链表指针),然后基于枢纽的位置可以知道左子表的终止位置(枢纽位置-1)右子表的起始位置(枢纽位置+1),从而分别对两个子表执行递归操作;
  • 直到子表内只包含一个元素时,视为排序结束。

不难看出划分操作才是快速排序的核心,通常划分函数需要获取待排序表、最低位置(low)、最高位置(high)

  • 最低位置和最高位置指示了需要操作的元素范围,这样我们就可以只分配一个表的存储空间,仅通过改变这两个位置来指示对表的哪一部分进行划分(即明确子表的位置)。
  • 从小到大排序为例,划分主要包含过程:
    • 假设两个指针 i 和 j,初始时分别指向 low 和 high;
    • 此时指针 i 所指的元素是枢纽,然后让指针 j 从后往前找到第一个小于枢纽的元素,交换两者的位置;
    • 此时指针 j 所指的元素是枢纽,我们再让指针 i 从前往后找到第一个大于枢纽的元素,交换两者的位置;
    • 重复上面两个步骤,直到指针 i 和 j 指向同一个元素,即枢纽元素。

① 上面的步骤中如果是从大到小排序,则指针 j 负责找大于枢纽的元素交换;指针 i 负责找小于枢纽的元素交换。
② 在实际操作中如果采用数组,首先指针 i 和 j 就是指数组下标;其次通常会单独保存枢纽的关键字,然后把交换操作变为赋值操作,在确定了枢纽最终的位置后,再把之前记录的关键字存储到该位置。


第四部分:选择排序

第一部分表中的选择排序就是指简单选择排序,在选择排序这一大类下还包含堆排序,是选择排序的重点。

1、简单选择排序

简单选择排序的思想非常简单:在序列中找到最小元素,依次换到序列最前端

  • 简单选择排序的每一趟排序就包含两个步骤:查找(比较)交换
  • 以第一趟为例:
    • 从第一个元素开始,顺序比较待排序表中的每一个元素,找出最小元素;
    • 将最小元素和第一个元素互换位置(如果最小元素不在第一个),然后将第一个元素从待排序表中去除。
  • 之后的每一趟都查找当前待排序表中最小的元素,即初始待排序表中第 i 小的元素,将其和第 i 个元素交换位置(如果该元素不在第 i 个),然后将第 i 个元素从待排序表中去除;
  • 直到第 n-1 趟做完,即待排序表元素只剩下一个

第 i 趟开始时待排序表元素个数为:n-i+1
第 i 趟结束后待排序表元素个数为:n-i
每一趟查找最小元素都需要比较n(n-1)/2次,与初始状态无关,所以简单选择排序很费时,不常用。

2、堆排序

堆排序比较复杂,在介绍具体排序过程之前需要先说几个概念。

2.1 堆的概念

在堆排序中,我们所说的“堆”简单来说就是完全二叉树,只不过这个完全二叉树有个特点:它的非叶子结点要么大于任意一个孩子结点,要么小于任意一个孩子结点。

  • 如果其非叶子结点大于任意一个孩子结点,就称其为大顶堆
  • 反之如果其非叶子结点小于任意一个孩子结点,就称其为小顶堆

大小顶堆示意图

对于大顶堆来说,其值最大的结点是根节点;
对于小顶堆来说,其值最小的结点也是根节点;
因此根节点也是堆排序算法实现的核心,所以堆排序也属于选择排序。

很多人可能会有一个问题:既然“堆”就是完全二叉树,那为什么还要叫“堆排序”而不是“完全二叉树排序”?
换句话说,堆不是动态分配空间的栈吗,怎么就变成完全二叉树了?

  • 要解答这个疑惑,首先要知道堆排序是指使用堆结构对一个序列进行排序。也就是说我们在排序过程中使用到的是堆的两大特性:
    • 和栈一样“出口”和“入口”在同一侧;
    • 可以在执行过程中动态分配空间,即随时更新。

这就是为何在堆排序中涉及到了插入元素和删除元素,堆排序的一大优势就是能处理一个随时会有更新的序列
同时也暗示了我们堆排序的插入元素和删除元素需要在同一侧进行。

  • 然后我们可以思考一下为什么用完全二叉树表示堆
    • 完全二叉树不同于普通二叉树,它限制了结点必须按从上到下、从左到右的顺序逐层编号,对树的插入和删除操作只能从最后的序号开始,保障了增删过程不会影响其他结点的编号。这样仅能在“树尾”进行操作就类似于堆栈里仅能在栈顶进行操作
    • 同时完全二叉树又不像满二叉树一样要求叶子结点全在最下层,进而导致满二叉树的增删就必须操作一整层的结点;而完全二叉树却可以实现单个结点的增删。
    • 并且我们都知道二叉树是可以用顺序表(数组)存储的,在存储结构上也满足堆栈的需求。
    • 而逐层编号使完全二叉树上层元素与下层元素在数组中的位置存在固定的规律,方便根据孩子结点定位双亲结点(孩子是 i ,则双亲是 ⌊ i / 2 ⌋ \lfloor i/2 \rfloor i/2,向上调整法中涉及)以及根据双亲结点定位孩子结点(结点是 i ,则左孩子是2i,右孩子是 2i+1,向下调整法中涉及)

需要补充一点,堆并不一定都是完全二叉树,只是在这里我们讨论的堆都可以表示为完全二叉树。
例如:二项堆、斐波那契堆就不属于二叉树,这里就不展开说明。

2.2 堆的调整算法

堆的调整算法是堆排序的核心所在。通常在实际场景中,我们会被给予一个数组,该数组对应了一个完全二叉树,我们需要把它按实际需求进一步调整为“大顶堆”或“小顶堆”。

  • 执行一次调整算法就相当于进行一次选择,如果实际需求是每次选出最小元素那么就调整为“小顶堆”,反之调整为“大顶堆”;
  • 调整完成后的根节点就是我们要选取的元素。
    具体的调整算法分为两种,下面以构建小顶堆为例介绍:

① 向下调整算法:

  • 从根节点开始,先计算其左右孩子的下标,然后比较对应下标的元素大小,选出较小的一方记录下标。(注意第 i 个结点的下标为 i-1,其左孩子下标为 j=2i-1=2(i-1)+1,其右孩子下标为 j+1=2i。)
  • 再将较小的一方与根结点比较,如果孩子结点小于根结点就交换两者的位置。
  • 然后将此时位于该记录位置的结点作为下一个待比较的结点,重复与根节点相同的操作。直到计算出的孩子结点下标大于等于数组长度 n,说明当前待比较的结点已经是叶子结点。

不难发现,在进行一轮向下调整算法后,根节点处的元素似乎是完全二叉树第1、2层里的最小值,似乎并没有达到找出整个序列最小值,即构建小顶堆的效果;
这是因为对完全二叉树使用该算法进行小顶堆构造时,要求当前二叉树的左右子树已经是小顶堆,这是很难实现的一种理想情况。(解决办法将在堆构造里介绍)

② 向上调整算法:

  • 与向下调整算法相反,用最后一个序号的结点,先计算其双亲结点的下标并记录。(注意第 i 个结点的下标为 i-1,其双亲下标为 j=[(i-1)-1]/2。)
  • 再将双亲结点与该结点比较,如果双亲结点大于当前结点就交换两者位置。
  • 然后将更新位置后的该结点重复上面的操作。直到计算出的双亲结点下标小于 0,说明插入结点已经来到根结点位置。

通常向上调整算法用于已经是小/大顶堆的完全二叉树,是其插入新元素后进行的调整。因为该方法不会与兄弟、堂兄弟结点进行比较,这就要求整个二叉树的每个非叶子结点都要小于/大于它的孩子结点,这样比较才有代表性。
所以当第二步发现插入元素结点不小于/不大于其双亲结点时,就不会发生交换,插入元素留在最后一个结点位置

2.3 堆的构造

堆的构造即“小/大顶堆”的构造,以下简称为“堆”。我们在前面说了只有当完全二叉树的左右子树本身就是“堆”时,才可以通过一次向下调整算法实现。
这听起来似乎很难实现,但是有一个特例——叶子结点:单个结点本身就是一个二叉树,所以它也算一个“堆”

  • 我们可以从最后一个非叶结点开始(也可以从最后一个结点开始,只不过,如果从最后一个结点开始效率不高),按序号对每一个结点使用堆的向下调整,直到根节点,这样就能构造一个“堆”了。
  • 如果一个完全二叉树有 n 个结点,那么它的最后一个非叶结点编号为 n 2 − 1 \frac{n}{2}-1 2n1
2.4 堆排序的流程

在完成堆的构造之后,虽然不能保障堆是完全有序的,但是却能保证根结点是最大的(大顶堆)最小的(小顶堆)。于是:

我们可以每次选出一个根结点,将其划到有序序列里面,并对剩余结点进行调整,使剩余部分再次成为一个堆,然后对这个堆再进行上述操作,这也就是为什么堆排序属于选择排序的原因

但是树是不能没有根节点的,也就是说我们并不能直接把根节点去除。因此:

  • 选出根节点后,让根节点与此时无序序列中的最后一个结点(第一次选择的时候就和最后一个结点交换,第二次选择的时候和倒数第二个结点交换,以此类推)进行交换
  • 此时根节点到达下方成为有序序列中的一员,将它从待排序列中去除
  • 而原本最后的结点到达根节点位置,由于这个结点到来,堆的结构被破坏,由于仅仅是当前的根节点破坏了堆的结构,所以只对该结点使用向下调整算法,重新生成一个堆,然后重复上述操作。

以小顶堆为例,经过上述操作,每次根节点到达有序序列的前一个位置,于是整个数组成为了降序排列;
以大顶堆为例,经过上述操作,整个数组就成为了升序排列。
因此:需要升序排序,就建立大顶堆,需要降序排序,就建立小顶堆。B:堆排序演示

堆排序流程展示图

2.5 插入和删除元素

① 插入:
将新元素插入到最后一个结点的下一位置(尾插到序列最后),由于这个结点的到来可能再次破坏了堆的结构(仅破坏了一部分),因此使用堆的向上调整算法重新建堆。
插入流程图

② 删除:
堆的删除默认只能删除第一个元素,因为删除其他元素没有意义(堆排序过程中去除根节点就是用到了删除)。将最后一个元素与第一个元素交换,然后对新的根使用堆的向下调整算法重新建堆。
删除流程图


第五部分:归并排序

和希尔排序一样,归并排序采用的是分治法的思想。二者其实都可以说是一种逻辑方法,底层实现还是需要依靠其他的排序,例如希尔排序“分”之后常采用插入排序进行“治”,所以将其归类在插入排序下。

  • 归并排序的逻辑就是对序列不断进行划分,直到划分后产生的子序列是有序的,或者子序列只包含一个元素,就可以开始对序列进行合并(即“归并”——回归合并成原长序列,并且在合并的过程中排序(找出两个待合并序列中最小的元素,把它先加入到合并序列中)。
  • 通常归并过程中采取两两归并,这样的排序方法被称为2路归并排序。也有多路归并,但2路归并使用的较多。

虽然归并排序的逻辑非常好理解,但是其代码较为复杂,需要使用递归。且排序过程不能在原序列中进行,需要额外分配空间,其空间复杂度为O(n),是所有排序中最大的

  • 在排序函数中先递归调用自身进行拆分过程,直到拆分为单个元素,再开始继续运行递归语句后的合并语句,合并结束就完成一次递归。
  • 合并过程中对两个序列(实际存储在一个数组中,通过两对指针区分)中的元素逐个比较,找出其中的最小/最大值复制到另一个序列(数组)中,所有元素都复制进去就完成了合并与排序。

第六部分:基数排序

基数排序是一种很特别的排序,它不基于对关键字整体的比较而移动排序,而是基于关键字“各位”的大小进行排序。如果把一个关键字的“各位”单独视为一个关键字,那么就可以说基数排序是借助多关键字排序的思想进行单逻辑关键字排序

  • 为了实现基数排序,我们需要用到 r 个队列,其中 r 的值取决于关键字各个位的取值范围大小(例如每一位是数字的话,取值范围是 0-9,则 r=10)。
  • 先后顺序依次对各个位进行大小比较,然后将关键字中当前位相同的元素按序列中的先后顺序,依次加入到对应的队列中,直到所有元素都入队。
  • 然后按队列的顺序依次将各个队列首尾相接,得到一个完整的序列,完成一趟排序。
  • 关键字有多少位就要进行多少趟排序,各个位的先后顺序可以按照最高位优先法(MSD)最低位优先法(LSD)
    • 如果是三位数字,最高位优先法就按照“百十个”的顺序进行三趟入队再连接的操作,从而完成基数排序;最低位优先法则按照“个十百”的顺序进行三趟入队再连接的操作,从而完成基数排序。

r 个队列会被重复使用,所以空间复杂度为O(r);
因为要频繁分配(入队)和收集(首尾相接),所以常采用链式结构


第一到六部分小结

各种排序算法的比较都在第一部分。上面我们提到的排序算法都属于内部排序算法
① 内部排序:
内部排序是排序的基础,在排序的过程中,把所有元素调到内存中进行排序,称之为内部排序。
② 外部排序:
在数据量大的时候,只能分块排序,但是块和块排序不能保证有序,外部排序用读写次数来衡量其效率

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

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

相关文章

计算机网络-计算机网络体系结构-网络层

目录 一、IPV4 IP数据报格式 *IP 数据报分片 *IPV4地址 分类 网络地址转换(NAT) 二、子网划分与子网掩码 *CIDR *超网 协议 ARP协议 DHCP协议 ICMP协议 三、IPV6 格式 IPV4和IPV6区别 地址表示形式 四、路由选择协议 RIP(路由信息协议) OPSF(开发最短路径优…

为什么高精度机器人普遍使用谐波减速器而不是普通减速器?

机器人作为一种能够代替人类完成各种工作的智能设备,已经广泛应用于工业生产、医疗卫生、军事防卫等领域。其中,机器人的关节传动系统是机器人运动的核心,而减速器作为关节传动系统中的重要组成部分部分,对机器人的性能和技术水平…

volatile-两大特性(可见性、有序性)、内存屏障

6.1 被volatile修饰的变量有两大特点 ● 特点:○ 可见性○ 有序性:有排序要求,有时需要禁重排● 内存语义:○ 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中○ 当读一个vola…

Zabbix“专家坐诊”第207期问答汇总

问题一 Q:不小心把host表删除了,怎么处理?现在使用的zabbix 4.0.3的server,agent是4.2.1,能不能不动agent的情况下升级server版本,重新部署? A:数据库有备份话恢复即可,…

当下流行的编程语言

在任何时候,一些编程语言都会把大量的开发人员变成热情的布道者,试图说服世界其他地方的人相信它的伟大。 当热起来的时候,这种语言可能会成为行业标准,但其他时候,这种受欢迎程度就会消失。 1、数据的由来 每年Stack…

关于vue2回显表格数据忽略中间空格补全

关于vue2回显表格数据忽略中间空格补全 发现问题解决 发现问题 发现回显数据中间空格忽略 解决 在全局中修改在页面内修改 主要案例主要是在页面内修改 ::v-deep .cell{white-space: pre; }

COMSOL超声换能器聚焦声场仿真

超声聚焦 超声聚焦广泛应用于各类工业设备与技术中,例如我们熟悉的无损检测(NDT)和医学成像。高强度聚焦超声(HIFU)是此技术的一项临床应用,它利用探头将大部分能量集中到目标组织区域,使组织发…

uniapp实现简单的九宫格抽奖(附源码)

效果展示 uniapp实现大转盘抽奖 实现步骤: 1.该页面可设置8个奖品,每个奖品可设置中奖机会的权重,如下chance越大,中奖概率越高(大于0) // 示例代码 prizeList: [{id: 1,image: "https://img.alicdn…

【好书推荐】深入理解现代JavaScript

作者介绍 T. J. Crowder是一位拥有30年经验的软件工程师。在他的整个职业生涯中,他至少有一半时间是在使用JavaScript从事开发工作。他经营着软件承包和产品公司Farsight Software。他经常在Stack Overflow上为人们提供帮助,他是十大贡献者之一和JavaScr…

Java身份证实名认证-阿里云API 【姓名、身份证号】

1. 阿里云API市场 https://market.aliyun.com/products/57126001/cmapi00053442.html?spm5176.2020520132.101.3.a6217218nxxEiy#skuyuncode47442000022 购买对应套餐 2. 复制AppCode https://market.console.aliyun.com/imageconsole/index.htm#/?_kl85e10 云市场-已购买服…

软件工程与计算总结(十八)代码设计

目标,编写高质量的代码,本章节只介绍在易读性、易维护性和可靠性方面的最基本的代码设计方法~ 一.设计易读的代码 如果编写的代码易读性不好,在维护阶段的多次阅读过程中,将会消耗维护人员大量的精力;研究实验表明&a…

提升品牌形象:利用OLED透明拼接屏进行品牌展示

在当今数字化时代,OLED透明拼接屏作为一项引人注目的新兴技术,正逐渐改变着各行各业的显示方式。 OLED透明拼接屏技术 OLED透明拼接屏采用有机发光二极管(OLED)技术,能够提供卓越的显示效果。 与传统的液晶显示屏相比…

ios UIDocumentPickerViewController 实现TEXT、DOC、PDF等文档读取

文章目录 一、前言二、iCould相关配置三、功能实现3.1 UIDocumentPickerViewController 选取控制器3.2 读取文件一、前言 最近正在研发的项目有一个需求: 允许用户将iCloud中的文档上传,实现文件的流转。 以前接触的项目对于资料类的上传大多是仅限于图片与视频。对于文档类…

WebDAV之π-Disk派盘 + 密码键盘

密码键盘是一款密码管理器,可以存储和管理需要受保护的数据。为方便日常使用,同时也是一款安全输入法,帮您安全便捷地填写账号密码、通用内容、卡包信息。 密码键盘使用军事级的 PBKDF2 有损加密算法保护您的根密码,使用军事级的 AES 加密算法保护您的存储数据。云端再额外…

车载视频如何转换视频格式

当你收集了多种视频想在车内进行播放,它们可能不会自动播放。你有可能会在屏幕上看到一条消息,显示“文件格式不受支持”,这是因为这些视频可能采用了你的汽车无法识别的格式。 那我们如何才可以转换为车载播放器上运行的最重要且最广泛使用…

【UE4 反射系统】 UCLAS UFUNCTION UPROPERTY 宏简单解析 持续更新

目录 0 引言1 C如何实现反射机制1.1 使用代码生成工具实现反射机制 2 UE4的反射系统2.1 ****.generated.h头文件2.2 GENERATED_BODY()2.3 反射宏 UCLASS 等2.4 UHT和UBT 3 基本宏的使用3.1 UCLASS3.2 UFUNCTION3.3 UPROPERTY 🙋‍♂️ 作者:海码007&…

nginx tomcat 动静分离

动静分离: 访问静态和动态页面分开 实现动态和静态页面负载均衡。 五台虚拟机 实验1,动静分离 思路: 需要设备:三台虚拟机 一台nginx 代理又是静态 两台tomcat 请求动态页面 在全局模块中配置upstream tomcat 新建location…

springboot书籍学习平台springboot48

大家好✌!我是CZ淡陌。一名专注以理论为基础实战为主的技术博主,将再这里为大家分享优质的实战项目,本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目,希望你能有所收获,少走一些弯路…

C++ 异形窗口

本文参考:Duilib源码 原理:分层窗口,通过定时器和手动触发绘画窗口 由于GDI对透明通道支持不是很好,最好选择GDI进行绘画。 1.设置窗口属性WS_EX_LAYERED和绘画定时器 DWORD dwStyle ::GetWindowLong(hWnd, GWL_EXSTYLE);SetWi…

es6(八)—— set(集合) 和map的使用

ES6的系列文章目录 第一章 Python 机器学习入门之pandas的使用 文章目录 ES6的系列文章目录一、set(集合)0. 定义1. 基本使用2.常用方法(1)代码(2)效果(3)遍历 二、map0. 定义1. 基…