线性表,双向链表,静态链表,循环链表(约瑟夫环)

news2024/11/23 20:02:28

目录

什么是线性表(线性存储结构)

顺序存储结构和链式存储结构

前驱和后继

顺序表(顺序存储结构)及初始化详解

顺序表的初始化

双向链表及创建(C语言)详解

双向链表的创建

静态链表及其创建(C语言实现)

静态链表中的节点

备用链表

静态链表的创建

循环链表(约瑟夫环)的建立及C语言实现

循环链表实现约瑟夫环

总结


什么是线性表(线性存储结构)

通过前面的学习我们知道,具有“一对一”逻辑关系的数据,最佳的存储方式是使用线性表。那么,什么是线性表呢?

线性表,全名为线性存储结构。使用线性表存储数据的方式可以这样理解,即“把所有数据用一根线儿串起来,再存储到物理空间中”。

 

"一对一"逻辑关系的数据


图 1 "一对一"逻辑关系的数据


如图 1 所示,这是一组具有“一对一”关系的数据,我们接下来采用线性表将其储存到物理空间中。

首先,用“一根线儿”把它们按照顺序“串”起来,如图 2 所示:

 


图 2 数据的"线性"结构


图 2 中,左侧是“串”起来的数据,右侧是空闲的物理空间。把这“一串儿”数据放置到物理空间,我们可以选择以下两种方式,如图 3 所示。

 

两种线性存储结构


图 3 两种线性存储结构


图 3a) 是多数人想到的存储方式,而图 3b) 却少有人想到。我们知道,数据存储的成功与否,取决于是否能将数据完整地复原成它本来的样子。如果把图 3a) 和图 3b) 线的一头扯起,你会发现数据的位置依旧没有发生改变(和图 1 一样)。因此可以认定,这两种存储方式都是正确的。

将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构(简称线性表)

使用线性表存储的数据,如同向数组中存储数据那样,要求数据类型必须一致,也就是说,线性表存储的数据,要么全部都是整形,要么全部都是字符串。一半是整形,另一半是字符串的一组数据无法使用线性表存储。

顺序存储结构和链式存储结构

图 3 中我们可以看出,线性表存储数据可细分为以下 2 种:

  1. 如图 3a) 所示,将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构(简称顺序表);
  2. 如图 3b) 所示,数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系,这种存储结构称为链式存储结构(简称链表);


也就是说,线性表存储结构可细分为顺序存储结构和链式存储结构。

前驱和后继

数据结构中,一组数据中的每个个体被称为“数据元素”(简称“元素”)。例如,图 1 显示的这组数据,其中 1、2、3、4 和 5 都是这组数据中的一个元素。

另外,对于具有“一对一”逻辑关系的数据,我们一直在用“某一元素的左侧(前边)或右侧(后边)”这样不专业的词,其实线性表中有更准确的术语:

  • 某一元素的左侧相邻元素称为“直接前驱”,位于此元素左侧的所有元素都统称为“前驱元素”;
  • 某一元素的右侧相邻元素称为“直接后继”,位于此元素右侧的所有元素都统称为“后继元素”;


以图 1 数据中的元素 3 来说,它的直接前驱是 2 ,此元素的前驱元素有 2 个,分别是 1 和 2;同理,此元素的直接后继是 4 ,后继元素也有 2 个,分别是 4 和 5。如图 4 所示:

 

前驱和后继


图 4 前驱和后继

顺序表(顺序存储结构)及初始化详解

顺序表,全名顺序存储结构,是线性表的一种。通过《什么是线性表》一节的学习我们知道,线性表用于存储逻辑关系为“一对一”的数据,顺序表自然也不例外。

不仅如此,顺序表对数据的物理存储结构也有要求。顺序表存储数据时,会提前申请一整块足够大小的物理空间,然后将数据依次存储起来,存储时做到数据元素之间不留一丝缝隙。

例如,使用顺序表存储集合 {1,2,3,4,5},数据最终的存储状态如图 1 所示:


 


