数据结构之双链表(C语言)


数据结构之双链表(C语言)

  • 1 链表的分类
  • 2 双向链表的结构
  • 3 双向链表的节点创建与初始化
    • 3.1 节点创建函数
    • 3.2 初始化函数
  • 4 双向链表插入节点与删除节点的前序分析
  • 5 双向链表尾插法与头插法
    • 5.1 尾插函数
    • 5.2 头插函数
  • 6 双向链表的尾删法与头删法
    • 6.1尾删函数
    • 6.2 头删函数
  • 7 双向链表在指定位置插入或删除数据
    • 7.1 查找指定节点函数
    • 7.2 在指定位置插入数据
    • 7.3 在指定位置删除数据
  • 8 销毁双向链表与打印函数
    • 8.1 销毁函数
    • 8.2 打印函数
  • 9 双向链表全程序及演示结果
    • 9.1 List.h文件
    • 9.2 List.c文件
    • 9.3 test.c文件

1 链表的分类

链表根据是否带头(哨兵位)、是否双向(单、双)、是否循环三种特性而分为8种不同的链表。上一节我们讲述了最常见的两种链表之一的单链表(不带头单向不循环链表),本节我们来介绍另一种类型:双链表(带头双向循环链表)。

2 双向链表的结构

双向链表也是由每个节点构成的,只不过相较于单链表,双向列表的每个节点由三部分组成。第一部分是该节点所指向的上一节点的指针(地址),第二部分是该节点所存储的数据,第三部分是该节点所指向的下一节点的指针(地址)。
在这里插入图片描述

代码定义如下:

typedef int LTDatatype;//对涉及链表内的int类型进行重命名typedef struct ListNode
{LTDatatype data;//该节点所存储的数据struct ListNode* prev;//指向前一个节点的指针struct ListNode* next;//指向后一个节点的指针
}LTNode;

3 双向链表的节点创建与初始化

3.1 节点创建函数

在单链表中,若单链表为空,我们便直接将其phead置为NULL。而在双向链表为空时,链表中只剩下一个头节点(哨兵位),在对其初始化时能否将prevnext指针都置为NULL

解决这一问题需要我们从双向链表的定义入手,双向链表是带头双向循环链表。在上一个问题中prevnext指针都为空,并不是一个循环。故此时的头节点并不能构成一个空的双向链表。正确操作是将头节点的prev指针与next指针都指向头节点自身,形成自循环。如此才是一个空的双向链表。在节点创建的其他方面与单链表的节点创建函数无异。
代码如下:

LTNode* LTBuyNode(LTDatatype x)
{LTNode* Newnode = (LTNode* )malloc(sizeof(LTNode));//申请一个节点,用一级指针Newnode存储该节点地址if (Newnode == NULL)//判断是否申请成功{perror("malloc fail");exit(1);}//申请成功Newnode->data = x;//将节点数据复制给dataNewnode->prev = Newnode->next = Newnode;//将prev与next指针都指向本节点地址,即Newnode所存储的地址return Newnode;
}

3.2 初始化函数

对一个空的双向链表进行初始化即对其头节点进行初始化。我们不妨将头节点命名为phead,内部数据data一般情况下均赋值为**-1**。在3.1中,我们之所以将节点创建函数的返回值类型设置为LTNode*,目的就是为了在初始化函数中直接调用它,再将它的返回值赋值给phead完成初始化。
代码如下:

void Init(LTNode** pphead)//注意这里使用的是二级指针来接受形参
{*pphead = LTBuyNode(-1);//对头节点进行初始化
}

在上面函数的形参中我们使用的是二级指针来接受传参,这种传参方式取决于测试文件test.c中的代码编写。具体见下:
test.c:

LTNode* plist = NULL;//定义一个空指针
Init(&plist);//初始化这个指针,使其成为双向空链表的头节点

我们若想避免在初始化函数中使用二级指针来接受传参,可以使用如下编写方式:
test.c:

