归并排序 (递归实+非递归)

前言

归并排序是一种逻辑很简单,但是实现有点难度的排序,尤其是非递归,对于区间的把握更是需要一点逻辑的推导,但是没有关系,轻松拿捏

归并排序gif

归并排序单趟实现

1,创建tmp数组,

2,递归到最小值进行排序,同时拷贝到题目破数组里面

3,这里的关键逻辑实现是,递归回来的时候进行排序

4,最后我们把数值重新拷贝回去原数组

注意:这里的取值我们直接取中间值进行递归,一分为二

代码实现注意事项

关于创建tmp数组,我们需要创建的数组应该是等大的,要么就进行初始化。

  • 因为我们是malloc出来的空间,
  • 为什么malloc出来的空间必须是等大的,因为这里我们用的是Visual Studio 2022的编译器,这个编译器里面是不支持可变长数组的。所以我们需要用malloc,或者realloc来实现创建空间,也就是动态内存的分配
  • malloc创建空间的时候,空间里面是有随机值的
  • realloc创建的空间里面的数值是0
  • 所以一旦我们进行排序的时候,如果我们不进行覆盖的话,这里面的数值也会参与排序,从而导致数值出现偏差

这里对于这一点不清楚的可以看看之前写的文章,因为当时写这个文章的时候,刚接触编程,所以讲述的主要是以语法和一些简单的性质特点,希望可以起到帮助

  • 动态内存函数的使用和综合实践-malloc,free,realloc,calloc_内存申请函数-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/137075045

代码实现

//归并排序(递归实现)子函数(实现函数)
void _MergeSort01(int* a, int* tmp, int begin, int end)
{if (begin >= end)return;int mini = (begin + end) / 2;//注意,这里关键在于,区间的传递,//不应该传递【left,mini-1】【mini,right】//  应该传递【left,mini】【mini+1,right】//递归左右区间, 递归到最小个数进行对比_MergeSort01(a, tmp, begin, mini);_MergeSort01(a, tmp, mini + 1, end);int begin1 = begin, end1 = mini;int begin2 = mini + 1, end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}//数据从tmp拷贝回去数组a里面memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
//归并排序(递归实现)(创建tmp函数)
void MergeSort01(int* a, int n)
{//这里n传递过来的是个数int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("MergeSort01:tmp:err:");exit(1);//正常退出}_MergeSort01(a, tmp, 0, n - 1);free(tmp);tmp = NULL;
}

解释:

  1. _MergeSort01 函数:这是归并排序的递归子函数,它接收三个参数:数组 a,临时数组 tmp,以及要排序的数组区间 [begin, end]

    • 如果 begin 大于或等于 end,则表示区间内没有元素或只有一个元素,不需要排序,直接返回。
    • 计算区间的中点 mini,将数组分为两部分:[begin, mini] 和 [mini + 1, end]
    • 对这两部分分别递归调用 _MergeSort01 函数进行排序,直到每个子区间只有一个元素。
  2. 合并过程

    • 初始化两个指针 begin1 和 end1 指向第一个子区间的开始和结束位置,begin2 和 end2 指向第二个子区间的开始和结束位置。
    • 使用一个索引 i 指向 tmp 数组的当前位置,用于存放合并后的有序元素。
    • 使用两个 while 循环来比较两个子区间的元素,将较小的元素复制到 tmp 数组中,并移动对应的指针。
    • 如果第一个子区间的所有元素已经被复制到 tmp 中,但第二个子区间还有剩余元素,将这些剩余元素复制到 tmp 数组的剩余位置。
    • 同理,如果第二个子区间的所有元素已经被复制,将第一个子区间的剩余元素复制到 tmp
  3. 拷贝回原数组

    • 使用 memcpy 函数将 tmp 数组中从索引 begin 开始的元素复制回原数组 a。这里计算了需要复制的元素数量为 end - begin + 1,乘以 sizeof(int) 来确定字节数。
  4. MergeSort01 函数:这是归并排序的初始化函数,接收数组 a 和数组的长度 n

    • 首先,使用 malloc 分配一个临时数组 tmp,大小为 n 个 int
    • 如果内存分配失败,使用 perror 打印错误信息,并调用 exit(1) 退出程序。
    • 调用 _MergeSort01 函数,传入数组 a、临时数组 tmp 和整个数组的区间 [0, n - 1] 进行排序。
    • 排序完成后,使用 free 释放 tmp 所占用的内存,并设置 tmp 为 NULL

归并排序的时间复杂度为 O(n log n),在大多数情况下表现良好,尤其是当数据量大且需要稳定排序时。不过,由于它需要额外的内存空间(如这里的 tmp 数组),在内存受限的情况下可能不是最佳选择。

递归排序(非递归解决)

前言

这里递归排序的非递归方式还是比较有难度的,所以需要多次观看两遍,我也会多次详细的讲解,促进知识的理解

非递归实现gif

非递归实现单趟实现

这里的关键的在于对于区间的理解

尤其是

            //但是此时可能产生越界行为,我们可以打印出来看一下
            //产生越界的
            //begin1, end1, begin2, end2 ->                       end2->[,][,15]
            //begin1, end1, begin2, end2 ->               begin2, end2->[,][12,15]
            //begin1, end1, begin2, end2 ->         end1, begin2, end2->[,11][12,15]
            //解决
            //begin2, end2区间越界的,我们直接下一次进行递归就可以
            //end2,我们重新设定新的end2的区间

所以我们需要对这个区间进行解决

代码的逐步实现

这里代码的实现,因为非递归实现是有难度的,所以这里我不会直接给出答案,而是逐步的写出步骤,促进理解

1,创建拷贝空间tmp,设定gap,一组假设初始1个数值,当然初始的时候,实际也是一个数值

//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{//这里n传递过来的是个数int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("MergeSort01:tmp:err:");exit(1);//正常退出}//假设初始化起始是一组int gap = 1;
}

