(C语言)单链表(1.0)(单链表教程)(数据结构,指针)

目录

1. 什么是单链表?

2. 单链表的代码表示

3. 单链表的基本操作

3.1 初始化链表

3.2 插入结点(头插法)

3.3 插入结点(尾插法)

3.4 遍历链表

4. 单链表的优缺点

代码:*L=(LinkList)malloc(sizeof(LNode))

1. malloc 的作用

2. sizeof(LNode) 的作用

3. 类型转换 (LinkList)

4. *L 的含义

5. 整体流程

6. 实际效果

7. 常见问题解答

Q1:为什么用 malloc 而不是直接声明变量?

Q2:头结点的 data 字段有意义吗?

Q3:为什么要用二级指针 LinkList* L?

8. 图解过程

DestoryLinkList 函数原理详解

1. 函数参数 LinkList* L(二级指针)

2. while (*L != NULL) 循环

3. 销毁过程图解

循环步骤:

4. 最终效果

5. 为什么需要这样实现?

6. 对比 ClearLinkList(清空链表)

7. 常见问题

Q1:为什么用 while (*L) 而不是 while ((*L)->next)?

Q2:如果链表为空(只有头结点),会发生什么?

Q3:为什么不用递归实现?

8. 代码验证

总结

1. 函数参数

2. 创建头结点

3. 头插法循环(for (i = n; i > 0; i--))

步骤拆解:

插入过程图示:

4. 输入顺序与链表顺序的关系

6. 关键代码解析

7. 内存管理注意事项

8. 示例输入输出

输入:

链表结构:

9. 常见问题

Q1:为什么头插法会导致顺序相反?

Q2:如果 n 为负数会发生什么?

Q3:头插法的时间复杂度是多少?

总结

ShowLinkList 函数原理详解(显示单链表内容)

1. 函数参数 const LinkList* L

2. 初始化指针 p

3. 检查链表是否为空

4. 遍历链表并打印数据

5. 示例输出

6. 遍历过程图解

7. 为什么用 while (p) 而不是 while (p->next)?

8. 时间复杂度

9. 安全性注意事项

总结

1. 函数参数

2. 创建头结点

3. 尾指针 p 的初始化

4. 尾插法循环(for (i = n; i > 0; i--))

步骤拆解:

插入过程图示:

5. 输入顺序与链表顺序的关系

7. 关键代码解析

8. 内存管理注意事项

9. 示例输入输出

输入:

链表结构:

10. 常见问题

Q1:为什么需要尾指针 p?

Q2:如果 p 不更新会怎样?

Q3:尾插法的时间复杂度是多少?

总结