LTNode* plist = Init();//将Init的返回值直接赋值给plist,在Init内即完成头节点phead的创建

List.c

LTNode* Init()
{LTNode* phead = LTBuyNode(-1);//创建头节点并完成初始化return phead;
}

4 双向链表插入节点与删除节点的前序分析

在单链表中我们是使用二级指针来来作为形参接收传参,这是因为单链表在为空链表是其头节点地址为NULL,传参后形参开辟空间所临时拷贝的值也是NULL,在栈区空间为NULL基础上进行插入与删除对原链表plist不会有影响,原链表plist仍然保持初始状态。
但是,在双向链表中由于空链表的情况下依然有一个头节点(哨兵位),其一级指针plist所存储的就不再是NULL,而是头节点(哨兵位)的地址。并且头节点(哨兵位)的地址不能发生改变,也就意味着后续的插入与删除都不能改变头节点(哨兵位)的位置。故我们对以下两种插入与删除函数形参部分的设置进行分析:
一、与单链表的插入保持一致,依然使用二级指针接收传参。
首先这种设置是肯定行得通的,但在删除节点时会存在将头节点(哨兵位)也删除的情况。若使用这种形参设置就要做好对头节点(哨兵位)的保护。
二、使用一级指针接收传参
之所以双向链表能使用一级指针接收传参,而单链表不能如此是因为双向链表为空时其头节点(哨兵位)有实际的地址,而单链表却为NULL。在头节点(哨兵位)有实际地址的情况下,将地址作为实参传给对应的插入与删除函数的形参(一级指针),之后无论是插入还是删除都是在这个实际地址之后进行操作故同样会对原链表头节点(哨兵位)之后的节点位置造成改变。相比之下由于单链表的头节点地址为NULL,便不会有上面的变化。此外,使用一级指针来作为形参的好处在于无论是插入还是删除都不会影响头节点(哨兵位)的位置。这是因为头节点(哨兵位)地址作为实参传到形参后,是由形参phead存储。此时一级指针plistphead均同时指向头节点(哨兵位)地址,不仅在phead存储的地址后进行插入与删除可以影响plist存储的实参后的内容,而且即使误将phead所存储的头节点(哨兵位)删除,还有plist指向头节点地址(虽然此时这块地址存储的内容已经为空),保证了头节点(哨兵位)位置的恒定,所以并没有违反双向空链表的规则。
综上所述,二者相较之下建议使用设置二作为形参设置。(后续的插入与删除操作以设置二为例)

5 双向链表尾插法与头插法

5.1 尾插函数

执行尾插操作时关键在于链接建立的顺序。首先我们要让新节点newnodeprevnext指针同原链表连接,然后改变原链表尾节点的位置,让尾节点为新插入的节点newnode
具体步骤一:
在双向链表中,找尾十分容易,头节点(哨兵位)的prev指针指向即为尾节点。所以让newnode->prev = phead->prev接完成了新节点与尾节点的连接,再让newnode->next = phead接完成了新节点与头节点phead的连接。循环设置的一半完成。
具体步骤二:
原本尾节点的next指针指向的是pheadnewnode尾插进来后要使其指向newnode让newnode成为新的尾节点,即phead->prev->next = newnode。然后再将头节点(哨兵位)的prev指针指向新的尾节点newnode,即phead->prev = newnode注意:步骤二中两步的先后顺序不能发生改变。
​如图所示:
在这里插入图片描述
代码如下:

void LTPushBack(LTNode* phead, LTDatatype x)
{//判断传参不为空assert(phead);LTNode* newnode = LTBuyNode(x);//步骤一newnode->prev = phead->prev;//newnode->prev指向尾节点newnode->next = phead;//newnode->next指向头节点//步骤二phead->prev->next = newnode;//原本尾节点指向新节点newnodephead->prev = newnode;//将新节点newnode变为新的尾节点//注意步骤二中两步的先后顺序不能发生改变
}