2,区间的设定

这里依旧是两个区间,我们根据这个区间设定的数值

这里是很关键的一点

    int begin1 = 0, end1 = 0 + gap - 1;
    int begin2 = 0 + gap, end2 = 0 + 2 * gap - 1;

//假设此时就两个数值,一组是一个数值,也就是这两个数值进行最小对比

//begin1左区间的初始位置,肯定是0

//end1左区间的结束位置。

  • 也就是第一个区间的结束位置,也就是一组,
  • -1是因为一组是实际的个数
  • +0,这里因为加的是第一个区间起始位置
  • 因为我们也可能是2-4区间的,不可能只是这一个区间

//begin2第二个区间的起始位置,这里还是比较好理解的,只要理解了end1

//end2第二个区间的结束位置,这里同理,

  • 我们有可能是2-4区间的,不只是0-1或者0-2区间的。而且随着第一次排序的结束,第二次排序就变成两个合并了,继续和另外的对比,所以是变化的
  • 0也就是初始位置
  • 2*gap,因为这里是需要加上中间两组,
  • 最后-1,因为gap是个数,不是下标
//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{//这里n传递过来的是个数int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("MergeSort01:tmp:err:");exit(1);//正常退出}//假设初始化起始是一组int gap = 1;int begin1 = 0, end1 = 0 + gap - 1;int begin2 = 0 + gap, end2 = 0 + 2 * gap - 1;
}

3,排序的实现


//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{//这里n传递过来的是个数int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("MergeSort01:tmp:err:");exit(1);//正常退出}//假设初始化起始是一组int gap = 1;while (gap < n){//递归到最小值,假设一组是一个,也就是此时是最小值进行比较并且入数组,关键是找出区间//这里一次跳过两组for (int i = 0; i < n - 1; i = i + gap * 2){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}}gap *= 2;}
}

4,区间的拷贝

这里区间的拷贝我们需要注意的是我们拷贝的是区间,并且我们需要一边拷贝一边拷贝回去,为什么需要一边排序一边拷贝回去,在接下来的越界行为里面我们会进行讲解

最后需要关注的点是,关于拷贝空间的大小,一定是end2-begin1,因为这两个区间是排序成功然后进入到tmp里面

所以我们需要从tmp拷贝回去,不能弄错了


//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{//这里n传递过来的是个数int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("MergeSort01:tmp:err:");exit(1);//正常退出}//假设初始化起始是一组int gap = 1;while (gap < n){//递归到最小值,假设一组是一个,也就是此时是最小值进行比较并且入数组,关键是找出区间//这里一次跳过两组for (int i = 0; i < n - 1; i = i + gap * 2){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}//拷贝回a数组里面,这里的区间是end2-begin1之间的区间memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}
}