图 1 顺序存储结构示意图


由此我们可以得出,将“具有 '一对一' 逻辑关系的数据按照次序连续存储到一整块物理空间上”的存储结构就是顺序存储结构。

通过观察图 1 中数据的存储状态,我们可以发现,顺序表存储数据同数组非常接近。其实,顺序表存储数据使用的就是数组。

顺序表的初始化

使用顺序表存储数据之前,除了要申请足够大小的物理空间之外,为了方便后期使用表中的数据,顺序表还需要实时记录以下 2 项数据:

  1. 顺序表申请的存储容量;
  2. 顺序表的长度,也就是表中存储数据元素的个数;

提示:正常状态下,顺序表申请的存储容量要大于顺序表的长度。

因此,我们需要自定义顺序表,C 语言实现代码如下:

 
  1. typedef struct Table{
  2. int * head;//声明了一个名为head的长度不确定的数组,也叫“动态数组”
  3. int length;//记录当前顺序表的长度
  4. int size;//记录顺序表分配的存储容量
  5. }table;

注意,head 是我们声明的一个未初始化的动态数组,不要只把它看做是普通的指针。

接下来开始学习顺序表的初始化,也就是初步建立一个顺序表。建立顺序表需要做如下工作:

  • 给 head 动态数据申请足够大小的物理空间;
  • 给 size 和 length 赋初值;


因此,C 语言实现代码如下:

 
  1. #define Size 5 //对Size进行宏定义,表示顺序表申请空间的大小
  2. table initTable(){
  3. table t;
  4. t.head=(int*)malloc(Size*sizeof(int));//构造一个空的顺序表,动态申请存储空间
  5. if (!t.head) //如果申请失败,作出提示并直接退出程序
  6. {
  7. printf("初始化失败");
  8. exit(0);
  9. }
  10. t.length=0;//空表的长度初始化为0
  11. t.size=Size;//空表的初始存储空间为Size
  12. return t;
  13. }

我们看到,整个顺序表初始化的过程被封装到了一个函数中,此函数返回值是一个已经初始化完成的顺序表。这样做的好处是增加了代码的可用性,也更加美观。与此同时,顺序表初始化过程中,要注意对物理空间的申请进行判断,对申请失败的情况进行处理,这里只进行了“输出提示信息和强制退出”的操作,可以根据你自己的需要对代码中的 if 语句进行改进。

通过在主函数中调用 initTable 语句,就可以成功创建一个空的顺序表,与此同时我们还可以试着向顺序表中添加一些元素,C 语言实现代码如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define Size 5
  4. typedef struct Table{
  5. int * head;
  6. int length;
  7. int size;
  8. }table;
  9. table initTable(){
  10. table t;
  11. t.head=(int*)malloc(Size*sizeof(int));
  12. if (!t.head)
  13. {
  14. printf("初始化失败");
  15. exit(0);
  16. }
  17. t.length=0;
  18. t.size=Size;
  19. return t;
  20. }
  21. //输出顺序表中元素的函数
  22. void displayTable(table t){
  23. for (int i=0;i<t.length;i++) {
  24. printf("%d ",t.head[i]);
  25. }
  26. printf("\n");
  27. }
  28. int main(){
  29. table t=initTable();
  30. //向顺序表中添加元素
  31. for (int i=1; i<=Size; i++) {
  32. t.head[i-1]=i;
  33. t.length++;
  34. }
  35. printf("顺序表中存储的元素分别是:\n");
  36. displayTable(t);
  37. return 0;
  38. }

程序运行结果如下:

顺序表中存储的元素分别是:
1 2 3 4 5

可以看到,顺序表初始化成功。

 

双向链表及创建(C语言)详解

目前我们所学到的链表,无论是动态链表还是静态链表,表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表(或单链表)。

虽然使用单链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,如果算法中需要大量地找某指定结点的前趋结点,使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,而 "从后往前" 找并不是它的强项。