#include <stdio.h>
#include <stdlib.h>//函数结果状态代码
#define OK 1
#define ERROR 0typedef int Status;//函数返回状态,ok,error
typedef int Elemtype;//链表元素为整形
typedef struct Lnode//定义结构体
{Elemtype data;//数据域struct Lnode* next;//指针域
}Lnode,*LinkList;//单个结点,整个链表(指向结点的指针)//初始化链表(建立一个头结点)
Status InitLinkList(LinkList* L){*L=(LinkList)malloc(sizeof(Lnode));//分配头结点内存if(*L==NULL){return ERROR;//判断是否分配成功}(*L)->next=NULL;//头结点的指针域为空return OK;
}//判断链表是否为空
Status IsEmptyLinkList(const LinkList* L){if((*L)->next==NULL){return ERROR;}else{return OK;}
}//销毁链表
Status DestoryLinkList(LinkList* L){LinkList p;//定义一个临时的指向结点的指针while (*L!=NULL){p=*L;//储存原来的指针(结点)*L=(*L)->next;//往后移动结点free(p);//释放原来的指针}return OK;
}//链表的插入,头插法
Status CreateLinkList_h(LinkList* L,int n){InitLinkList(L);//创建头结点for(int i=0;i<n;i++){LinkList newlnode;//创建一个新结点newlnode=(Lnode*)malloc(sizeof(Lnode));//为新节点分配内存if(newlnode==NULL){return ERROR;//判断是否分配成功}printf("请输入数据:\n");scanf("%d",&newlnode->data);newlnode->next=(*L)->next;//使新结点指向原指针(*L)->next=newlnode;//使头指针指向新结点}return OK;
}//链表的插入,尾插入
Status CreateLinkList_r(LinkList* L,int n){InitLinkList(L);//创建头结点LinkList p=*L;//定义临时尾结点for(int i=0;i<n;i++){LinkList newlnode;newlnode=(Lnode*)malloc(sizeof(Lnode));//给新结点分配内存if(newlnode==NULL){return ERROR;//判断是否分配成功}printf("请输入数据:\n");scanf("%d",&newlnode->data);newlnode->next=NULL;//使新结点指向空p->next=newlnode;//使原结点指向新结点p=p->next;//后移一次,定义新的尾结点}return OK;
}//查看链表
Status ShowLinkList(const LinkList* L){Lnode* p=(*L)->next;//定义个临时结点if(p==NULL){printf("链表为空!\n");return OK;}int i=1;while (p!=NULL){printf("%d : %d\n", i, p->data);  // 打印序号和数据i++;            // 序号递增p = p->next;    // p 移动到下一个结点}return OK;
}//查看第i个元素
Status LocatElem(const LinkList* L,int i){int j=i;//赋值给ji=1;//初始化iLinkList p=(*L)->next;//创建临时结点表示第一个结点if(p==NULL){printf("链表为空!\n");//判断链表是否为空return OK;}//逐步后移,直到i和j相等while (i!=j){i++;p=p->next;}printf("第%d个 : %d\n", j, p->data);  // 打印第i个序号,和数据return OK;
}//主函数
int main(){LinkList mylist;mylist=NULL;//CreateLinkList_h(&mylist,3);//头插CreateLinkList_r(&mylist,3);//尾插ShowLinkList(&mylist);LocatElem(&mylist,2);
}

下面来解释相关知识点和部分代码:

单链表是数据结构中最基础的一种链式存储结构,非常适合新手学习指针和动态内存管理的概念。下面我会用最易懂的方式讲解单链表的核心知识。

1. 什么是单链表?

单链表就像一列火车:

  • 每节车厢(结点)包含两部分:货物(数据)和连接钩(指针)

  • 车头(头结点)不装货物,只负责带领整列火车

  • 最后一节车厢的连接钩是空的(NULL)

2. 单链表的代码表示

typedef struct Node {int data;           // 数据域(可以是任意类型)struct Node* next;  // 指针域(指向下一个结点)
} Node, *LinkedList;    // Node表示单个结点,LinkedList表示整个链表

3. 单链表的基本操作

3.1 初始化链表

LinkedList initList() {LinkedList L = (LinkedList)malloc(sizeof(Node)); // 创建头结点L->next = NULL;     // 初始为空链表return L;
}

3.2 插入结点(头插法)

void headInsert(LinkedList L, int data) {Node* newNode = (Node*)malloc(sizeof(Node)); // 创建新结点newNode->data = data;newNode->next = L->next;  // 新结点指向原第一个结点L->next = newNode;        // 头结点指向新结点
}

特点:新结点总是插在最前面,链表顺序与插入顺序相反

3.3 插入结点(尾插法)