5,解决越界行为

这里我们可以很清楚的看到哪些位置会产生越界行为

这里我们处理一下之后,我们发现就解决了


            //产生越界的区间有
            //begin1, end1, begin2, end2 ->                       end2->[,][,15]
            //begin1, end1, begin2, end2 ->               begin2, end2->[,][12,15]
            //begin1, end1, begin2, end2 ->         end1, begin2, end2->[,11][12,15]
            //解决
            //begin2, end2区间越界的,我们直接下一次进行递归就可以
            //end2,我们重新设定新的end2的区间


//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{//这里n传递过来的是个数int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("MergeSort01:tmp:err:");exit(1);//正常退出}//假设初始化起始是一组int gap = 1;while (gap < n){//递归到最小值,假设一组是一个,也就是此时是最小值进行比较并且入数组,关键是找出区间//这里一次跳过两组for (int i = 0; i < n - 1; i = i + gap * 2){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//但是此时可能产生越界行为,我们可以打印出来看一下//产生越界的//begin1, end1, begin2, end2 ->                       end2->[,][,15]//begin1, end1, begin2, end2 ->               begin2, end2->[,][12,15]//begin1, end1, begin2, end2 ->         end1, begin2, end2->[,11][12,15]//解决//begin2, end2区间越界的,我们直接下一次进行递归就可以//end2,我们重新设定新的end2的区间printf("[begin1=%d,end1=%d][begin2=%d,end2=%d]\n", begin1, end1, begin2, end2);if (begin2 > n){break;}if (end2 > n){//记得这里需要是n-1不能是n,因为end2是下标end2 = n-1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}//拷贝回a数组里面,这里的区间是end2-begin1之间的区间memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}
}

时间复杂度

时间复杂度分析如下:

  1. 分解阶段:归并排序首先将原始数组分解成多个子数组,每个子数组的大小为1。这个过程是线性的,时间复杂度为 O(n),其中 n 是原始数组的大小。

  2. 合并阶段:在合并阶段,算法将这些有序的子数组逐步合并成更大的有序数组。每次合并操作都需要将两个有序数组合并成一个更大的有序数组,这个过程的时间复杂度是 O(n)。

  3. 重复合并:归并排序的合并阶段会重复进行,直到所有的元素都被合并成一个有序数组。合并的次数可以看作是对数级的,具体来说,是 log2(n) 次。

综合以上两点,归并排序的总时间复杂度主要由合并阶段决定,因为分解阶段是线性的,而合并阶段虽然每次都是线性的,但由于重复了对数级次数,所以总的时间复杂度是:

𝑂(𝑛)+𝑂(𝑛log⁡𝑛)

由于在大 O 符号中,我们只关注最高次项和系数,所以归并排序的时间复杂度通常被简化为:𝑂(𝑛log⁡𝑛)

这意味着归并排序的时间效率与数组的大小成对数关系,是相当高效的排序算法。此外,归并排序是一种稳定的排序算法,它保持了相等元素的原始顺序。然而,它需要与原始数组同样大小的额外空间来存储临时数组,这可能会在空间受限的情况下成为一个问题。

代码实现


//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{//这里n传递过来的是个数int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("MergeSort01:tmp:err:");exit(1);//正常退出}//假设初始化起始是一组int gap = 1;while (gap < n){//递归到最小值,假设一组是一个,也就是此时是最小值进行比较并且入数组,关键是找出区间//这里一次跳过两组for (int i = 0; i < n - 1; i = i + gap * 2){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//但是此时可能产生越界行为,我们可以打印出来看一下//产生越界的//begin1, end1, begin2, end2 ->                       end2->[,][,15]//begin1, end1, begin2, end2 ->               begin2, end2->[,][12,15]//begin1, end1, begin2, end2 ->         end1, begin2, end2->[,11][12,15]//解决//begin2, end2区间越界的,我们直接下一次进行递归就可以//end2,我们重新设定新的end2的区间printf("[begin1=%d,end1=%d][begin2=%d,end2=%d]\n", begin1, end1, begin2, end2);if (begin2 > n){break;}if (end2 > n){//记得这里需要是n-1不能是n,因为end2是下标end2 = n-1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}//拷贝回a数组里面,这里的区间是end2-begin1之间的区间memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}
}