为了能够高效率解决类似的问题,本节来学习双向链表(简称双链表)。

从名字上理解双向链表,即链表是 "双向" 的,如图 1 所示:


 

双向链表结构示意图


图 1 双向链表结构示意图

双向,指的是各节点之间的逻辑关系是双向的,但通常头指针只设置一个,除非实际情况需要

从图 1 中可以看到,双向链表中各节点包含以下 3 部分信息(如图 2 所示):

  1. 指针域:用于指向当前节点的直接前驱节点;
  2. 数据域:用于存储数据元素。
  3. 指针域:用于指向当前节点的直接后继节点;


 

双向链表的节点构成


图 2 双向链表的节点构成


因此,双链表的节点结构用 C 语言实现为:

  1. typedef struct line{
  2. struct line * prior; //指向直接前趋
  3. int data;
  4. struct line * next; //指向直接后继
  5. }line;

双向链表的创建

同单链表相比,双链表仅是各节点多了一个用于指向直接前驱的指针域。因此,我们可以在单链表的基础轻松实现对双链表的创建。

需要注意的是,与单链表不同,双链表创建过程中,每创建一个新节点,都要与其前驱节点建立两次联系,分别是:

  • 将新节点的 prior 指针指向直接前驱节点;
  • 将直接前驱节点的 next 指针指向新节点;


这里给出创建双向链表的 C 语言实现代码:

  1. line* initLine(line * head){
  2. head=(line*)malloc(sizeof(line));//创建链表第一个结点(首元结点)
  3. head->prior=NULL;
  4. head->next=NULL;
  5. head->data=1;
  6. line * list=head;
  7. for (int i=2; i<=3; i++) {
  8. //创建并初始化一个新结点
  9. line * body=(line*)malloc(sizeof(line));
  10. body->prior=NULL;
  11. body->next=NULL;
  12. body->data=i;
  13. list->next=body;//直接前趋结点的next指针指向新结点
  14. body->prior=list;//新结点指向直接前趋结点
  15. list=list->next;
  16. }
  17. return head;
  18. }


我们可以尝试着在 main 函数中输出创建的双链表,C 语言代码如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. //节点结构
  4. typedef struct line{
  5. struct line * prior;
  6. int data;
  7. struct line * next;
  8. }line;
  9. //双链表的创建函数
  10. line* initLine(line * head);
  11. //输出双链表的函数
  12. void display(line * head);
  13. int main() {
  14. //创建一个头指针
  15. line * head=NULL;
  16. //调用链表创建函数
  17. head=initLine(head);
  18. //输出创建好的链表
  19. display(head);
  20. //显示双链表的优点
  21. printf("链表中第 4 个节点的直接前驱是:%d",head->next->next->next->prior->data);
  22. return 0;
  23. }
  24. line* initLine(line * head){
  25. //创建一个首元节点,链表的头指针为head
  26. head=(line*)malloc(sizeof(line));
  27. //对节点进行初始化
  28. head->prior=NULL;
  29. head->next=NULL;
  30. head->data=1;
  31. //声明一个指向首元节点的指针,方便后期向链表中添加新创建的节点
  32. line * list=head;
  33. for (int i=2; i<=5; i++) {
  34. //创建新的节点并初始化
  35. line * body=(line*)malloc(sizeof(line));
  36. body->prior=NULL;
  37. body->next=NULL;
  38. body->data=i;
  39. //新节点与链表最后一个节点建立关系
  40. list->next=body;
  41. body->prior=list;
  42. //list永远指向链表中最后一个节点
  43. list=list->next;
  44. }
  45. //返回新创建的链表
  46. return head;
  47. }
  48. void display(line * head){
  49. line * temp=head;
  50. while (temp) {
  51. //如果该节点无后继节点,说明此节点是链表的最后一个节点
  52. if (temp->next==NULL) {
  53. printf("%d\n",temp->data);
  54. }else{
  55. printf("%d <-> ",temp->data);
  56. }
  57. temp=temp->next;
  58. }
  59. }