5.2 头插函数

5.1知,将新节点插入到头节点的前面即尾插(因为是循环链表)。故将新节点插入到头节点的后面即头插。头插法的关键与尾插一致,均为链接建立的顺序。首先我们要让新节点newnode与原链表的第二个节点phead->next建立联系,然后再将新节点newnode与头节点phead连接。
具体步骤一:
首先我们要找到第二个节点phead->next,然后让新节点newnodenext指针指向第二个节点phead->next,即newnode->next = phead->next再将第二个节点phead->nextprev指针指向新节点newnode,即phead->next->prev = newnode
具体步骤二:
原本pheadnext指针是指向第二个节点phead->next,新节点newnode插入进来后要让pheadnext指针指向newnode,即phead->next = newnode。然后再让newnodeprev指针指向phead,即newnode->prev = phead
代码如下:

void LTPushFront(LTNode* phead, LTDatatype x)
{//判断传参不为空assert(phead);LTNode* newnode = LTBuyNode(x);//步骤一newnode->next = phead->next;phead->next->prev = newnode;//步骤二newnode->prev = phead;phead->next = newnode;
}

6 双向链表的尾删法与头删法

6.1尾删函数

既然要删除链表中的节点,我们就要保证链表中不能只有一个头节点,即链表不能为空。此外,在删除的过程中,我们要确保双向链表的结构不受到破坏,也就是我们不能直接删除掉对应节点,否则该节点前面的节点与后面的节点就无法连接到一起,故应该先把要删除的节点保存下来,完成上述连接操作后再删除该节点。
代码如下:

void LTPopBack(LTNode* phead)
{//链表必须有效且不能为空(只有一个哨兵位)assert(phead && phead->next != phead);//将尾节点先保存下来LTNode* del = phead->prev;//步骤一:重新设置尾节点del->prev->next = phead;phead->prev = del->prev;//步骤二:删除原本尾节点(del)free(del);del = NULL;
}

6.2 头删函数

6.1中尾删函数步骤相同,先把要删除的节点保存下来,将头节点(哨兵位)与第三个节点连接起来,在将先前所保存的第二个节点删除。
代码如下:

void LTPopFront(LTNode* phead)
{//链表必须有效且不能为空(只有一个哨兵位)assert(phead && phead->next != phead);//将第二个节点先保存下来LTNode* del = phead->next;//步骤一:重新设置第二个节点del->next->prev = phead;phead->next = del->next;//步骤二:删除原本第二个节点(del)free(del);del = NULL;
}

7 双向链表在指定位置插入或删除数据

7.1 查找指定节点函数

要想在指定位置插入或删除数据就要先找到指定位置的节点。在双向链表中查找指定节点需要对链表进行遍历。代码如下:

LTNode* Find(LTNode* phead, LTDatatype x)
{//判断传参不为空,并且链表不为空(只有一个哨兵位)assert(phead && phead->next != phead);//从第一个有效节点开始查找,该节点用pcur存储LTNode* pcur = phead->next;while (pcur != phead)//当查找到头节点(哨兵位)时表示没找到,退出循环{if (pcur->data == x)//注意这里是判断语句,要用“==”而不是赋值的“=”{return pcur;//找到指定节点}pcur = pcur->next;//没找到指定节点,继续向后遍历}return NULL;//在该链表中不存在指定节点
}

7.2 在指定位置插入数据

与尾插和头插思路一致,先完成新节点newnode与插入位置之后的节点的连接,再完成newnode与插入位置之前的节点的连接。我们以在指定的pos节点之后插入数据为例:
代码如下:

void Insert(LTNode* pos, LTDatatype x)
{//判断传参不为空assert(pos);//创建要插入的节点LTNode* newnode = LTBuyNode(x);//步骤一:完成与插入位置后的节点的连接newnode->next = pos->next;pos->next->prev = newnode;//步骤二:完成与插入位置前的节点的连接newnode->prev = pos;pos->next = newnode;
}