解释:

  1. 内存分配:首先,函数使用 malloc 分配一个与原数组 a 同样大小的临时数组 tmp。如果内存分配失败,使用 perror 打印错误信息,并调用 exit 退出程序。

  2. 初始化间隔gap 变量初始化为 1,表示每个元素自身作为一个有序的子数组。

  3. 外层循环:使用 while 循环来逐步增加 gap 的值,每次迭代将 gap 乘以 2。这个循环继续进行,直到 gap 大于或等于数组的长度 n

  4. 内层循环:对于每个 gap 值,使用 for 循环遍历数组,计算每次合并操作的起始索引 i。每次迭代,i 增加 gap * 2,以确保每次处理的子数组间隔是 gap

  5. 计算子数组边界:在每次迭代中,计算两个子数组的起始和结束索引:begin1end1begin2end2。这两个子数组的间隔是 gap

  6. 处理数组越界:如果 begin2 大于数组长度 n,循环将终止。如果 end2 超出了数组的界限,将其调整为 n-1

  7. 合并子数组:使用 while 循环来合并两个子数组。比较 a[begin1]a[begin2],将较小的元素复制到 tmp 数组中,并相应地移动索引。当一个子数组的所有元素都被复制后,使用另外两个 while 循环复制剩余子数组中的元素。

  8. 拷贝回原数组:使用 memcpytmp 中的有序子数组复制回原数组 a。这里,拷贝的区间是从索引 iend2,拷贝的元素数量是 end2 - i + 1

  9. 更新间隔:外层循环的 gap 每次迭代后翻倍,这样在每次迭代中,处理的子数组的大小逐渐增加,直到整个数组被排序。

非递归的归并排序在某些情况下可能比递归版本更有效,因为它避免了递归调用的开销,尤其是在深度很大的递归树中。然而,它需要更多的迭代来逐步构建有序数组,因此在实际应用中,选择哪种实现取决于具体的需求和场景。

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

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

相关文章

javaWeb项目-在线考试系统详细功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、Java简介 Java语…

「Qt Widget中文示例指南」如何实现一个滑动条(一)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 滑动条示例展示了如…

轻轻松松上手的LangChain学习说明书

本文为笔者学习LangChain时对官方文档以及一系列资料进行一些总结&#xff5e;覆盖对Langchain的核心六大模块的理解与核心使用方法&#xff0c;全文篇幅较长&#xff0c;共计50000字&#xff0c;可先码住辅助用于学习Langchain。 一、Langchain是什么&#xff1f; 如今各类AI…

昇思25天学习打卡营第1天|快速入门

学AI还能赢奖品&#xff1f;每天30分钟&#xff0c;25天打通AI任督二脉 (qq.com) 本节通过MindSpore的API来快速实现一个简单的深度学习模型。若想要深入了解MindSpore的使用方法&#xff0c;请参阅各节最后提供的参考链接。 import mindspore from mindspore import nn from …

项目训练营第一天

项目训练营第一天 springboot后端环境搭建 1、首先需要找文章下载好tomcat、JDK、maven、mysql、IDEA。&#xff08;软件下载及环境变量配置略&#xff09; 2、在下载好的IDEA中&#xff0c;选择新建spring initial项目&#xff0c;选定java web&#xff0c;即可新建一个spri…

win11 之下载安装 allure

1. 下载 https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/2.25.0/allure-commandline-2.25.0.zip 2. 配置系统变量 path 下添加解压后的bin目录 3. 验证是否安装成功 输入 allure

Mac 安装HomeBrew(亲测成功)

1、终端安装命令&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"执行后&#xff0c;没有安装git&#xff0c;会先安装&#xff0c;安装后再执行一下命令。 2、根据中文选择源安装 3、相关命令 查看版本号&a…

python flask使用flask_migrate管理数据库迁移

&#x1f308;所属专栏&#xff1a;【Flask】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您的点…

一篇文章了解常用排序算法

排序 文章目录 排序直接(插入)排序InsertSort思想实现方法&#xff1a; 希尔排序ShellSort&#xff08;可过OJ)思想预排序gap的作用整体代码 选择排序SelectSort思想完整代码 堆排序HeapSort(可过OJ)思想大根堆向下调整 完整代码 冒泡排序BubbleSort快速排序&#xff08;快排&a…