程序运行结果:

1 <-> 2 <-> 3 <-> 4 <-> 5
链表中第 4 个节点的直接前驱是:3

 

静态链表及其创建(C语言实现)

《顺序表和链表优缺点》一节,我们了解了两种存储结构各自的特点,那么,是否存在一种存储结构,可以融合顺序表和链表各自的优点,从而既能快速访问元素,又能快速增加或删除数据元素

静态链表,也是线性存储结构的一种,它兼顾了顺序表和链表的优点于一身,可以看做是顺序表和链表的升级版。

使用静态链表存储数据,数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整形变量(称为"游标",和指针功能类似)维持(和链表类似)。

例如,使用静态链表存储 {1,2,3} 的过程如下:

创建一个足够大的数组,假设大小为 6,如图 1 所示:


 

空数组


图 1 空数组


接着,在将数据存放到数组中时,给各个数据元素配备一个整形变量,此变量用于指明各个元素的直接后继元素所在数组中的位置下标,如图 2 所示:


 

静态链表存储数据


图 2 静态链表存储数据

通常,静态链表会将第一个数据元素放到数组下标为 1 的位置(a[1])中。

图 2 中,

从 a[1] 存储的数据元素 1 开始,通过存储的游标变量 3,就可以在 a[3] 中找到元素 1 的直接后继元素 2;

通过元素 a[3] 存储的游标变量 5,可以在 a[5] 中找到元素 2 的直接后继元素 3,这样的循环过程直到某元素的游标变量为 0 截止(因为 a[0] 默认不存储数据元素)

类似图 2 这样,通过 "数组+游标" 的方式存储具有线性关系数据的存储结构就是静态链表。

静态链表中的节点

通过上面的学习我们知道,静态链表存储数据元素也需要自定义数据类型,至少需要包含以下 2 部分信息:

  • 数据域:用于存储数据元素的值;
  • 游标:其实就是数组下标,表示直接后继元素所在数组中的位置;


因此,静态链表中节点的构成用 C 语言实现为:

 
  1. typedef struct {
  2. int data;//数据域
  3. int cur;//游标
  4. }component;

备用链表

图 2 显示的静态链表还不够完整,静态链表中,除了数据本身通过游标组成的链表外,还需要有一条连接各个空闲位置的链表,称为备用链表。

备用链表的作用是回收数组中未使用或之前使用过(目前未使用)的存储空间,留待后期使用。也就是说,静态链表使用数组申请的物理空间中,存有两个链表,一条连接数据,另一条连接数组中未使用的空间。

通常,备用链表的表头位于数组下标为 0(a[0]) 的位置,而数据链表的表头位于数组下标为 1(a[1])的位置。

静态链表中设置备用链表的好处是,可以清楚地知道数组中是否有空闲位置,以便数据链表添加新数据时使用。比如,若静态链表中数组下标为 0 的位置上存有数据,则证明数组已满。

例如,使用静态链表存储 {1,2,3},假设使用长度为 6 的数组 a,则存储状态可能如图 3 所示:


 

备用链表和数据链表


图 3 备用链表和数据链表


图 3 中,备用链表上连接的依次是 a[0]、a[2] 和 a[4],而数据链表上连接的依次是 a[1]、a[3] 和 a[5]。

静态链表的创建

假设使用静态链表(数组长度为 6)存储 {1,2,3},则需经历以下几个阶段:

  1. 在数据链表未初始化之前,数组中所有位置都处于空闲状态,因此都应被链接在备用链表上,如图 4 所示:


     

    未存储数据之前静态链表的状态


    图 4 未存储数据之前静态链表的状态


    当向静态链表中添加数据时,需提前从备用链表中摘除节点,以供新数据使用。

    备用链表摘除节点最简单的方法是摘除 a[0] 的直接后继节点;同样,向备用链表中添加空闲节点也是添加作为 a[0] 新的直接后继节点。因为 a[0] 是备用链表的第一个节点,我们知道它的位置,操作它的直接后继节点相对容易,无需遍历备用链表,耗费的时间复杂度为 O(1)

  2. 在图 4 的基础上,向静态链表中添加元素 1 的过程如图 5 所示:


     

    静态链表中添加元素 1


    图 5 静态链表中添加元素 1

  3. 在图 5 的基础上,添加元素 2 的过程如图 6 所示:


     

    静态链表中继续添加元素 2


    图 6 静态链表中继续添加元素 2

  4. 在图 6 的基础上,继续添加元素 3 ,过程如图 7 所示:


     

    静态链表中继续添加元素 3


    图 7 静态链表中继续添加元素 3