7.3 在指定位置删除数据

首先使用查找函数找到pos节点,然后将pos节点的前后节点连接,再将pos节点删除即可。
代码如下:

void LTErase(LTNode* phead, LTNode* pos)
{//判断传参不为空,并且pos节点不是头节点(哨兵位)assert(pos && pos != phead);//步骤一:将pos节点前后的节点连接pos->prev->next = pos->next;pos->next->prev = pos->prev;//步骤二:删除pos节点free(pos);pos = NULL;
}

8 销毁双向链表与打印函数

8.1 销毁函数

与前文中删除节点类似,只不过销毁时需要借助循环来完成,并且在销毁过程中我们需要保存本次循环删除的节点的下一个节点,并且在后续循环中不断更新。
代码如下:

void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//此时pcur也指向phead,phead还没有被删除free(phead);phead = NULL;
}

其实无论是LTErase还是LTDestroy理论上都要传二级指针,因为我们需要让形参影响实参,但是我们为了接口的一致性都是用一级指针来接收传参。用一级来接收传参的弊端在于当我们在函数内将phead(或find)指向的地址的空间(存储内容)释放掉后,在函数内将形参phead(或find)置为NULL却并不能将函数外的实参plist(或Pos)也置为NULL。因此解决办法是:在调用完函数后手动将实参置为NULL

8.2 打印函数

总体与单链表的打印相同,只不过双向链表的打印需要我们跳过头节点(哨兵位)。