Unity URP 仿原神角色渲染过程记录

想学一下NPR渲染&#xff0c;话不多说&#xff0c;先搞一只芙再说&#xff0c;边做边学 一、资源整理 终于是把东西全都集齐了 1、纹理设置 首先要把将Diffuse和Lightmap的压缩改成"无"或"高质量"。 法线贴图的纹理类型改成"法线贴图"。 除颜…

HarmonyOS 角落里的知识 —— 状态管理

一、前言 在探索 HarmonyOS 的过程中&#xff0c;我们发现了许多有趣且实用的功能和特性。有些总是在不经意间或者触类旁通的找到。或者是某些开发痛点。其中&#xff0c;状态管理是ArkUI开发非常核心的一个东西&#xff0c;我们进行了大量的使用和测试遇到了许多奇奇怪怪的问…

Android平台下VR头显如何低延迟播放4K以上超高分辨率RTSP|RTMP流

技术背景 VR头显需要更高的分辨率以提供更清晰的视觉体验、满足沉浸感的要求、适应透镜放大效应以及适应更广泛的可视角度&#xff0c;超高分辨率的优势如下&#xff1a; 提供更清晰的视觉体验&#xff1a;VR头显的分辨率直接决定了用户所看到的图像的清晰度。更高的分辨率意…

001.VMware Workstation Pro虚拟平台安装

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

《数字图像处理-OpenCV/Python》第16章:图像的特征描述

《数字图像处理-OpenCV/Python》第16章&#xff1a;图像的特征描述 本书京东 优惠购书链接 https://item.jd.com/14098452.html 本书CSDN 独家连载专栏 https://blog.csdn.net/youcans/category_12418787.html 第16章&#xff1a;图像的特征描述 特征通常是针对图像中的目标或…

快排(霍尔排序实现+前后指针实现)(递归+非递归)

前言 快排是很重要的排序&#xff0c;也是一种比较难以理解的排序&#xff0c;这里我们会用递归的方式和非递归的方式来解决&#xff0c;递归来解决是比较简单的&#xff0c;非递归来解决是有点难度的 快排也称之为霍尔排序&#xff0c;因为发明者是霍尔&#xff0c;本来是命名…

基于Spring Boot+VUE旧物置换网站

1前台首页功能模块 旧物置换网站&#xff0c;在系统首页可以查看首页、旧物信息、网站公告、个人中心、后台管理等内容&#xff0c;如图1所示。 图1系统功能界面图 用户注册&#xff0c;在用户注册页面通过填写用户名、密码、姓名、性别、头像、手机、邮箱等内容进行用户注册&…

FlinkCDC介绍及使用

CDC简介 什么是CDC&#xff1f; cdc是Change Data Capture(变更数据获取)的简称。核心思想是&#xff0c;监测并捕获数据库的 变动(包括数据或数据表的插入&#xff0c;更新以及删除等)&#xff0c;将这些变更按发生的顺序完整记录下来&#xff0c;写入到消息中间件以供其它服…

IPv4的报头详解

1.ipv4的报头&#xff1a; 注意&#xff1a;ipv4的报头长度共有20个字节&#xff0c;数据包通过ipv4协议传输后&#xff0c;会进行封装和解封装&#xff1a; 封装&#xff1a;tcp/ip五层参考模型 应用层网络层传输层数据链路层物理层 网络层&#xff1a;tcp/udp 传输层&#…

【odoo】如何开启开发者模式,开启有什么作用?

概要 在 Odoo 中&#xff0c;开发者模式&#xff08;Developer Mode&#xff09;是一种专门为开发和调试提供的模式。启用开发者模式可以让开发人员访问到更多的功能和信息&#xff0c;从而更方便地进行模块开发、调试和测试。 启用方式&#xff08;主要两种&#xff09; 1.设…

「iOS」UI——无限轮播图实现与UIPageControl运用

「OC」UI 文章目录 「OC」UI无限轮播图的实现以及UIPageControl的实际运用明确要求简单滚动视图的实现UIPageControl的实现设置NSTimer实现自动移动补充实现 进行无限滚动视图的修改思路实现 完整代码展示 无限轮播图的实现以及UIPageControl的实际运用 明确要求 我们要实现一…