由此,静态链表就创建完成了。

下面给出了创建静态链表的 C 语言实现代码:

  1. #include <stdio.h>
  2. #define maxSize 6
  3. typedef struct {
  4. int data;
  5. int cur;
  6. }component;
  7. //将结构体数组中所有分量链接到备用链表中
  8. void reserveArr(component *array);
  9. //初始化静态链表
  10. int initArr(component *array);
  11. //输出函数
  12. void displayArr(component * array,int body);
  13. //从备用链表上摘下空闲节点的函数
  14. int mallocArr(component * array);
  15. int main() {
  16. component array[maxSize];
  17. int body=initArr(array);
  18. printf("静态链表为:\n");
  19. displayArr(array, body);
  20. return 0;
  21. }
  22. //创建备用链表
  23. void reserveArr(component *array){
  24. for (int i=0; i<maxSize; i++) {
  25. array[i].cur=i+1;//将每个数组分量链接到一起
  26. array[i].data=-1;
  27. }
  28. array[maxSize-1].cur=0;//链表最后一个结点的游标值为0
  29. }
  30. //提取分配空间
  31. int mallocArr(component * array){
  32. //若备用链表非空,则返回分配的结点下标,否则返回 0(当分配最后一个结点时,该结点的游标值为 0)
  33. int i=array[0].cur;
  34. if (array[0].cur) {
  35. array[0].cur=array[i].cur;
  36. }
  37. return i;
  38. }
  39. //初始化静态链表
  40. int initArr(component *array){
  41. reserveArr(array);
  42. int body=mallocArr(array);
  43. //声明一个变量,把它当指针使,指向链表的最后的一个结点,因为链表为空,所以和头结点重合
  44. int tempBody=body;
  45. for (int i=1; i<4; i++) {
  46. int j=mallocArr(array);//从备用链表中拿出空闲的分量
  47. array[tempBody].cur=j;//将申请的空闲分量链接在链表的最后一个结点后面
  48. array[j].data=i;//给新申请的分量的数据域初始化
  49. tempBody=j;//将指向链表最后一个结点的指针后移
  50. }
  51. array[tempBody].cur=0;//新的链表最后一个结点的指针设置为0
  52. return body;
  53. }
  54. void displayArr(component * array,int body){
  55. int tempBody=body;//tempBody准备做遍历使用
  56. while (array[tempBody].cur) {
  57. printf("%d,%d ",array[tempBody].data,array[tempBody].cur);
  58. tempBody=array[tempBody].cur;
  59. }
  60. printf("%d,%d\n",array[tempBody].data,array[tempBody].cur);
  61. }

代码输出结果为:

静态链表为:
-1,2 1,3 2,4 3,0

提示,此代码创建了一个带有头节点的静态链表,因此最先输出的 "-1,2" 表示的是头节点(-1表示此处未存储数据),其首元节点(存储元素 1 的节点)在数组 array[2] 中。

循环链表(约瑟夫环)的建立及C语言实现

无论是静态链表还是动态链表,有时在解决具体问题时,需要我们对其结构进行稍微地调整。比如,可以把链表的两头连接,使其成为了一个环状链表,通常称为循环链表。

和它名字的表意一样,只需要将表中最后一个结点的指针指向头结点,链表就能成环儿,如图 1 所示。

图1 循环链表