void LTPrint(LTNode* phead)
{assert(phead && phead->next != phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}

9 双向链表全程序及演示结果

9.1 List.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDatatype;typedef struct ListNode
{LTDatatype data;struct ListNode* prev;struct ListNode* next;
}LTNode;//节点创建函数
LTNode* LTBuyNode(LTDatatype x);//初始化函数01
//void Init(LTNode** pphead)//初始化函数02
LTNode* Init();//尾插函数
void LTPushBack(LTNode* phead, LTDatatype x);//头插函数
void LTPushFront(LTNode* phead, LTDatatype x);//尾删函数
void LTPopBack(LTNode* phead);//头删函数
void LTPopFront(LTNode* phead);//查找函数
LTNode* LTFind(LTNode* phead, LTDatatype x);//在pos位置之后插入
void LTInsert(LTNode* pos, LTDatatype x);//删除POS节点
void LTErase(LTNode* phead, LTNode* pos);//销毁链表
void LTDestroy(LTNode* phead);//打印函数
void LTPrint(LTNode* phead);

9.2 List.c文件

#include"List.h"//节点创建函数
LTNode* LTBuyNode(LTDatatype x)
{LTNode* Newnode = (LTNode* )malloc(sizeof(LTNode));//申请一个节点,用一级指针Newnode存储该节点地址if (Newnode == NULL)//判断是否申请成功{perror("malloc fail");exit(1);}//申请成功Newnode->data = x;//将节点数据复制给dataNewnode->prev = Newnode->next = Newnode;//将prev与next指针都指向本节点地址,即Newnode所存储的地址
}//初始化函数01
//void Init(LTNode** pphead)//注意这里使用的是二级指针来接受形参
//{
//	*pphead = LTBuyNode(-1);//对头节点进行初始化
//}//初始化函数02
LTNode* Init()
{LTNode* phead = LTBuyNode(-1);return phead;
}//尾插函数
void LTPushBack(LTNode* phead, LTDatatype x)
{//判断传参不为空assert(phead);LTNode* newnode = LTBuyNode(x);//步骤一newnode->prev = phead->prev;//newnode->prev指向尾节点newnode->next = phead;//newnode->next指向头节点//步骤二phead->prev->next = newnode;//原本尾节点指向新节点newnodephead->prev = newnode;//将新节点newnode变为新的尾节点//注意步骤二中两步的先后顺序不能发生改变
}//头插函数
void LTPushFront(LTNode* phead, LTDatatype x)
{//判断传参不为空assert(phead);LTNode* newnode = LTBuyNode(x);//步骤一newnode->next = phead->next;phead->next->prev = newnode;//步骤二newnode->prev = phead;phead->next = newnode;
}//尾删函数
void LTPopBack(LTNode* phead)
{//链表必须有效且不能为空(只有一个哨兵位)assert(phead && phead->next != phead);//将尾节点先保存下来LTNode* del = phead->prev;//步骤一:重新设置尾节点del->prev->next = phead;phead->prev = del->prev;//步骤二:删除原本尾节点(del)free(del);del = NULL;
}//头删函数
void LTPopFront(LTNode* phead)
{//链表必须有效且不能为空(只有一个哨兵位)assert(phead && phead->next != phead);//将第二个节点先保存下来LTNode* del = phead->next;//步骤一:重新设置第二个节点del->next->prev = phead;phead->next = del->next;//步骤二:删除原本第二个节点(del)free(del);del = NULL;
}//查找函数
LTNode* LTFind(LTNode* phead, LTDatatype x)
{//判断传参不为空,并且链表不为空(只有一个哨兵位)assert(phead && phead->next != phead);//从第一个有效节点开始查找,该节点用pcur存储LTNode* pcur = phead->next;while (pcur != phead)//当查找到头节点(哨兵位)时表示没找到,退出循环{if (pcur->data == x)//注意这里是判断语句,要用“==”而不是赋值的“=”{return pcur;//找到指定节点}pcur = pcur->next;//没找到指定节点,继续向后遍历}return NULL;//在该链表中不存在指定节点
}//在pos位置之后插入
void LTInsert(LTNode* pos, LTDatatype x)
{//判断传参不为空assert(pos);//创建要插入的节点LTNode* newnode = LTBuyNode(x);//步骤一:完成与插入位置后的节点的连接newnode->next = pos->next;pos->next->prev = newnode;//步骤二:完成与插入位置前的节点的连接newnode->prev = pos;pos->next = newnode;
}//删除POS节点
void LTErase(LTNode* phead, LTNode* pos)
{//判断传参不为空,并且pos节点不是头节点(哨兵位)assert(pos && pos != phead);//步骤一:将pos节点前后的节点连接pos->prev->next = pos->next;pos->next->prev = pos->prev;//步骤二:删除pos节点free(pos);pos = NULL;
}//销毁链表
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//此时pcur也指向phead,phead还没有被删除free(phead);phead = NULL;
}//打印函数
void LTPrint(LTNode* phead)
{assert(phead && phead->next != phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}

9.3 test.c文件

运行结果图:
在这里插入图片描述

#include"List.h"
void Test01()
{/*LTNode* plist = NULL;Init(&plist);*///若想让初始化函数Init内不用二级指针来接受传参//可按如下方式编写LTNode* plist = Init();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPrint(plist);LTNode* find = LTFind(plist, 3);//LTInsert(find, 66);LTErase(plist,find);find = NULL;LTPrint(plist);LTDestroy(plist);
}int main()
{Test01();
}

全文至此结束!!!
写作不易,不知各位老板能否给个一键三连或是一个免费的赞呢(▽)(▽),这将是对我最大的肯定与支持!!!谢谢!!!(▽)(▽)

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

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

相关文章

【0x007A】HCI_Write_Secure_Connections_Host_Support命令详解

目录 一、命令概述 二、命令格式及参数 2.1. HCI_Write_Secure_Connections_Host_Support命令格式 2.2. Secure_Connections_Host_Support 三、生成事件及参数 3.1. HCI_Command_Complete事件格式 3.2. Status 四、命令执行流程梳理 4.1. 命令发送阶段 4.2. 命令接收…

第一节 环境搭建

Visual Studio Visual Studio 2019 密码&#xff1a;gd24 组件 安装即可

《Spring Framework实战》4:Spring Framework 文档

欢迎观看《Spring Framework实战》视频教程 概述 历史&#xff0c; 设计理念&#xff0c; 反馈&#xff0c; 开始。 核心技术 IoC 容器、事件、资源、i18n、 验证、数据绑定、类型转换、SpEL、AOP、AOT。 测试 Mock 对象、TestContext 框架、 Spring MVC 测试&#xff0c;…

【adb】5分钟入门adb操作安卓设备

ADB&#xff08;Android Debug Bridge&#xff09; 是一个多功能的命令行工具&#xff0c;用于与 Android 设备进行交互、调试和管理。它提供了对设备的直接控制&#xff0c;能够帮助开发者进行调试、安装应用、传输文件等。 目录 将设备和电脑连接 adb shell 文件的基本操…

Tauri教程-基础篇-第二节 Tauri的核心概念上篇

“如果结果不如你所愿&#xff0c;就在尘埃落定前奋力一搏。”——《夏目友人帐》 “有些事不是看到了希望才去坚持&#xff0c;而是因为坚持才会看到希望。”——《十宗罪》 “维持现状意味着空耗你的努力和生命。”——纪伯伦 Tauri 技术教程 * 第四章 Tauri的基础教程 第二节…

Ubuntu 下载安装 elasticsearch7.17.9

参考 https://blog.csdn.net/qq_26039331/article/details/115024218 https://blog.csdn.net/mengo1234/article/details/104989382 过程 来到 Es 的版本发布列表页面&#xff1a;https://www.elastic.co/downloads/past-releases#elasticsearch 根据自己的系统以及要安装的…

深度学习与计算机视觉 (博士)

文章目录 零、计算机视觉概述一、深度学习相关概念1.学习率η2.batchsize和epoch3.端到端(End-to-End)、序列到序列(Seq-to-Seq)4.消融实验5.学习方式6.监督学习的方式(1)有监督学习(2)强监督学习(3)弱监督学习(4)半监督学习(5)自监督学习(6)无监督学习(7)总结&#xff1a;不同…

深入Android架构(从线程到AIDL)_18 SurfaceView的UI多线程02

目录 2、 使用SurfaceView画2D图 范例一 设计GameLoop(把小线程移出来) 范例二 2、 使用SurfaceView画2D图 范例一 以SurfaceView绘出Bitmap图像设计SpriteView类别来实作SurfaceHolder.Callback接口首先来看个简单的程序&#xff0c;显示出一个Bitmap图像。这个图像就构…

数据库相关面试重点知识

一、Mysql索引 1.索引的本质 索引是帮助 Mysql 高效获取数据的排好序的数据结构。 索引的数据结构&#xff1a; 二叉树红黑树Hash表B-Tree&#xff08;BTree&#xff09; Question&#xff1a;为什么加入索引之后效率就会变高呢&#xff1f; 以上图为例&#xff0c;如果…

ansible-api分析(VariableManager变量)

一. 简述&#xff1a; ansible是一个非常强大的工具&#xff0c;可以支持多种类型(字符,数字,列表&#xff0c;字典等)的变量。除了有大量的内置变量及fact变量&#xff0c;也可以通过多种方式进行变量自定义 。不同方式定义的变量&#xff0c;优先级也不太一样&#xff0c;之…

【Uniapp-Vue3】image媒体组件属性

如果我们想要在页面上展示图片就需要使用到image标签。 这部分最重要的是图片的裁剪&#xff0c;图片的裁剪和缩放属性&#xff1a; mode 图片裁剪、缩放的模式 默认值是scaleToFill 我将用两张图片对属性进行演示&#xff0c;一张是pic1.jpg&#xff08;宽更长&#xf…

VisionPro软件Image Stitch拼接算法

2D图像拼接的3种情景 1.一只相机取像位置固定&#xff0c;或者多只相机固定位置拍图&#xff0c;硬拷贝拼图&#xff0c;采用CopyRegion工具实现 2.一只或多只相机在多个位置拍照&#xff0c;相机视野互相重叠&#xff0c;基于Patmax特征定位后&#xff0c;无缝 拼图&#xff…

“多维像素”多模态雷视融合技术构建自动驾驶超级感知能力|上海昱感微电子创始人蒋宏GADS演讲预告

2025年1月14日&#xff0c;第四届全球自动驾驶峰会将在北京中关村国家自主创新示范区展示交易中心-会议中心举行。经过三年的发展&#xff0c;全球自动驾驶峰会已经成长为国内自动驾驶领域最具影响力、规模最大的产业峰会之一。在主会场下午的城市NOA专题论坛上&#xff0c;上海…

C语言初阶习题【25】strcpy的模拟实现

1. 首先先调用下库函数&#xff0c;看它实现了什么 2. 我们自己实现一个strcpy函数 3. 改进1 把*destnation和source 写上去&#xff0c;使用后置 4. 改进2 这里直接把赋值操作放到了while的判断条件里面&#xff0c;然后while循环语句什么都不做&#xff0c;放了一个空语句…

使用 SQL 和表格数据进行问答和 RAG(6)—将指定目录下的 CSV 或 Excel 文件导入 SQLite 数据库

将指定目录下的 CSV 或 Excel 文件导入 SQLite 数据库。以下是详细代码逻辑&#xff1a; 1. 类结构 该类包含三个主要方法&#xff1a; _prepare_db&#xff1a;负责将文件夹中的 CSV 和 XLSX 文件转换为 SQL 表。_validate_db&#xff1a;用于验证 SQL 数据库中创建的表是否…

设计模式 行为型 策略模式(Strategy Pattern)与 常见技术框架应用 解析

策略模式&#xff08;Strategy Pattern&#xff09;核心思想是将算法的实现从使用该算法的类中分离出来&#xff0c;作为独立的对象&#xff0c;通过接口来定义算法家族&#xff0c;这样就可以很容易地改变或扩展算法。通过这种方式&#xff0c;可以避免在客户端代码中使用大量…

如何操作github,gitee,gitcode三个git平台建立镜像仓库机制,这样便于维护项目只需要维护一个平台仓库地址的即可-优雅草央千澈

如何操作github&#xff0c;gitee&#xff0c;gitcode三个git平台建立镜像仓库机制&#xff0c;这样便于维护项目只需要维护一个平台仓库地址的即可-优雅草央千澈 问题背景 由于我司最早期19年使用的是gitee&#xff0c;因此大部分仓库都在gitee有几百个库的代码&#xff0c;…

B+树的原理及实现

文章目录 B树的原理及实现一、引言二、B树的特性1、结构特点2、节点类型3、阶数 三、B树的Java实现1、节点实现2、B树操作2.1、搜索2.2、插入2.3、删除2.4、遍历 3、B树的Java实现示例 四、总结 B树的原理及实现 一、引言 B树是一种基于B树的树形数据结构&#xff0c;它在数据…

大纲笔记幕布的替换

文章目录 前言类似的大纲软件探索 DynalistLogseq通过国内代码仓库建立 Git 仓库Logseq 的使用PC 端安卓端Git 操作Termux git 步骤Termux 的桌面组件&#xff1a;Termux widget 报错参考 前言 之前我一直用幕布&#xff0c;买了三年&#xff0c;奈何要过期了&#xff0c;又三…

MoEs and Transformers 笔记

ref:https://huggingface.co/blog/zh/moe#%E7%94%A8router-z-loss%E7%A8%B3%E5%AE%9A%E6%A8%A1%E5%9E%8B%E8%AE%AD%E7%BB%83 MoEs and Transformers Transformer 类模型明确表明&#xff0c;增加参数数量可以提高性能&#xff0c;因此谷歌使用 GShard 尝试将 Transformer 模型…