void tailInsert(LinkedList L, int data) {Node* p = L;while(p->next != NULL) {  // 找到最后一个结点p = p->next;}Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = data;newNode->next = NULL;p->next = newNode;  // 最后一个结点指向新结点
}

特点:新结点总是插在最后面,链表顺序与插入顺序相同

3.4 遍历链表

void printList(LinkedList L) {Node* p = L->next;  // 跳过头结点while(p != NULL) {printf("%d ", p->data);p = p->next;    // 移动到下一个结点}printf("\n");
}

4. 单链表的优缺点

优点

  • 插入/删除速度快(O(1)时间复杂度)

  • 不需要预先知道数据规模

  • 不需要连续的内存空间

缺点

  • 查找速度慢(O(n)时间复杂度)

  • 需要额外的空间存储指针

下面来解释代码,更加了解单链表:

代码:*L=(LinkList)malloc(sizeof(LNode))

这行代码是单链表初始化中的核心操作,涉及指针、动态内存分配和类型转换。让我们用新手能理解的方式逐步拆解:


1. malloc 的作用

malloc 是内存分配函数(memory allocation),它的作用是从堆(Heap)内存中申请一块指定大小的内存空间。

  • 比喻:就像在仓库里预订一个储物柜,告诉管理员你需要多大的空间。

  • 语法void* malloc(size_t size);

    • 参数 size:需要申请的字节数

    • 返回值:成功时返回指向分配内存的指针,失败返回 NULL


2. sizeof(LNode) 的作用

sizeof 是一个运算符,用来计算数据类型或变量占用的字节数。

  • sizeof(LNode):计算结构体 LNode 的大小。

    • 假设 LNode 包含一个 int(4字节)和一个指针(8字节,64位系统),则 sizeof(LNode) = 12 字节。

    • 意义:告诉 malloc 要申请一个足够存放 LNode 的内存块。


3. 类型转换 (LinkList)

malloc 返回的是 void*(通用指针),需要强制转换为 LinkList 类型。

  • LinkList 是什么
    根据代码中的定义:

    typedef struct Signle_Link_List LNode, *LinkList;
    • LNode 是结构体类型(结点)

    • LinkList 是 LNode* 的别名(指向结点的指针)

  • 转换目的:明确告诉编译器,这块内存将被当作 LinkList(即指向 LNode 的指针)使用。


4. *L 的含义

函数的参数是 LinkList* L(二级指针):

  • LinkList* L 可以理解为:指向头指针的指针。

  • *L 解引用后得到的是头指针(LinkList 类型)。

  • 操作目的:通过 *L = ... 修改外部的头指针,使其指向新分配的内存。


5. 整体流程

这行代码的完整意义是:

  1. 申请内存:在堆内存中申请一块大小为 sizeof(LNode) 的内存。

  2. 类型转换:将返回的 void* 转换为 LinkList 类型(即 LNode*)。

  3. 赋值给头指针:让外部的头指针 *L 指向这块内存。


6. 实际效果

这行代码执行后:

  • 创建了一个头结点:头结点的 data 字段未初始化(可能是垃圾值),但 next 指针会被初始化为 NULL(见后续代码 (*L)->next = NULL;)。

  • 链表结构

    *L(头指针)│▼
    [头结点] → next = NULL

7. 常见问题解答

Q1:为什么用 malloc 而不是直接声明变量?
  • :链表结点需要动态增减,malloc 允许在运行时按需申请内存。

  • 对比

    LNode node;        // 栈内存,函数结束后自动释放
    LNode* p = malloc(...); // 堆内存,需要手动释放(用 free)
Q2:头结点的 data 字段有意义吗?
  • :在标准实现中,头结点的 data 通常不存储有效数据(仅作为链表入口),但代码中可能用它存储元信息(如长度)。

Q3:为什么要用二级指针 LinkList* L
  • :需要修改外部传入的头指针的值。C语言中,若想通过函数修改指针的值,必须传递指针的地址(即二级指针)。

  • 示例

    void Init(LinkList* L) {*L = malloc(...); // 修改外部的头指针
    }
    int main() {LinkList myList;  // 此时 myList 是野指针Init(&myList);    // 传递头指针的地址
    }

8. 图解过程

Before malloc:
+------+
|  L  | --> 随机值(野指针)
+------+After malloc:
+------+       +---------------------+
|  L  | -->   | data(未初始化)     |
+------+       | next = NULL         |+---------------------+

DestoryLinkList 函数原理详解

这个函数的作用是 销毁整个单链表,包括 头结点 和所有 数据结点,并释放它们占用的内存。让我们一步步解析它的工作原理:


1. 函数参数 LinkList* L(二级指针)

  • LinkList 是 LNode* 的别名(指向结点的指针)。

  • LinkList* L 是一个 指向头指针的指针(二级指针),目的是 修改外部的头指针,使其最终变为 NULL

    • 如果只传 LinkList L(一级指针),函数内部修改 L 不会影响外部的头指针,导致内存泄漏。


2. while (*L != NULL) 循环

  • 循环条件:只要 *L(当前头指针)不为 NULL,就继续释放内存。

  • 循环过程

    1. p = *L:临时指针 p 保存当前要释放的结点(头结点或数据结点)。

    2. *L = (*L)->next:让头指针 *L 指向下一个结点(相当于链表“跳过”当前结点)。

    3. free(p):释放 p 指向的结点内存。


3. 销毁过程图解

假设链表结构如下:

头指针 *L│▼
[头结点] → [结点1] → [结点2] → NULL
循环步骤:
  1. 第一次循环

    • p = *Lp 指向头结点)

    • *L = (*L)->next(头指针 *L 指向结点1)

    • free(p)(释放头结点)

    *L → [结点1] → [结点2] → NULL
  2. 第二次循环

    • p = *Lp 指向结点1)

    • *L = (*L)->next(头指针 *L 指向结点2)

    • free(p)(释放结点1)

    *L → [结点2] → NULL
  3. 第三次循环

    • p = *Lp 指向结点2)

    • *L = (*L)->next(头指针 *L 指向 NULL

    • free(p)(释放结点2)

    *L → NULL
  4. 循环结束

    • *L 为 NULL,退出循环。


4. 最终效果

  • 链表被完全销毁:所有结点(包括头结点)的内存被释放。

  • 头指针 *L 被置为 NULL:防止外部代码误用已释放的内存(避免野指针)。


5. 为什么需要这样实现?

  • 防止内存泄漏:必须逐个释放所有结点,否则未释放的内存会一直占用堆空间。

  • 安全性:将头指针置为 NULL,避免后续代码误操作已释放的内存。


6. 对比 ClearLinkList(清空链表)

ClearLinkList 只释放数据结点,保留头结点(链表可复用):

Status ClearLinkList(LinkList* L) {LNode* p, *q;q = (*L)->next;  // q 指向第一个数据结点while (q != NULL) {p = q;q = q->next;free(p);}(*L)->next = NULL;  // 头结点的 next 置空return OK;
}
  • 区别

    • DestoryLinkList:销毁整个链表(头结点+数据结点)。

    • ClearLinkList:只清空数据结点,保留头结点。


7. 常见问题

Q1:为什么用 while (*L) 而不是 while ((*L)->next)
  • *L 是当前头指针,需要释放所有结点(包括头结点)。如果检查 (*L)->next,会漏掉头结点。

Q2:如果链表为空(只有头结点),会发生什么?
  • *L 指向头结点,第一次循环释放头结点后,*L 被置为 NULL,循环结束。

Q3:为什么不用递归实现?
  • :递归实现可能因链表过长导致栈溢出。迭代(循环)更安全高效。


8. 代码验证

可以通过打印结点地址验证释放过程:

Status DestoryLinkList(LinkList* L) {LNode* p;while (*L != NULL) {p = *L;printf("Freeing node at address: %p\n", p);  // 打印释放的结点地址*L = (*L)->next;free(p);}return OK;
}

总结

  • 核心操作:循环遍历链表,逐个释放结点,并更新头指针。

  • 关键点:二级指针修改头指针、free 释放内存、防止野指针。

  • 适用场景:当确定链表不再使用时调用,避免内存泄漏。

这个函数的作用是 用头插法创建一个包含 n 个结点的单链表。特点是 新结点总是插入在头结点之后,因此链表的顺序与输入顺序 相反。下面逐步解析其工作原理:


1. 函数参数

  • LinkList* L:二级指针,用于修改外部的头指针。

  • int n:要创建的结点数量。


2. 创建头结点

*L = (LinkList)malloc(sizeof(LNode));  // 分配头结点内存
(*L)->next = NULL;                     // 头结点的 next 初始化为 NULL
  • 作用:初始化一个空链表,只有头结点(不存储实际数据)。

  • 图示

    *L(头指针)│▼
    [头结点] → NULL

3. 头插法循环(for (i = n; i > 0; i--)

循环 n 次,每次创建一个新结点并插入到 头结点之后

步骤拆解
  1. 申请新结点内存

    LNode* newlnode = (LNode*)malloc(sizeof(LNode));
    • 为新结点分配内存,并通过 scanf 输入数据。

  2. 插入新结点

    newlnode->next = (*L)->next;  // 新结点的 next 指向原第一个结点
    (*L)->next = newlnode;        // 头结点的 next 指向新结点
    • 关键点:新结点插入后成为链表的 第一个数据结点

插入过程图示
  • 初始状态(只有头结点):

    [头结点] → NULL
  • 插入第一个结点(值为 1)

    [头结点] → [1] → NULL
  • 插入第二个结点(值为 2)

    [头结点] → [2] → [1] → NULL
  • 插入第三个结点(值为 3)

    [头结点] → [3] → [2] → [1] → NULL

4. 输入顺序与链表顺序的关系

  • 输入顺序:假设依次输入 1, 2, 3

  • 链表顺序3 → 2 → 1(与输入相反)。

  • 原因:每次新结点都插入在链表头部。


头插法尾插法
新结点插入头结点之后新结点追加到链表末尾
链表顺序与输入顺序相反链表顺序与输入顺序相同
无需维护尾指针需要维护尾指针 tail
时间复杂度:O(n)(每次插入为 O(1))时间复杂度:O(n)

6. 关键代码解析

newlnode->next = (*L)->next;  // 新结点的 next 指向原第一个结点
(*L)->next = newlnode;        // 头结点的 next 指向新结点
  • 类比:像排队时每次都让新来的人站到队伍最前面。

  • 操作顺序:必须先设置 newlnode->next,再修改 (*L)->next,否则会丢失原链表的引用。


7. 内存管理注意事项

  • 每个 malloc 分配的内存必须在链表销毁时通过 free 释放(如调用 DestoryLinkList)。

  • 如果输入 n 为 0,函数会创建一个只有头结点的空链表。


8. 示例输入输出

输入
CreateLinkList_H(&myList, 3);
// 依次输入:10, 20, 30
链表结构
头结点 → [30] → [20] → [10] → NULL

9. 常见问题

Q1:为什么头插法会导致顺序相反?
  • :每次新结点都插入在链表头部,类似“后来居上”。

Q2:如果 n 为负数会发生什么?
  • :循环不会执行,链表只有头结点(需在函数开头添加参数检查)。

Q3:头插法的时间复杂度是多少?
  • :O(n),因为每个结点的插入操作是 O(1),共 n 次。


总结

  • 核心思想:通过每次在头部插入新结点构建链表。

  • 特点:简单高效,但顺序与输入相反。

  • 适用场景:不需要保持输入顺序,或需要频繁在头部插入的场景(如栈的实现)。

ShowLinkList 函数原理详解(显示单链表内容)

这个函数的作用是 遍历并打印单链表中的所有数据结点,同时显示每个结点的序号。如果链表为空,会提示用户。以下是逐步解析:


1. 函数参数 const LinkList* L
  • const 修饰符:表示不会修改链表内容(安全保护)。

  • LinkList* L:二级指针,用于访问头结点(但这里只读不修改)。


2. 初始化指针 p
LNode* p = (*L)->next;  // p 指向第一个数据结点(跳过头结点)
  • 为什么从 (*L)->next 开始
    头结点(*L)不存储实际数据,它的 next 才指向第一个有效结点。


3. 检查链表是否为空
if (!p) {  // 等价于 if (p == NULL)puts("The LinkList is empty");return;
}
  • 逻辑:如果 p 为 NULL,说明头结点的 next 为空,链表无数据结点。


4. 遍历链表并打印数据
int i = 1;          // 结点序号从1开始
while (p != NULL) {  // 遍历直到链表末尾printf("%d : %d\n", i, p->data);  // 打印序号和数据i++;            // 序号递增p = p->next;    // p 移动到下一个结点
}
  • 关键点

    • p->data:当前结点的数据。

    • p = p->next:指针后移,实现遍历。


5. 示例输出

假设链表结构:

头结点 → [10] → [20] → [30] → NULL

调用 ShowLinkList(&list) 输出

1 : 10
2 : 20
3 : 30

如果链表为空,输出:

The LinkList is empty

6. 遍历过程图解
初始状态:
p = 头结点->next → [10] → [20] → [30] → NULL第一次循环:
打印 1:10,p 移动到 [20]第二次循环:
打印 2:20,p 移动到 [30]第三次循环:
打印 3:30,p 移动到 NULL(循环结束)

7. 为什么用 while (p) 而不是 while (p->next)
  • while (p):确保当前结点 p 有效时才打印数据(包括最后一个结点)。

  • 如果写成 while (p->next),会漏掉最后一个结点的数据!


8. 时间复杂度
  • O(n):需要遍历所有 n 个数据结点,每个结点访问一次。


9. 安全性注意事项
  1. const 保护:防止函数内意外修改链表。

  2. 空指针检查:避免访问 NULL->next(已通过 if (!p) 处理)。


总结

  • 功能:按顺序显示链表所有结点的数据和序号。

  • 关键操作:指针遍历 (p = p->next)、空链表检查。

  • 适用场景:调试、查看链表内容、交互式程序输出。

这个函数的作用是 用尾插法创建一个包含 n 个结点的单链表。特点是 新结点总是插入在链表末尾,因此链表的顺序与输入顺序 一致。下面逐步解析其工作原理:


1. 函数参数

  • LinkList* L:二级指针,用于修改外部的头指针。

  • int n:要创建的结点数量。


2. 创建头结点

*L = (LinkList)malloc(sizeof(LNode));  // 分配头结点内存
(*L)->next = NULL;                     // 头结点的 next 初始化为 NULL
  • 作用:初始化一个空链表,只有头结点(不存储实际数据)。

  • 图示

    *L(头指针)│▼
    [头结点] → NULL

3. 尾指针 p 的初始化

LNode* p = *L;  // p 初始指向头结点
  • p 的作用:始终指向当前链表的 最后一个结点(尾结点)。

  • 初始时:链表只有头结点,所以 p 指向头结点。


4. 尾插法循环(for (i = n; i > 0; i--)

循环 n 次,每次创建一个新结点并插入到 链表末尾

步骤拆解
  1. 申请新结点内存

    LNode* newlnode = (LNode*)malloc(sizeof(LNode));
    • 为新结点分配内存,并通过 scanf 输入数据。

  2. 初始化新结点

    newlnode->next = NULL;  // 新结点的 next 置空(因为它将是新的尾结点)
  3. 插入新结点到末尾

    p->next = newlnode;  // 原尾结点的 next 指向新结点
    p = newlnode;        // p 移动到新结点(更新尾指针)
    • 关键点:通过 p 直接找到链表末尾,实现 O(1) 时间复杂度的插入。

插入过程图示
  • 初始状态(只有头结点):

    [头结点] → NULL
    p → [头结点]
  • 插入第一个结点(值为 1)

    [头结点] → [1] → NULL
    p → [1]
  • 插入第二个结点(值为 2)

    [头结点] → [1] → [2] → NULL
    p → [2]
  • 插入第三个结点(值为 3)

    [头结点] → [1] → [2] → [3] → NULL
    p → [3]

5. 输入顺序与链表顺序的关系

  • 输入顺序:假设依次输入 1, 2, 3

  • 链表顺序1 → 2 → 3(与输入一致)。

  • 原因:每次新结点都追加到链表尾部。


尾插法头插法
新结点插入链表末尾新结点插入头结点之后
链表顺序与输入顺序相同链表顺序与输入顺序相反
需要维护尾指针 p无需维护尾指针
时间复杂度:O(n)时间复杂度:O(n)

7. 关键代码解析

p->next = newlnode;  // 将新结点链接到末尾
p = newlnode;        // 更新尾指针
  • 类比:像排队时每次都让新来的人站到队伍最后面。

  • 必要性:必须更新 p,否则下次插入无法找到新的末尾。


8. 内存管理注意事项

  • 每个 malloc 分配的内存必须在链表销毁时通过 free 释放(如调用 DestoryLinkList)。

  • 如果输入 n 为 0,函数会创建一个只有头结点的空链表。


9. 示例输入输出

输入
CreateLinkList_R(&myList, 3);
// 依次输入:10, 20, 30
链表结构
头结点 → [10] → [20] → [30] → NULL

10. 常见问题

Q1:为什么需要尾指针 p
  • :直接通过头指针找到链表末尾需要 O(n) 时间,而维护 p 可以在 O(1) 时间内访问末尾。

Q2:如果 p 不更新会怎样?
  • :所有新结点都会插入到原尾结点之后,但原尾结点不会更新,导致链表断裂。

Q3:尾插法的时间复杂度是多少?
  • :O(n),因为每个结点的插入操作是 O(1),共 n 次。


总结

  • 核心思想:通过维护尾指针,每次在链表末尾插入新结点。

  • 特点:保持输入顺序,适合需要顺序一致的场景。

  • 关键操作:尾指针更新 (p = newlnode)、内存分配与释放。

运行结果如下:

请输入数据:
90
请输入数据:
60
请输入数据:
45
1 : 90
2 : 60
3 : 45
第2个 : 60请按任意键继续. . .

注:该代码是本人自己所写,可能不够好,不够简便,欢迎大家指出我的不足之处。如果遇见看不懂的地方,可以在评论区打出来,进行讨论,或者联系我。上述内容全是我自己理解的,如果你有别的想法,或者认为我的理解不对,欢迎指出!!!如果可以,可以点一个免费的赞支持一下吗?谢谢各位彦祖亦菲!!!!! 

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

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

相关文章

Sentinel-自定义资源实现流控和异常处理

目录 使用SphU的API实现自定义资源 BlockException 使用SentinelResource注解定义资源 SentinelResourceAspect 使用Sentinel实现限流降级等效果通常需要先把需要保护的资源定义好&#xff0c;之后再基于定义好的资源为其配置限流降级等规则。 Sentinel对于主流框架&#…

Linux信号处理解析:从入门到实战

Linux信号处理全解析&#xff1a;从入门到实战 一、初识Linux信号&#xff1a;系统级的"紧急电话" 信号是什么&#xff1f; 信号是Linux系统中进程间通信的"紧急通知"&#xff0c;如同现实中的交通信号灯。当用户按下CtrlC&#xff08;产生SIGINT信号&…

Java的Selenium的特殊元素操作与定位之select下拉框

如果页面元素是一个下拉框&#xff0c;我们可以将此web元素封装为Select对象 Select selectnew Select(WebElement element); Select对象常用api select.getOptions();//获取所有选项select.selectBylndex(index);//根据索引选中对应的元素select.selectByValue(value);//选…

蓝桥云客 刷题统计

刷题统计 问题描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目&#xff0c;周六和周日每天做 b 道题目。请你帮小明计算&#xff0c;按照计划他将在第几天实现做题数大于等于 n 题&#xff1f; 输入格式 输入一行包含三个整数 a, b 和 …

三防笔记本有什么用 | 三防笔记本有什么特别

在现代社会&#xff0c;随着科技的不断进步&#xff0c;笔记本电脑已经成为人们工作和生活的重要工具。然而&#xff0c;在一些特殊的工作环境和极端条件下&#xff0c;普通笔记本电脑往往难以满足需求。这时&#xff0c;三防笔记本以其独特的设计和卓越的性能&#xff0c;成为…

智能体和RPA都需要程序思维,如何使用影刀的变量?

欢迎来到涛涛聊AI&#xff0c; 不管AI还是RPA&#xff0c;都需要用到编程思想才能完成批量工作。今天研究了下影刀的变量。 变量类型 根据变量值选择相应的类型&#xff0c;可选择任意一种影刀所支持的数据类型 变量值 指定变量中保存的值&#xff0c;会根据不同的类型设置…

【蓝桥杯】算法笔记3

1. 最长上升子序列(LIS) 1.1. 题目 想象你有一排数字,比如:3, 1, 2, 1, 8, 5, 6 你要从中挑出一些数字,这些数字要满足两个条件: 你挑的数字的顺序要和原来序列中的顺序一致(不能打乱顺序) 你挑的数字要一个比一个大(严格递增) 问:最多能挑出多少个这样的数字? …

性能测试之jmeter的基本使用

简介 Jmeter是Apache的开源项目&#xff0c;基于Java开发&#xff0c;主要用于进行压力测试。 优点&#xff1a;开源免费、支持多协议、轻量级、功能强大 官网&#xff1a;https://jmeter.apache.org/index.html 安装 安装步骤&#xff1a; 下载&#xff1a;进入jmeter的…

【NLP 面经 7、常见transformer面试题】

目录 1. 为何使用多头注意力机制&#xff1f; 2. Q和K使用不同权重矩阵的原因 3. 选择点乘而非加法的原因 4. Attention进行scaled的原因 5. 对padding做mask操作 6. 多头注意力降维原因 7. Transformer Encoder模块简介 8. 乘以embedding size的开方的意义 9. 位置编码 10. 其…

【深度学习】CNN简述

文章目录 一、卷积神经网络&#xff08;CNN&#xff09;二、CNN结构特性1. CNN 典型结构2. 局部连接3. 权重共享4.空间或时间上的次采样 三、理解层面 一、卷积神经网络&#xff08;CNN&#xff09; 卷积神经网络(Convolutional Neural Network&#xff0c;CNN)是一种用于处理…

理解OSPF 特殊区域NSSA和各类LSA特点

本文基于上文 理解OSPF Stub区域和各类LSA特点 在理解了Stub区域之后&#xff0c;我们再来理解一下NSSA区域&#xff0c;NSSA区域用于需要引入少量外部路由&#xff0c;同时又需要保持Stub区域特性的情况 一、 网络总拓扑图 我们在R1上配置黑洞路由&#xff0c;来模拟NSSA区域…

论文阅读笔记:Denoising Diffusion Implicit Models (5)

0、快速访问 论文阅读笔记&#xff1a;Denoising Diffusion Implicit Models &#xff08;1&#xff09; 论文阅读笔记&#xff1a;Denoising Diffusion Implicit Models &#xff08;2&#xff09; 论文阅读笔记&#xff1a;Denoising Diffusion Implicit Models &#xff08…

蓝桥杯2024年第十五届省赛真题-R 格式

题目链接&#xff1a; 思路&#xff1a; 通过数组模拟d的每一位&#xff0c;逐位进行计算&#xff0c;从而实现对d的精确处理。 代码&#xff1a; #include<bits/stdc.h> #define int long long using namespace std; const int N 2020;int n; string s; vector<i…

深入探索 Linux Top 命令:15 个实用示例

在 Linux 系统管理中&#xff0c;top 命令是系统性能监控不可或缺的工具。它能够实时显示系统的 CPU、内存、进程等资源的使用情况&#xff0c;帮助您快速识别性能瓶颈和异常进程。本文将详细介绍 15 个实用的 top 命令使用示例&#xff0c;旨在帮助您更高效地进行系统管理与优…

15.1linux设备树下的platform驱动编写(知识)_csdn

上一章我们详细的讲解了 Linux 下的驱动分离与分层&#xff0c;以及总线、设备和驱动这样的驱动框架。基于总线、设备和驱动这样的驱动框架&#xff0c; Linux 内核提出来 platform 这个虚拟总线&#xff0c;相应的也有 platform 设备和 platform 驱动。 上一章我们讲解了传统的…

Eclipse 视图(View)

Eclipse 视图(View) Eclipse 视图(View)是 Eclipse 界面的重要组成部分,它提供了用户交互的平台,使得用户可以通过图形界面来编辑、调试、分析代码等。在本文中,我们将深入探讨 Eclipse 视图的功能、使用方法以及它们在软件开发中的作用。 1. 视图的功能 Eclipse 视图具…

Python解决“数字插入”问题

Python解决“数字插入”问题 问题描述测试样例解题思路代码 问题描述 小U手中有两个数字 a 和 b。第一个数字是一个任意的正整数&#xff0c;而第二个数字是一个非负整数。她的任务是将第二个数字 b 插入到第一个数字 a 的某个位置&#xff0c;以形成一个最大的可能数字。 你…

ubuntu部署ollama+deepseek+open-webui

ubuntu部署ollamadeepseekopen-webui 全文-ubuntu部署ollamadeepseekopen-webui 大纲 Ollama部署 安装Ollama&#xff1a;使用命令apt install curl和curl -fsSL https://ollama.com/install.sh | sh ollama-v网络访问配置&#xff1a;设置环境变量OLLAMA_HOST0.0.0.0:11434&…

Java的Selenium常用的元素操作API

click 触发当前元素的点击事件 clear() 清空内容 sendKeys(...) 往文本框一类元素中写入内容 getTagName() 获取元素的的标签名 getAttribute(属性名) 根据属性名获取元素属性值 getText() 获取当前元素的文本值 isDisplayed() 查看元素是否显示 get(String url) 访…

洛谷题单3-P1035 [NOIP 2002 普及组] 级数求和-python-流程图重构

题目描述 已知&#xff1a; S n 1 1 2 1 3 … 1 n S_n 1\dfrac{1}{2}\dfrac{1}{3}…\dfrac{1}{n} Sn​121​31​…n1​。显然对于任意一个整数 k k k&#xff0c;当 n n n 足够大的时候&#xff0c; S n > k S_n>k Sn​>k。 现给出一个整数 k k k&#xff0…