【数据结构】单链表的C语言实现--万字详解介绍

📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:数据结构
🎯长路漫漫浩浩,万事皆有期待
文章目录
- 1.链表
- 1.1 链表的概念及结构:
- 1.2 顺序表的缺陷
- 1.3 链表的意义:
- 2.链表的分类:
- 2.1 单向或双向:
- 2.2 带头或不带头:
- 2.3 循环或非循环:
- 3.单链表的实现:
- 3.1 工程文件:
- 3.2 接口设计
- 3.3 `重点`接口实现:
- ①.创建新节点:
- ②.打印单链表:
- ③.单链表尾插:
- ④.单链表头插:
- ⑤.单链表尾删:
- ⑥.单链表头删:
- ⑦.单链表查找:
- ⑧.单链表插入:
- Ⅰ、单链表前插:
- Ⅱ、单链表后插:
- ⑨.单链表删除:
- I、删除pos位置的节点
- II、删除pos位置之后的节点
- ⑩.单链表销毁:
- 4.链表实现全部源码:
- 4.1 SList.h:
- 4.2 SList.c:
- 4.3 test.c:
- 5.总结:
1.链表
1.1 链表的概念及结构:
链表(linked list)是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
1.2 顺序表的缺陷
在之前的博客中,我们对顺序表有了较深层次的认知,同时我们发现,顺序表在创建和使用时,存在一些问题:
- 当空间不足时,顺序表需要进行增容,但由于顺序表本质为数组,数据在物理上连续存放,因此在扩容时要付出较大的空间代价。为避免频繁扩容,基本每次都扩容 2 倍,当扩容次数增多时,就很有可能会造成一定程度的空间浪费。
- 由于顺序表内数据连续存储,我们在向顺序表中位置插入或删除数据时,就需要挪动大量的数据,效率不高。
1.3 链表的意义:
人们针对顺序表的这些缺陷,设计出了链表这个概念来克服这些不足。
而链表克服这些缺陷的方法是,将每一个节点划分为数据与指针两部分,数据部分用于存储数据而指针部分用于指向下一节点。这样做的好处是通过指针指向下一节点,于是数据的物理存储就可以不必连续,于是无论是在扩容,还是在插入与删除数据时,都能快速、方便的实现,并且不会造成很大的空间浪费。
2.链表的分类:
2.1 单向或双向:

2.2 带头或不带头:

2.3 循环或非循环:

(注:虽然有很多种不同的链表结构,但是最常用的链表结构主要是两种:无头单向非循环链表与带头双向循环链表)
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
带头双向循环链表:结构最复杂,一般用在单独存储数据。虽然它结构复杂,但在实际使用中使用代码实现后,会带来很多优势,实现反而更加简单。并且在我们的实际中所使用的链表数据结构,一般都是带头双向循环链表。
3.单链表的实现:
3.1 工程文件:
与顺序表的实现方式类似,使用三个文件进行代码书写:
SeqList.h:存放函数声明、包含其他头文件、定义宏。
SeqList.c:书写函数定义,书写函数实现。
test.c:书写程序整体执行逻辑。
其中,我们的接口实现主要研究的是函数实现文件 SeqList.c 中的内容。
单链表和顺序表的结构略有不同,单链表的主结构为节点。
一个节点由两部分组成:data和next。data为我们存储数据的地方。而next则为下一个节点的地址。
typedef struct SListNode
{int data;struct SListNode* next;
}SLTNode;
上面我们设计的是不带哨兵位的单链表。这种结构设计相对于带哨兵位的链表的缺点就是设计接口函数时需要考虑链表是否为空的情况。
哨兵位也叫哨兵节点,哑节点。该节点并不存储任何数据,只是为了方便操作而引入这个节点。起一个站岗放哨的作用。所以形象的叫它哨兵位。如果一个链表有哨兵位,那么链表的第一个元素应该是链表第二个节点对应的元素。这时链表永不为空,这就可以避免边界问题的处理,简化代码与减少代码出错的可能性。
3.2 接口设计
void SLPrint(SLNode* SLHead); //打印单链表
SLNode* BuyListNode(SLDataType x); //申请新节点
void SLPushBack(SLNode** pphead, SLDataType x); //单链表尾插
void SLPushFront(SLNode** pphead, SLDataType x); //单链表头插
void SLPopBack(SLNode** pphead); //单链表尾删
void SLPopFront(SLNode** pphead); //单链表头删
void SLFind(SLNode* phead, SLDataType x); //单链表查找
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataType x); //单链表前插
void SLInsertAfter(SLNode* pos, SLDataType x); //单链表后插
void SLErase(SLNode** pphead, SLNode* pos); //单链表删除
void SLEraseAfter(SLNode** pphead, SLNode* pos);//单链表后删除
void SLDestory(SLNode** pphead); //单链表销毁
我们发现绝大多数接口的参数都为二级指针,这是为什么?
我们先了解一下平时单链表的测试用例:
void TestList()
{SLTNode* plist = NULL;
}
链表的一开始是空的。所以我们插入数据时,需要让plist指向我们新节点。就相当于改变plist的指向。plist是一级指针,那么要改变plist就要传它的地址&plist,为二级指针,所以也需要用二级指针来接收该参数。
就好比当我们要改变一个int类型的变量时a,我们需要传它的地址&a,那么函数的形参就应该用int* 接收;对于指针也是这样,一个int* 的指针变量p,它也是变量,我们需要改变这个值,就应该传它的地址&p,那么函数参数就应该那int* * 接收。
而对于一些接口就不需要传二级指针,就拿打印来说吧,因为我并不需要改变plist,我只需要通过结构体指针访问结构内的next成员,并迭代到下一个节点,然后打印出数据就可以,所以不需要传二级指针。
3.3 重点接口实现:
这里是本文的重点,即 SeqList.c 文件中的接口具体实现:
①.创建新节点:
在各项操作前,需动态创建新节点,再对新创建的节点进行操作。
SLNode* BuyListNode(SLDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("newnode:>");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}
②.打印单链表:
执行操作前无需进行断言,原因是循环条件为空指针,则条件不满足并停止循环,不会出现死循环。
根据指针循环操作,打印数据后使指针指向节点内存放下一节点地址的指针 next。
void SListPrint(SLNode* SLHead)
{SLNode* cur = SLHead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}
③.单链表尾插:
- 如果此时头节点为空指针,说明此时单链表内没有节点,此时只需要将申请来的新节点作为头节点即可。
- 若头节点不为空,则应当首先找到尾节点,使尾结点指针指向申请来的新节点后,再使新节点指针指向空即可。
void SLPushBack(SLNode** pphead, SLDataType x)
{SLNode* newnode = BuyListNode(x);if (*pphead == NULL){*pphead = newnode;}else{//找到尾结点SLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
测试接口:

④.单链表头插:
头插实现非常简单,首先申请新节点,先使新节点的指针指向原来的头节点,再使链表头指向新节点即可。
这里不需要考虑空节点的原因是,就算链表内没有内容,即首节点为空,新节点指针指向空也没有问题(申请新节点时原本就指向空)。
void SLPushFront(SLNode** pphead, SLDataType x)
{SLNode* newnode = BuyListNode(x);newnode->next = *pphead;*pphead = newnode;
}
测试接口:

⑤.单链表尾删:
执行操作前需要进行非空判断,防止传入空指针。
- 若链表内只有一个节点,则直接释放并置空即可。
- 若有一个以上节点,则当执行下面三个步骤:
- 首先寻找尾节点,找到后将其释放并置空。
- 但在释放前,必须将倒数第二个节点的指针指向空,否则将在指向最后一个被释放的节点时,变成野指针。
- 单向链表无法回溯,于是我们应当定义另一个指针,在每次移动尾节点指针前保存当前节点的位置。
void SLPopBack(SLNode** pphead)
{//判断链表是否为空:if (*pphead == NULL){return;}//若只有一个节点,直接释放并置空即可:if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//若有多个节点则执行多步尾删:else{SLNode* tail = *pphead;SLNode* prev = NULL;//找到尾节点:while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;}
}
测试接口:

⑥.单链表头删:
执行操作前判断指针非空,防止对空指针进行操作。
使链表头指向第二个节点,接着将第一个节点释放并置空即可。
void SLPopFront(SLNode** pphead)
{if (*pphead == NULL){return;}else{SLNode* next = (*pphead)->next;free(*pphead);*pphead = next;}
}
测试接口:

⑦.单链表查找:
采用遍历思想,依次对每一个节点的数据进行判断,符合条件即进行打印,不符合条件则继续向后遍历,直至指向 NULL,并且因此无需进行非空判断。
void SLFind(SLNode* phead, SLDataType x)
{SLNode* cur = phead;int count = 1;while (cur){if (cur->data == x){printf("第%d个节点:%p -> %d\n", count++, cur, x);}cur = cur->next;}
}
测试接口:

⑧.单链表插入:
单链表在插入新节点时有两种插入方式,一种是在目标节点前方插入,另一种是在目标节点后方插入,因此需要分别进行实现:
Ⅰ、单链表前插:
前插有两种情况:
- 在首节点处插入,此时即相当于头插操作。
- 在非首节点前插入,此时首先申请新节点,接着遍历单链表,找到插入位置后,使新节点指向目标节点后插入其前方。
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataType x)
{//首先申请新节点SLNode* newnod = BuyListNode(x);//头插if (*pphead == pos){newnod->next = *pphead;*pphead = newnod;}else{//找到目标节点 pos 的前一个节点:SLNode* PostPrev = *pphead;while (PostPrev->next != pos){PostPrev = PostPrev->next;}//找到后插入新节点:PostPrev->next = newnod;newnod->next = pos;}
}
测试接口:
同下图
Ⅱ、单链表后插:
在前插操作时,我们需要遍历整个链表来查找插入位置,因此效率较为低下,于是我们通常使用后插的方式插入新节点。后插直接申请节点,插入目标位置后即可。
void SLInsertAfter(SLNode* pos, SLDataType x)
{SLNode* newnode = BuyListNode(x);newnode->next = pos->next;pos->next = newnode;
}
测试接口:

⑨.单链表删除:
I、删除pos位置的节点
分为两种情况进行处理:
- 一种情况是整个链表中只有一个节点,则删除节点相当于释放整个数组并置空。
- 另一种情况是含有一个以上节点,此时只需要让目标节点的前一个节点指针指向后一个节点,再释放目标节点并置空即可。
void SLErase(SLNode** pphead, SLNode* pos)
{//头删if (*pphead == pos){*pphead = pos->next;free(pos);pos = NULL;}else{ //找到目标节点 pos 的前一个节点:SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
测试接口:
同下图
II、删除pos位置之后的节点
和在pos位置之前插入一样,我们发现删除pos位置的节点,也很麻烦。因为需要找pos的前一个位置,所以这种设计也比较麻烦。
所以有时我们会删除pos位置之后的节点,这样就很方便了。
既然要删除pos位置之后的节点,那么我就需要将pos位置的节点pos位置的 next 的 next 链接起来。那么我们首先用next拷贝记录一下pos->next ,然后,再将pos->next赋值为next->next,也就是pos往后的两个节点。最后释放next位置的节点即可。
注意:pos位置的下一个节点不能是尾结点后面的节点
// 删除指定pos位置后的一个节点
void SLEraseAfter(SLNode** pphead, SLNode* pos)
{assert(pos);assert(pos->next);// 删除的不能是尾结点后面的位置SLNode* next = pos->next;// 拷贝pos的下一个节点pos->next = next->next;// 将pos的next变为下一个节点的nextfree(next);// 释放之前pos的下一个节点next = NULL;
}
测试接口:

⑩.单链表销毁:
遍历整个单链表,将每一个节点都进行释放并置空即可。
注意:记得把*pphead置空,防止野指针问题出现
void SListDestory(SLNode** pphead)
{if (*pphead == NULL){return;}SLNode* cur = *pphead;while (cur){SLNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}
测试接口:

4.链表实现全部源码:
4.1 SList.h:
#pragma once#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
typedef int SLDataType;//单链表节点结构:
typedef struct SListNode
{SLDataType data;struct SListNode* next;
}SLNode;void SLPrint(SLNode* SLHead); //打印单链表
SLNode* BuyListNode(SLDataType x); //申请新节点
void SLPushBack(SLNode** pphead, SLDataType x); //单链表尾插
void SLPushFront(SLNode** pphead, SLDataType x); //单链表头插
void SLPopBack(SLNode** pphead); //单链表尾删
void SLPopFront(SLNode** pphead); //单链表头删
void SLFind(SLNode* phead, SLDataType x); //单链表查找
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataType x); //单链表前插
void SLInsertAfter(SLNode* pos, SLDataType x); //单链表后插
void SLErase(SLNode** pphead, SLNode* pos); //单链表删除
void SLEraseAfter(SLNode** pphead, SLNode* pos);//单链表后删除
void SLDestory(SLNode** pphead); //单链表销毁
4.2 SList.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//打印单链表:
void SLPrint(SLNode* SLHead)
{SLNode* cur = SLHead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}//申请节点:
SLNode* BuyListNode(SLDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("newnode:>");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}//单链表尾插:
void SLPushBack(SLNode** pphead, SLDataType x)
{SLNode* newnode = BuyListNode(x);if (*pphead == NULL){*pphead = newnode;}else{//找到尾结点SLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}//单链表头插:
void SLPushFront(SLNode** pphead, SLDataType x)
{SLNode* newnode = BuyListNode(x);newnode->next = *pphead;*pphead = newnode;
}//单链表尾删:
void SLPopBack(SLNode** pphead)
{//判断链表是否为空:if (*pphead == NULL){return;}//若只有一个节点,直接释放并置空即可:if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//若有多个节点则执行多步尾删:else{SLNode* tail = *pphead;SLNode* prev = NULL;//找到尾节点:while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;}
}//单链表头删
void SLPopFront(SLNode** pphead)
{if (*pphead == NULL){return;}else{SLNode* next = (*pphead)->next;free(*pphead);*pphead = next;}
}//单链表查找
void SLFind(SLNode* phead, SLDataType x)
{SLNode* cur = phead;int count = 1;while (cur){if (cur->data == x){printf("第%d个节点:%p -> %d\n", count++, cur, x);}cur = cur->next;}
}//单链表前插:
void SLInsertFront(SLNode** pphead, SLNode* pos, SLDataType x)
{//首先申请新节点SLNode* newnod = BuyListNode(x);if (*pphead == pos){newnod->next = *pphead;*pphead = newnod;}else{//找到目标节点 pos 的前一个节点:SLNode* PostPrev = *pphead;while (PostPrev->next != pos){PostPrev = PostPrev->next;}//找到后插入新节点:PostPrev->next = newnod;newnod->next = pos;}
}//单链表后插:
void SLInsertAfter(SLNode* pos, SLDataType x)
{SLNode* newnode = BuyListNode(x);newnode->next = pos->next;pos->next = newnode;
}//单链表删除:
void SLErase(SLNode** pphead, SLNode* pos)
{if (*pphead == pos){*pphead = pos->next;free(pos);pos = NULL;}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
// 删除指定pos位置后的一个节点
void SLEraseAfter(SLNode** pphead, SLNode* pos)
{assert(pos);assert(pos->next);// 删除的不能是尾结点后面的位置SLNode* next = pos->next;// 拷贝pos的下一个节点pos->next = next->next;// 将pos的next变为下一个节点的nextfree(next);// 释放之前pos的下一个节点next = NULL;
}
//单链表销毁:
void SLDestory(SLNode** pphead)
{if (*pphead == NULL){return;}SLNode* cur = *pphead;while (cur){SLNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}
4.3 test.c:
#define _CRT_SECURE_NO_WARNINGS 1 #include "SList.h"// 测试尾插、带节点的头插
void TestList1()
{SLNode* plist = NULL;//SLPushBack(&plist, 1);//SLPushBack(&plist, 2);//SLPushBack(&plist, 3);//SLPushBack(&plist, 4);//SLPrint(plist);//SLPushBack(&plist, 5);//SLPrint(plist);SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLPushFront(&plist, 4);SLPrint(plist);SLPushFront(&plist, 5);SLPrint(plist);}// 测试头插、尾删
void TestList2()
{SLNode* plist = NULL;SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLPushFront(&plist, 4);SLPushFront(&plist, 5);SLPrint(plist);SLPopBack(&plist);SLPopBack(&plist);SLPopBack(&plist);SLPopBack(&plist);SLPrint(plist);SLPopBack(&plist);SLPrint(plist);}// 测试头删
void TestList3()
{SLNode* plist = NULL;SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLPushFront(&plist, 4);SLPushFront(&plist, 5);SLPrint(plist);SLPopFront(&plist);SLPrint(plist);SLPopFront(&plist);SLPrint(plist);SLPopFront(&plist);SLPrint(plist);SLPopFront(&plist);SLPrint(plist);SLPopFront(&plist);SLPrint(plist);}void TestList4()
{SLNode* plist = NULL;SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLPushFront(&plist, 2);SLPushFront(&plist, 5);SLNode* pos = SLFind(plist, 2);int i = 1;while (pos){printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);pos = SLFind(pos->next, 2);}// 修改 3->30pos = SLFind(plist, 3);if (pos){pos->data = 30;}SLPrint(plist);
}void TestList5()
{SLNode* plist = NULL;SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLPushFront(&plist, 2);SLPushFront(&plist, 5);// 3前面插入一个30SLNode* pos = SLFind(plist, 3);if (pos){SLInsertFront(&plist, pos, 30);}SLPrint(plist);// 5后面插入一个10pos = SLFind(plist, 5);if (pos){SLInsertAfter( pos, 10);}SLPrint(plist);
}
void TestList6()
{SLNode* plist = NULL;SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLPushFront(&plist, 2);SLPushFront(&plist, 5);SLPrint(plist);// 删除3位置节点SLNode* pos = SLFind(plist, 3);if (pos){SLErase(&plist, pos);}SLPrint(plist);// 删除4位置之后节点pos = SLFind(plist, 5);if (pos){SLEraseAfter(&plist, pos);}SLPrint(plist);SLDestory(&plist);SLPrint(plist);}int main()
{//TestList1();//TestList2();//TestList3();//TestList4();//TestList5();TestList6();return 0;
}
5.总结:
今天我们认识并学习了单向无头链表的相关概念、结构与接口实现,并且针对每个常用的功能接口都进行了实现分解,并对各个接口的各项注意点都进行了强调说明,希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

相关文章:
【数据结构】单链表的C语言实现--万字详解介绍
📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:数据结构 🎯长路漫漫浩浩,万事皆有期待 文章目录1.链表1.1 链表的概念…...
电子科技大学软件工程期末复习笔记(七):测试策略
目录 前言 重点一览 V模型 回归测试 单元测试 集成测试 重要概念 自顶向下的集成方法 自底向上的集成方法 SMOKE方法 系统测试 验收测试 α测试 β测试 本章小结 前言 本复习笔记基于王玉林老师的课堂PPT与复习大纲,供自己期末复习与学弟学妹参考用…...
逆向-还原代码之除法 (Interl 64)
除法和32位差不多,毕竟背后的数学公式是一样的。区别只是32位的乘法需要两个寄存器来存放大数相乘的结果,而64位的不需要,一个寄存器就能存下。所以在64位的环境下,多了右移32位这条指令,其他指令一样。 //code #incl…...
Python WebDriver自动化测试
Webdriver Selenium 是 ThroughtWorks 一个强大的基于浏览器的开源自动化测试工具,它通常用来编写 Web 应用的自动化测试。 Selenium 2,又名 WebDriver,它的主要新功能是集成了 Selenium 1.0 以及 WebDriver(WebDriver 曾经是…...
2023年微信小程序获取手机号授权登录注册详细教程,包含服务端教程
前言 小程序中有很多地方都会用到用户的手机号,比如登陆注册,填写收货地址等等。有了这个组件可以快速获取微信绑定手机号码,无须用户填写。网上大多数教程还是往年的,而微信官方的api已做了修改。本篇文章将使用最新的方法获取手…...
YOLOv8模型学习笔记
在前面的章节中博主学习了YOLOv5的相关知识,从YOLOv5的数据增强处理到模型设计,从正负样本匹配策略到LOSS设计,今天博主学习的是YOLOv8,同为ultralytics公司的产品,两者无论是思想层面还是具体的设计方面都有着异曲同工…...
Java SE知识点1
一、continue、break、和return的区别是什么? 在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要 在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词: 1. continue :指跳出当前的这一次循环,…...
华为OD机试模拟题 用 C++ 实现 - 端口合并(2023.Q1)
最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明端口合并题目输入输出示例一输入输出说明示例二输入输出说明示例三输入输出说明...
C++ Primer Plus 第6版 读书笔记(3) 第3章 处理数据
目录 3.1 简单变量 3.1.1 变量名 *位与字节 3.1.4 无符号类型 3.1.7 C如何确定常量的类型 C是在 C 语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言,是C语言的超集。本书是根据2003年的ISO/ANSI C标准编写的,通过大量短…...
ArrayList源码解读
参数 //默认初始容量private static final int DEFAULT_CAPACITY 10;//空数组(用于空实例)private static final Object[] EMPTY_ELEMENTDATA {};//用于默认大小空实例的共享空数组private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA {};//保存数据的数组tra…...
python实战应用讲解-【语法高级篇】时间与日期(附python示例代码)
目录 保持时间、计划任务和启动程序 time 模块 time.time() 函数 time.sleep() 函数 Python3 日期和时间...
D. Moscow Gorillas(双指针 + 区间分析)
Problem - D - Codeforces 在冬天,莫斯科动物园的居民非常无聊,尤其是大猩猩。你决定娱乐他们,带了一个长度为n的排列p到动物园。长度为n的排列是由n个从1到n的不同整数以任意顺序组成的数组。例如,[2,3,1,5,4]是一个排列…...
华为OD机试题,用 Java 解【相同数字的积木游戏 1】问题
最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…...
Python实现GWO智能灰狼优化算法优化BP神经网络分类模型(BP神经网络分类算法)项目实战
说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后获取。1.项目背景灰狼优化算法(GWO),由澳大利亚格里菲斯大学学者 Mirjalili 等人于2014年提出来的一种群智能优…...
无线蓝牙耳机哪个牌子好?2023质量好的无线蓝牙耳机推荐
近几年,随着蓝牙技术的不断进步,使用蓝牙耳机的人也越来越多。蓝牙耳机的出现,不仅能让我们摆脱线带来的约束,还能提升我们学习和工作的效率。最近看到很多人问,无线蓝牙耳机哪个牌子好?下面,我…...
Qt之QTableView自定义排序/过滤(QSortFilterProxyModel实现,含源码+注释)
一、效果示例图 1.1 自定义表格排序示例图 本文过滤条件为行索引取余2等于0时返回true,且从下图中可以看到,奇偶行是各自挨在一起的。 1.2 自定义表格过滤示例图 下图添加两列条件(当前数据大于当前列条件才返回true,且多个列…...
电商(强一致性系统)的场景设计
领域拆分:如何合理地拆分系统? 一般来说,强一致性的系统都会牵扯到“锁争抢”等技术点,有较大的性能瓶颈,而电商时常做秒杀活动,这对系统的要求更高。业内在对电商系统做改造时,通常会从三个方面…...
算法与数据结构(一)
一、时间复杂度 一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。 时间复杂度为一个算法流程中,常数操作数量的一个指标。常用O(读作big O)来表示。具体来说,这个算法流程中,发生了多…...
【Python】元组如何创建?
嗨害大家好鸭!我是小熊猫~ Python 元组 Python 的元组与列表类似, 不同之处在于元组的元素不能修改。 元组使用小括号,列表使用方括号。 元组创建很简单,只需要在括号中添加元素, 并使用逗号隔开即可。 如下实例…...
qt操作文件以及字符串转换
//从文件加载英文属性与中文属性对照表QFile file(":/propertyname.txt");if (file.open(QFile::ReadOnly)) {//QTextStream方法读取速度至少快百分之30#if 0while(!file.atEnd()) {QString line file.readLine();appendName(line);}#elseQTextStream in(&file)…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...
uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...
