数据结构线性表——单链表

前言:小伙伴们又见面啦,这篇文章我们来一起学习线性表的第二模块——单链表。

单链表的学习就要开始上强度啦,小伙伴们一定要努力,坚持!


目录

一.什么是单链表

二.单链表与顺序表的区别

三.单链表的实现

1.单链表的定义 

2.单链表的初始化

3.单链表节点的创建

 四.单链表的操作

1.单链表的打印

 2.单链表的尾插

3.单链表的头插

4.单链表的尾删

5.单链表的头删

6.单链表的查找

7.单链表在指定位置前插

8.单链表在指定位置后插

9.单链表在指定位置删除

10.单链表的销毁

五.完整代码展示

1.SLinkList.h

2.SLIinkList.c

test.c

六.总结


一.什么是单链表

通过物理模型我们可以这样理解,单链表就是每个数据之间都用一个链条来连接,而单的含义就是两个数据之间只有一条链子连接


二.单链表与顺序表的区别

我们已经知道,顺序表是用一个数组来存储和管理数据,因为数组的大小难以改变就算是我们能够通过动态内存管理来实现数组大小的不断扩容,但还是可能会出现内存浪费的情况

而单链表就可以完美的解决这个问题。

单链表可以实现存一个数据就开辟一块空间的完美内存控制。

如下图,我们每需要存入一个数据,就开辟一个空间,而每个空间之间,我们用指针来相连前一个空间在存储数据的同时,存储下一块空间的地址,这样一来,我们就可以杜绝空间浪费。而这样一小块一小块的空间,我们称之为节点

带着这个思路,我们就开始来真正的实现单链表。


三.单链表的实现

1.单链表的定义 

通过上边的思路,不难得出,我们需要用结构体来管理每块空间:

typedef int SLLDataType;
//定义单链表
typedef struct SLinkList
{SLLDataType data;//数据struct SLinkList* next;//链条
}SLLNode;

因为指针要指向下一个数据空间,而空间是结构体类型,所以该指针的类型就要是结构体

而指针的名字,规定为next,对等“下一个”的意思。


2.单链表的初始化

因为单链表创建之后是没有数据的,内存为空,所以单链表的初始化,也就是我们要使用单链表的时候,创建一个单链表类型的指针变量

    SLLNode* ssl = NULL;

注意当我们创建完一个指针变量之后,如果不对其进行任何赋值操作,就要置空,否则就会变成野指针


3.单链表节点的创建

初始时刻,单链表是没有任何数据的,那么想要存入数据,就要创建新的节点

一个节点同时包含该节点的数据和要指向下一个节点地址的指针

SLLNode* CreatNode(SLLDataType x)
{SLLNode* newnode = (SLLNode*)malloc(sizeof(SLLNode));if (newnode == NULL){perror("CreatNode->malloc");exit(-1);}newnode->data = x;	newnode->next = NULL;return newnode;
}

开辟节点的空间,我们用到malloc函数。

当我们新创建了一个节点之后,要将他的指针置空,因为我们并不需要在该函数中使用它的指针

至此,单链表的基础外壳就已经全部实现了,下面我们就要进行单链表的各种操作。 


 四.单链表的操作

1.单链表的打印

基于前边顺序表学习的经验,我们实现每一种操作之后,都要及时测试是否无误,所以我们要先有打印函数,那么单链表该如何打印呢???

有小伙伴说:这简单啊,不是每个节点都有指针指向下一个节点的地址吗,直接用指针遍历打印不就好了。

但是小伙伴们是否发现,虽然我们说每个节点都有一个指针来指向下一个节点,但是第一个节点的地址该怎么存放???,依此疑惑,我们引出头结点的定义。

头结点,我们用phead表示,让头结点去指向我们第一个节点的地址。

事实上,头结点还是我们在调用各种单链表操作函数时的形参