需要注意的是,虽然循环链表成环状,但本质上还是链表,因此在循环链表中,依然能够找到头指针和首元节点等。循环链表和普通链表相比,唯一的不同就是循环链表首尾相连,其他都完全一样。

循环链表实现约瑟夫环

约瑟夫环问题,是一个经典的循环链表问题

题意是:已知 n 个人(分别用编号 1,2,3,…,n 表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 开始,还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,直到圆桌上剩余一个人

如图 2 所示,假设此时圆周周围有 5 个人,要求从编号为 3 的人开始顺时针数数,数到 2 的那个人出列:
 

循环链表实现约瑟夫环


图 2 循环链表实现约瑟夫环


出列顺序依次为:

  • 编号为 3 的人开始数 1,然后 4 数 2,所以 4 先出列;
  • 4 出列后,从 5 开始数 1,1 数 2,所以 1 出列;
  • 1 出列后,从 2 开始数 1,3 数 2,所以 3 出列;
  • 3 出列后,从 5 开始数 1,2 数 2,所以 2 出列;
  • 最后只剩下 5 自己,所以 5 胜出。

约瑟夫环问题有多种变形,比如顺时针转改为逆时针等,虽然问题的细节有多种变数,但解决问题的中心思想是一样的,即使用循环链表。

通过以上的分析,我们可以尝试编写 C 语言代码,完整代码如下所示:

 
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. typedef struct node{
  4. int number;
  5. struct node * next;
  6. }person;
  7. person * initLink(int n){
  8. person * head=(person*)malloc(sizeof(person));
  9. head->number=1;
  10. head->next=NULL;
  11. person * cyclic=head;
  12. for (int i=2; i<=n; i++) {
  13. person * body=(person*)malloc(sizeof(person));
  14. body->number=i;
  15. body->next=NULL;
  16. cyclic->next=body;
  17. cyclic=cyclic->next;
  18. }
  19. cyclic->next=head;//首尾相连
  20. return head;
  21. }
  22. void findAndKillK(person * head,int k,int m){
  23. person * tail=head;
  24. //找到链表第一个结点的上一个结点,为删除操作做准备
  25. while (tail->next!=head) {
  26. tail=tail->next;
  27. }
  28. person * p=head;
  29. //找到编号为k的人
  30. while (p->number!=k) {
  31. tail=p;
  32. p=p->next;
  33. }
  34. //从编号为k的人开始,只有符合p->next==p时,说明链表中除了p结点,所有编号都出列了,
  35. while (p->next!=p) {
  36. //找到从p报数1开始,报m的人,并且还要知道数m-1de人的位置tail,方便做删除操作。
  37. for (int i=1; i<m; i++) {
  38. tail=p;
  39. p=p->next;
  40. }
  41. tail->next=p->next;//从链表上将p结点摘下来
  42. printf("出列人的编号为:%d\n",p->number);
  43. free(p);
  44. p=tail->next;//继续使用p指针指向出列编号的下一个编号,游戏继续
  45. }
  46. printf("出列人的编号为:%d\n",p->number);
  47. free(p);
  48. }
  49. int main() {
  50. printf("输入圆桌上的人数n:");
  51. int n;
  52. scanf("%d",&n);
  53. person * head=initLink(n);
  54. printf("从第k人开始报数(k>1且k<%d):",n);
  55. int k;
  56. scanf("%d",&k);
  57. printf("数到m的人出列:");
  58. int m;
  59. scanf("%d",&m);
  60. findAndKillK(head, k, m);
  61. return 0;
  62. }

输出结果:

输入圆桌上的人数n:5
从第k人开始报数(k>1且k<5):3
数到m的人出列:2
出列人的编号为:4
出列人的编号为:1
出列人的编号为:3
出列人的编号为:2
出列人的编号为:5

最后出列的人,即为胜利者。当然,你也可以改进程序,令查找出最后一个人时,输出此人胜利的信息。

总结

循环链表和动态链表唯一不同在于它的首尾连接,这也注定了在使用循环链表时,附带最多的操作就是遍历链表。

在遍历的过程中,尤其要注意循环链表虽然首尾相连,但并不表示该链表没有第一个节点和最后一个结点。所以,不要随意改变头指针的指向。

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

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

相关文章

Ant Design Mobile是什么?

在当今的数字时代&#xff0c;移动应用程序和网页设计已经成为各行各业的重要组成部分。用户界面的设计直接影响到用户体验和产品的成功。为了帮助设计师在移动设计领域更好&#xff0c;Antdesignmobile应运而生。Antdesignmobile是蚂蚁金服的移动UI设计语言和框架&#xff0c;…

Android进阶之多级列表

遇到一个需求需要显示多级列表&#xff0c;因为界面是在平板上的&#xff0c;所以层级是从左向右往下排的&#xff0c;类似于 我当时的写法是在xml布局里一个个RecyclerView往下排的 当然前提是已经规定好最大的层级我才敢如此去写界面&#xff0c;如果已经明确规定只有两级或…

回归预测 | MATLAB实现GRNN广义回归神经网络多输入多输出预测

回归预测 | MATLAB实现GRNN广义回归神经网络多输入多输出预测 目录 回归预测 | MATLAB实现GRNN广义回归神经网络多输入多输出预测预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 MATLAB实现GRNN广义回归神经网络多输入多输出预测&#xff0c;输入10个特征&#x…

每日一题——合并二叉树

题目 已知两颗二叉树&#xff0c;将它们合并成一颗二叉树。合并规则是&#xff1a;都存在的结点&#xff0c;就将结点值加起来&#xff0c;否则空的位置就由另一个树的结点来代替。例如&#xff1a; 两颗二叉树是: Tree 1 Tree 2 合并后的树为 数据范围&#xff1a;树上节点数…

【笔试题心得】排序算法总结整理

排序算法汇总 常用十大排序算法_calm_G的博客-CSDN博客 以下动图参考 十大经典排序算法 Python 版实现&#xff08;附动图演示&#xff09; - 知乎 冒泡排序 排序过程如下图所示&#xff1a; 比较相邻的元素。如果第一个比第二个大&#xff0c;就交换他们两个。对每一对相邻…

快速入门vue3组合式API

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 使用create-vue创建项目 1. 前提环境条件 已安装 16.0 或更高版本的 Node.js node -v 2. 创建一个Vue应用 npm…

第三章nginx详解

nginx&#xff1a;高性能&#xff0c;轻量级的web服务软件。 特点&#xff1a; 1&#xff0c;稳定性高。&#xff08;没有apache稳定&#xff09; 2&#xff0c;系统资源消耗地较低。&#xff08;处理http请求的并发能力非常高&#xff0c;单台物理服务器可以处理30000-5000…

【漏洞通知】JeecgBoot 修复 Freemarker 模板注入漏洞, 漏洞危害等级:高危

Freemarker模板注入导致远程命令执行, 远程攻击者可利用该漏洞调用在系统上执行任意命令。 JeecgBoot官方已修复&#xff0c;建议大家尽快升级至相关底层依赖和源码 一、漏洞描述 Freemarker模板注入导致远程命令执行, 远程攻击者可利用该漏洞调用在系统上执行任意命令。漏洞…

神经网络基础-神经网络补充概念-14-逻辑回归中损失函数的解释

概念 逻辑回归损失函数是用来衡量逻辑回归模型预测与实际观测之间差异的函数。它的目标是找到一组模型参数&#xff0c;使得预测结果尽可能接近实际观测。 理解 在逻辑回归中&#xff0c;常用的损失函数是对数似然损失&#xff08;Log-Likelihood Loss&#xff09;&#xff…

流程挖掘in汽车丨宝马的流程效能提升实例

汽车行业在未来10年里&#xff0c;可能会面临比过去50年更多的变化。电动化、智能化、共享化和自动驾驶等方面的趋势可能给企业流程带来以下挑战&#xff1a; 供应链管理-电动化和智能化的发展可能导致供应链中的零部件和系统结构发生变化&#xff0c;企业需要重新评估和优化供…

【Linux】IO多路转接——epoll

目录 epoll初识 epoll的相关系统调用 epoll工作原理 epoll服务器 epoll的优点 epoll工作方式 对比LT和ET epoll初识 epoll也是系统提供的一个多路转接接口。 epoll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪&#xff0c;与select和poll的定位是…

torch_geometric实现GCN和LightGCN

torch_geometric实现GCN和LightGCN 题记demo示意图GCN代码LightGCN代码参考博文及感谢 题记 使用torch_geometric实现GCN和LightGCN&#xff0c;以后可能要用&#xff0c;做一下备份 demo示意图 GCN代码 X ′ D ^ − 1 / 2 A ^ D ^ − 1 / 2 X Θ \mathbf{X}^{\prime} \mat…

Linux 僵死进程

fork复制进程之后&#xff0c;会产生一个进程叫做子进程&#xff0c;被复制的进程就是父进程。不管父进程先结束&#xff0c;还是子进程先结束&#xff0c;对另外一个进程完全没有影响&#xff0c;父进程和子进程是两个不同的进程。 一、孤儿进程 现在有以下代码&#xff1a;…

【Vue-Router】路由元信息

路由元信息&#xff08;Route Meta Information&#xff09;是在路由配置中为每个路由定义的一组自定义数据。这些数据可以包含任何你希望在路由中传递和使用的信息&#xff0c;比如权限、页面标题、布局设置等。Vue Router 允许你在路由配置中定义元信息&#xff0c;然后在组件…

国产32位单片机XL32F001,带1 路 12bit ADC,I2C、SPI、USART 等外设

XL32F001 系列单片机采用高性能的 32 位 ARM Cortex-M0内核&#xff0c;宽电压工作范围的 MCU。嵌入 24KbytesFlash 和 3Kbytes SRAM 存储器&#xff0c;最高工作频率 24MHz。包含多种不同封装类型多款产品。芯片集成 I2C、SPI、USART 等通讯外设&#xff0c;1 路 12bit ADC&am…

【校招VIP】测试方案之测试用例分析

考点介绍 测试用例是测试岗面试和工作后的核心&#xff0c;在面试里对测试用例的分析是高频考查点。但是很多同学因为没有真实的商业产品需求&#xff0c;只能简单的看别人的用例学习&#xff0c;导致面试时被一个陌生问题卡住。 比如最简单的用户名密码输入&#xff0c;在商业…

2023年网络安全比赛--综合渗透测试(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 1.扫描目标靶机将靶机开放的所有端口,当作flag提交(例:21,22,23); 2.扫描目标靶机将靶机的http服务版本信息当作flag提交(例:apache 2.3.4); 3.靶机网站存在目录遍历漏洞,请将…

数据库--SQL关键字的执行顺序

一条sql语句通常包括&#xff1a; select from join where group by having order by 聚合函数 limit top 浅谈执行顺序&#xff1a; 1&#xff09;、首先确定一点&#xff0c;并不是按照我们写的语句顺序&#xff0c;从左—>右执行的 2&#xff09;、…

JVM——分代收集理论和垃圾回收算法

一、分代收集理论 1、三个假说 弱分代假说&#xff1a;绝大多数对象都是朝生夕灭的。 强分代假说&#xff1a;熬过越多次垃圾收集过程的对象越难以消亡。 这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则&#xff1a;收集器应该将Java堆划分出不同的区域&…

R语言实现免疫浸润分析(1)

免疫浸润分析是生物信息学研究中的一项关键内容&#xff0c;它旨在评估肿瘤微环境中不同类型的免疫细胞组成。免疫细胞在肿瘤发展和治疗中起着至关重要的作用&#xff0c;因为它们可以影响肿瘤的生长、扩散和对治疗的响应。 为了了解免疫细胞在肿瘤中的分布和数量&#xff0c;…