当我们新创建了单链表类型的变量,这个变量就是一个头节点,要用它去实现各种功能。

了解过头结点之后,我们就能够来实现打印函数了: 

void SLListPrint(SLLNode* phead)
{SLLNode* cur = phead;while (cur != NULL){printf("%d ", cur->data);cur = cur->next;}
}

 为了防止phead发生改变,我们用cur来作为临时变量进行操作

那么该如何理解while循环的判断条件(cur != NULL)呢???

SLLNode* ssl = NULL;

这是我们的头结点,也是实参,把它传给函数,并用phead接收,再赋值给cur

如果cur一开始就为空,那么就证明当前单链表没有节点,无需打印如果不为空,就证明单链表有节点,开始循环打印,每打印一次,就让cur指向下一个节点直到cur再次为空时,说明没有下一个节点了,循环便退出。


 2.单链表的尾插

先来分析一下,单链表该如何尾插呢???

如果单链表本来有数据,那么只需要找到它的最后一个节点,让它的指针指向新节点的地址

但如果本来就没有数据,那么尾插不就等于创建一个新节点。

//尾插
void SLListPushBack(SLLNode** phead, SLLDataType x)
{assert(phead);SLLNode* newnode = CreatNode(x);if (*phead == NULL){*phead = newnode;}else{//找尾SLLNode* tail = *phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

首先进行一个assert(phead)的断言,因为如果phead为空的话,就说明单链表不存在

尾插要创建一个新的节点,所以我们先调用CreatNode函数,并创建newnode节点变量来接收

接着进行头结点的判断如果为空,说明当前没有数据,直接让头结点指向新的节点

如果不为空,就从头开始往后找到最后一个节点,然后让最后一个节点的指针指向新的节点

要注意这里while循环的判断条件如果tail是最后一个节点了,那么它的指针就为空,因为它没有节点可以指向,所以要置空防止变成野指针,也就是tail->next == NULL,随后就让该指针指向新节点即可。

下面请注意最重要的一点

不知道小伙伴们有没有注意到形式参数phead是一个二级指针类型呀?

好奇怪,为什么是二级指针呢???

这就又要扯到形参是实参的一份临时拷贝这个知识点上了。

注意,我们的实参ssl是一个一级结构体指针类型,那么想要改变它所关联的结构体的内部数据,就需要传它的地址

接收一个指针的地址,就需要二级指针了。

 这里经常有小伙伴会认为,只要我是指针,我就能改变值,这是一个很大的误区

下面我们就来进行测试:

void test()
{SLLNode* ssl = NULL;SLListPushBack(&ssl, 1);SLListPushBack(&ssl, 2);SLListPushBack(&ssl, 3);SLListPushBack(&ssl, 4);SLListPrint(ssl);
}

注意我们调用函数时,传的都是实参ssl的地址,而打印并不会改变什么,所以不需要传地址。

结果如下:


3.单链表的头插

头插相对于尾插就简单多了,我们只需要创建一个新节点让它的指针指向第一个节点的地址,再让头结点指向新节点的地址

//头插
void SLListPushFront(SLLNode** phead,SLLDataType x)
{assert(phead);SLLNode* newnode = CreatNode(x);newnode->next = *phead;*phead = newnode;
}

首先进行一个assert(phead)的断言,因为如果phead为空的话,就说明单链表不存在

第一个节点的地址存在*phead里边,所以直接将*phead赋值给新节点的指针,然后再让*phead指向新节点

这段代码同样也满足原单链表没有节点的情况,这样*phead就为空,赋值之后同样满足最后一个节点指向NULL

测试如下:

void test()
{SLLNode* ssl = NULL;SLListPushBack(&ssl, 1);SLListPushBack(&ssl, 2);SLListPushBack(&ssl, 3);SLListPrint(ssl);printf("\n");SLListPushFront(&ssl, 5);SLListPushFront(&ssl, 6);SLListPrint(ssl);
}

结果如下:


4.单链表的尾删

想要尾删,那就和尾插一样,要先找到尾节点,然后将指向尾结点的前一个节点的指针置空,因为是用malloc开辟的空间,所以需要再将尾节点free掉,就可以完成尾删的操作了。

但是尾删还需要关注没有节点只有一个节点有多个节点这三种情况。

那有小伙伴就会问,为什么一个节点和多个节点不能用同一个代码呢???

我们来分析一下,如果只有一个节点,我们是要直接对*phead进行free的,这就需要二级指针

但是如果有多个节点,我们就可以用一个临时变量指针来free空间,不需要二级指针

  • 没有节点好说,那就是*phead为空
  • 当只有一个节点时,我们只需要将*phead给free了再将*phead置空即可。
  • 当有多个节点时,我们就要找尾了,这时候又有一个问题,找到尾之后,该如何找到尾的前一个节点呢???
  • 有一个便捷的方法,既然tail->next是下一个节点,那么tail->next->next就是下下个节点当tail->next->next为尾节点时,此时的tail->next就为尾结点的前一个节点啦

实现如下: 

//尾删
void SLListPopBack(SLLNode** phead)
{//空assert(*phead);//一个节点if ((*phead)->next == NULL){free(*phead);*phead = NULL;}else//多个节点{//找尾SLLNode* tail = *phead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}

判断单链表是否为空,我们直接用assert函数,强制断言,防止过渡尾删出现错误

测试如下:

void test()
{SLLNode* ssl = NULL;SLListPushBack(&ssl, 1);SLListPushBack(&ssl, 2);SLListPushBack(&ssl, 3);SLListPushFront(&ssl, 5);SLListPrint(ssl);printf("\n");SLListPopBack(&ssl);SLListPopBack(&ssl);SLListPopBack(&ssl);SLListPrint(ssl);
}

结果如下:


5.单链表的头删

头删需要和尾删一样考虑没有节点,只有一个节点和多个节点这三种情况吗???

我们来分析一下,没有节点肯定是要用assert函数来断言的;紧接着,删除第一个节点,那就是要让头结点指向第一个节点的next如果只有一个节点,那么就是让*phead指向NULL,而此时第一个节点的next不就为NULL吗

所以一个节点和多个节点就不用分开考虑了。

//头删
void SLListPopFront(SLLNode** phead)
{//空assert(*phead);//非空SLLNode* tail = *phead;*phead = (*phead)->next;free(tail);tail = NULL;
}

临时变量tail来记录第一个节点,接着让*phead指向下一个节点,最后通过tail来释放第一个节点

测试如下:

void test()
{SLLNode* ssl = NULL;SLListPushBack(&ssl, 1);SLListPushBack(&ssl, 2);SLListPushBack(&ssl, 3);SLListPushBack(&ssl, 4);SLListPrint(ssl);printf("\n");SLListPopFront(&ssl);SLListPopFront(&ssl);SLListPopFront(&ssl);SLListPrint(ssl);
}

结果如下:


6.单链表的查找

我们规定单链表的查找为:通过输入数据来查找找到则返回该数据的地址,找不到则返回NULL

其实和打印也是一个差不多的类型,我们就直接来实现:

//查找
SLLNode* SLListFind(SLLNode* phead, SLLDataType x)
{SLLNode* cur = phead;while (cur != NULL){if (cur->data == x){return cur;}else{cur = cur->next;}}return NULL;
}

7.单链表在指定位置前插

单链表不像顺序表一样,因为是数组,所以可以直接在某个位置进行插入。而单链表的指定位置插入则可以分为前插和后插

首先要知道,想在单链表中插入数据,就必须有指针,所以我们要先找到指定位置的地址,说到找地址,就要用到单链表的查找功能了,所以单链表在指定位置的插入就必须和查找挂钩使用

//pos前插
void SLListInsertFront(SLLNode** phead, SLLNode* pos, SLLDataType x)
{assert(phead && *phead && pos);if (*phead == pos){//头插SLListPushFront(phead, x);}else{SLLNode* val = *phead;while (val->next != pos){val = val->next;}SLLNode* newnode = CreatNode(x);val->next = newnode;newnode->next = pos;}
}

注意,pos位置前插入数据,我们就不是仅仅要断言phead了,同时还要断言*phead和pos

  • 如果*phead为空,说明链表为空,空链表哪有什么指定位置的概念;
  • 如果pos为空,就说明你给了一个空位置,空位置怎么插入?

值得注意的是,前插要判断pos是否为第一个节点,如果是第一个节点,就直接调用头插函数

那有小伙伴们可能会问,为什么pos是第一个节点就要特殊处理呢???

我们来看while的循环条件如果pos是第一个节点,那么val就是pos,这样val->next永远都不可能是pos,最后为空,对空指针解引用操作,程序就崩了

如果是前插,就必须先找到pos位置前的节点让它去指向新的节点newnode,再让新节点指向pos位置的地址

测试如下:

void test()
{SLLNode* ssl = NULL;SLListPushBack(&ssl, 1);SLListPushBack(&ssl, 2);SLListPushBack(&ssl, 3);SLListPushBack(&ssl, 4);SLListPrint(ssl);printf("\n");SLLNode* pos = SLListFind(ssl, 3);SLListInsertFront(&ssl, pos, 5);SLListPrint(ssl);
}

比如我们要在数据3所处的位置的前边插入,就先用查找操作找出3节点的地址,并赋值给pos,接着进行前插,结果如下:


8.单链表在指定位置后插

指定位置后插入数据就比前插简单多了,因为我们并不用去找pos位置前的节点,只需要让pos节点的指针指向新节点,再让新节点去指向pos节点的后一个节点即可

//pos后插
void SLListInsertBack(SLLNode* pos, SLLDataType x)
{assert(pos);SLLNode* newnode = CreatNode(x);SLLNode* tmp = pos->next;newnode->next = tmp;pos->next = newnode;
}

同样先断言一下pos定义tmp去接收pos节点的下一个节点的地址,让newnode指向tmp,再让pos去指向newnode

测试如下:

void test()
{SLLNode* ssl = NULL;SLListPushBack(&ssl, 1);SLListPushBack(&ssl, 2);SLListPushBack(&ssl, 3);SLListPushBack(&ssl, 4);SLListPrint(ssl);printf("\n");SLLNode* pos = SLListFind(ssl, 3);SLListInsertBack(pos, 5);SLListPrint(ssl);
}

依然是在数据3的后边插入,结果如下:


9.单链表在指定位置删除

单链表在指定位置的删除也比较简单,找到pos节点的前一个,让它直接指向pos节点的后一个,再将pos节点释放即可

那么指定位置的删除是否要考虑节点数问题呢???

首先没有节点或是表不存在肯定是不可取的,所以要先断言。

其次,考虑单节点和多节点,如果只有一个节点,那么pos位置前就是头节点pheadpos节点后就是NULL让头节点直接指向NULL,同样合情合理,所以单节点和多节点可以一起处理。

此外,指定位置的删除也需要判断pos是否为第一个节点

//pos删
void SLListErase(SLLNode** phead, SLLNode* pos)
{assert(phead && *phead && pos);if (*phead == pos){//头删SLListPopFront(phead);}else{SLLNode* val = *phead;while (val->next != pos){val = val->next;}val->next = val->next->next;free(pos);pos = NULL;}
}

测试如下:

void test()
{SLLNode* ssl = NULL;SLListPushBack(&ssl, 1);SLListPushBack(&ssl, 2);SLListPushBack(&ssl, 3);SLListPushBack(&ssl, 4);SLListPrint(ssl);printf("\n");SLLNode* pos = SLListFind(ssl, 3);SLListErase(&ssl, pos);SLListPrint(ssl);
}

删除数据3所在位置的节点,结果如下:


10.单链表的销毁

单链表的所有空间也是使用malloc函数开辟的,所以使用之后要及时销毁。

那么只需要从头到尾将单链表遍历一遍,一个一个free即可:

//销毁
void SLListDestroy(SLLNode** phead)
{while (*phead){SLLNode* tmp = *phead;*phead = (*phead)->next;free(tmp);}
}

五.完整代码展示

1.SLinkList.h

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLLDataType;//定义单链表
typedef struct SLinkList
{SLLDataType data;struct SLinkList* next;
}SLLNode;//打印
void SLListPrint(SLLNode* phead);
//尾插
void SLListPushBack(SLLNode** phead, SLLDataType x);
//尾删
void SLListPopBack(SLLNode** phead);
//头插
void SLListPushFront(SLLNode** phead, SLLDataType x);
//头删
void SLListPopFront(SLLNode** phead);
//查找
SLLNode* SLListFind(SLLNode* phead, SLLDataType x);
//pos前插
void SLListInsertFront(SLLNode** phead, SLLNode* pos, SLLDataType x);
//pos后插
void SLListInsertBack(SLLNode* pos, SLLDataType x);
//任意删
void SLListErase(SLLNode** phead, SLLNode* pos);

2.SLIinkList.c

#include "SLinkList.h"
//打印
void SLListPrint(SLLNode* phead)
{SLLNode* cur = phead;while (cur != NULL){printf("%d ", cur->data);cur = cur->next;}printf("NULL");
}
//创建新节点
SLLNode* CreatNode(SLLDataType x)
{SLLNode* newnode = (SLLNode*)malloc(sizeof(SLLNode));if (newnode == NULL){perror("CreatNode->malloc");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}
//尾插
void SLListPushBack(SLLNode** phead, SLLDataType x)
{assert(phead);SLLNode* newnode = CreatNode(x);if (*phead == NULL){*phead = newnode;}else{//找尾SLLNode* tail = *phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
//头插
void SLListPushFront(SLLNode** phead, SLLDataType x)
{assert(phead);SLLNode* newnode = CreatNode(x);newnode->next = *phead;*phead = newnode;
}
//尾删
void SLListPopBack(SLLNode** phead)
{assert(phead);//空assert(*phead);//一个节点if ((*phead)->next == NULL){free(*phead);*phead = NULL;}else//多个节点{//找尾SLLNode* tail = *phead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}
//头删
void SLListPopFront(SLLNode** phead)
{assert(phead);//空assert(*phead);//非空SLLNode* tail = *phead;*phead = (*phead)->next;free(tail);tail = NULL;
}
//查找
SLLNode* SLListFind(SLLNode* phead, SLLDataType x)
{SLLNode* cur = phead;while (cur != NULL){if (cur->data == x){return cur;}else{cur = cur->next;}}return NULL;
}
//pos前插
void SLListInsertFront(SLLNode** phead, SLLNode* pos, SLLDataType x)
{assert(phead && *phead && pos);SLLNode* val = *phead;while (val->next != pos){val = val->next;}SLLNode* newnode = CreatNode(x);val->next = newnode;newnode->next = pos;
}
//pos后插
void SLListInsertBack(SLLNode* pos, SLLDataType x)
{assert(pos);SLLNode* newnode = CreatNode(x);SLLNode* tmp = pos->next;newnode->next = tmp;pos->next = newnode;
}
//pos删
void SLListErase(SLLNode** phead, SLLNode* pos)
{assert(phead && *phead && pos);SLLNode* val = *phead;while (val->next != pos){val = val->next;}val->next = val->next->next;free(pos);pos = NULL;
}
//销毁
void SLListDestroy(SLLNode** phead)
{while (*phead){SLLNode* tmp = *phead;*phead = (*phead)->next;free(tmp);}
}

test.c

#include "SLinkList.h"
void test()
{SLLNode* ssl = NULL;SLListPushBack(&ssl, 1);SLListPushBack(&ssl, 2);SLListPushBack(&ssl, 3);SLListPushBack(&ssl, 4);SLListPrint(ssl);printf("\n");SLLNode* pos = SLListFind(ssl, 3);SLListErase(&ssl, pos);SLListPrint(ssl);
}
int main()
{test();return 0;
}

测试代码仅为例子,小伙伴们可以根据自己的需求自由更改。


六.总结

单链表基本知识的分享到这里就结束啦,博主到最后才发现这篇博客的字数竟然破万了!!!

其实大部分都是代码所占哈哈哈。

最后小伙伴们不要忘记一键三连哦!!!

我们下期再见啦!

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

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

相关文章

ClickHouse 学习之从高级到监控以及备份(二)

第 一 部分 高级篇 第 1 章 Explain 查看执行计划 在 clickhouse 20.6 版本之前要查看 SQL 语句的执行计划需要设置日志级别为 trace 才能可以看到&#xff0c;并且只能真正执行 sql&#xff0c;在执行日志里面查看。在 20.6 版本引入了原生的执行计划的语法。在 20.6.3 版本成…

【MongoDB】集群搭建实战 | 副本集 Replica-Set | 分片集群 Shard-Cluster | 安全认证

文章目录 MongoDB 集群架构副本集主节点选举原则搭建副本集主节点从节点仲裁节点 连接节点添加副本从节点添加仲裁者节点删除节点 副本集读写操作副本集中的方法 分片集群分片集群架构目标第一个副本集第二个副本集配置集初始化副本集路由集添加分片开启分片集合分片删除分片 安…

第23期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练 Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…

Java 高效生成按指定间隔连续递增的列表(int,double)

简介 Java 按照指定间隔生成连续递增的List 列表&#xff08;引入Stream 类和流操作来提高效率&#xff09;&#xff1a; 1. 生成递增的List< Integer> Testpublic void test009(){int start 1;int interval 2;int count 10;List<Integer> list IntStream.ite…

【亚马逊云科技产品测评】活动征文|亚马逊云科技AWS之EC2详细测评

引言 &#xff08;授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道&#xff09; 在当前的数字化时代&#xff0c;云服务已…

算法题:203. 移除链表元素(递归法、设置虚拟头节点法等3种方法)Java实现创建链表与解析链表

1、算法思路 讲一下设置虚拟头节点的那个方法&#xff0c;设置一个新节点指向原来链表的头节点&#xff0c;这样我们就可以通过判断链表的当前节点的后继节点值是不是目标删除值&#xff0c;来判断是否删除这个后继节点了。如果不设置虚拟头节点&#xff0c;则需要将头节点和后…

【SpringSecurity】简介

SpringSecurity简介 Spring Security 的前身是Acegi Security&#xff0c;在被收纳为Spring 子项目后正式更名为Spring Security。Spring Security目前已经到了6.x&#xff0c;并且加入了原生OAuth2.0框架&#xff0c;支持更加现代化的密码加密方式。可以预见&#xff0c;在Ja…

HDFS速通之一文详解HDFS全部知识点

文章目录 HDFS介绍HDFS体系HDFS的Shell介绍HDFS的常见Shell操作 HDFS案例实操Java操作HDFS配置环境 HDFS的回收站HDFS的安全模式实战&#xff1a;定时上传数据至HDFSHDFS的高可用和高扩展HDFS的高可用(HA)HDFS的高扩展(Federation) HDFS介绍 HDFS是一个分布式的文件系统 假设…

oracle-sql语句执行过程

客户端输入sql语句。 sql语句通过网络到达数据库实例。 服务器进程(server process)接收到sql语句。 sql – 解析成执行计划&#xff0c;然后sql才能执行。 会将sql和sql的执行计划缓存到共享池中。解析: 会消耗很多资源。 从数据库找数据&#xff0c;先从buffer cache中找&a…

数组相关的面试OJ题

目录 1.移除元素 方法1【暴力求解】 方法2【双指针】 2.删除两个有序数组中的重复项 方法1【双指针】 3.合并两个有序数组 方法1【暴力求解】 方法2【开辟新数组】---选择较小的尾插 方法3【三指针】---选择较大的头插 4.有序数组的合并 方法1【三指针】 写一个算…

Git extension 中合并工具kdiff3乱码问题

打开kdiff3合并工具&#xff0c;setting->region settings 设置下面的编码格式为utf-8就可以啦&#xff01; 注意&#xff1a;需要在合并工具中设置编码格式&#xff0c; 在git 中配置编码格式没有效果

人工智能AI创作系统ChatGPT网站系统源码+AI绘画系统支持GPT4.0/支持Midjourney局部重绘

一、前言 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建…

Leetcode刷题详解——组合

1. 题目链接&#xff1a;77. 组合 2. 题目描述&#xff1a; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[…

归并排序(c语言代码实现)

归并排序&#xff08;稳定的排序&#xff09;&#xff1a; 归并排序是一种分治策略的排序算法&#xff0c;其基本思想是将待排序数组分成两个子数组&#xff0c;分别对这两个子数组进行排序&#xff0c;然后合并这两个已经排序好的子数组&#xff0c;最终得到完整的已排序数组…

Sentinel黑白名单授权规则解读

目录 基本介绍 代码实战 架构说明 RequestOriginParser的实现类 网关添加请求头 配置授权规则 基本介绍 授权规则可以对请求方来源做判断和控制。 很多时候&#xff0c;我们需要根据调用来源来判断该次请求是否允许放行&#xff0c;这时候可以使用 Sentinel 的来源…

酒水展示预约小程序的效果如何

酒的需求度非常高&#xff0c;各种品牌、海量经销商组成了庞大市场&#xff0c;而在实际经营中&#xff0c;酒水品牌、经销商、门店经营者等环节往往也面临着品牌传播拓客引流难、产品展示预约订购难、营销难、销售渠道单一等痛点。 那么商家们应该怎样解决呢&#xff1f; 可以…

31.Map集合用法、遍历、排序与常见API

概要&#xff1a; java.util 中的集合类包含 Java 中某些最常用的类。最常用的集合类是 List 和 Map。 Map 提供了一个更通用的元素存储方法。Map 集合类用于存储元素对&#xff08;称作“键”和“值”&#xff09;&#xff0c;其中每个键映射到一个值。 本文主要介绍java ma…

如何使用VSCode来查看二进制文件

2023年11月6日&#xff0c;周一下午 目录 方法1&#xff1a;安装插件Binary Viewer然后用vscode打开一个二进制文件&#xff0c;并点击右上角的"HEX"方法2&#xff1a;安装插件Binary然后用vscode打开一个二进制文件&#xff0c;并点击右上角的"B" 方法1&…

C-DS二叉树_另一棵树的子树

Description 给你两棵二叉树tree1和tree2,检验tree1中是否包含和tree2具有相同结构和结点值的子树。如果存在,输出true;否则,输出false。 Input 第一行输入t,表示有t个测试样例。 第二行首先输入n1,接着输入n1个整数,表示二叉树tree1。 第三行首先输入n2,接着输入n…

Vite 的基本原理,和 webpack 在开发阶段的比较

目录 1&#xff0c;webpack 的流程2&#xff0c;Vite 的流程简单编译 3&#xff0c;总结 主要对比开发阶段。 1&#xff0c;webpack 的流程 开发阶段大致流程&#xff1a;指定一个入口文件&#xff0c;对相关的模块&#xff08;js css img 等&#xff09;先进行打包&#xff0…