【初阶数据结构】逆流的回环链桥:双链表

【初阶数据结构】逆流的回环链桥:双链表

文章目录

本篇是链表专题的双链表,是一种链表数据结构,它的每个节点除了包含数据域(用于存储数据)之外,还包含两个指针域,一个指向前一个节点(prev),另一个指向后一个节点(next)

1.双链表接口实现

这次我们实现的是带头双向循环的链表,不仅有指向前一个节点的prev指针,还有指向下一个节点的next指针,最后一个节点有指向开头的指针next,开头的节点有指向结尾的指针prev,形成循环

在这里插入图片描述

双链表定义:

typedefint LTDataType;typedefstructListNode{structListNode* next;structListNode* prev; LTDataType data;}LTNode;

有人问为什么每次都要重新定义数据类型,因为每次的节点数据类型不一定是一样的,定义一下方便修改所有地方的数据类型

1.1 双链表节点创建

LTNode*BuyListNode(LTDataType x){ LTNode* newnode =(LTNode*)malloc(sizeof(LTNode));if(newnode ==NULL){perror("malloc fail");returnNULL;} newnode->data = x; newnode->prev =NULL; newnode->next =NULL;return newnode;}

还是和之前的链表节点创建一样,这里不过多叙述

1.2 双链表初始化

LTNode*LTInit(){ LTNode* phead =BuyListNode(-1); phead->next = phead; phead->prev = phead;return phead;}

通常我们会在主函数创建一个指针接收一个初始化指针,prev和next都指向自己形成循环,也就是一个哨兵位,也间接规避了链表为空的情况,直接可以PushBack节点

1.3 双链表销毁

voidLTDestroy(LTNode* phead){assert(phead); LTNode* cur = phead->next;while(cur != phead){ LTNode* next = cur->next;free(cur); cur = next;}free(phead); phead =NULL;}

head是哨兵位需要开始时被访问,所以从phead->next开始,先保存下一个节点的指针,然后释放当前移动指针指向的节点,再把保存过的节点赋值给移动指针实现移动遍历节点,最后再释放哨兵位head

1.4 双链表打印

voidLTPrint(LTNode* phead){assert(phead);printf("<= head =>"); LTNode* cur = phead->next;while(cur != phead){printf(" %d =>", cur->data); cur = cur->next;}printf("\n");}

要根据双链表的特性来,双链表有哨兵位,所以打印要从哨兵位的下一个节点开始打印,直到遇到的节点的next指针指向哨兵位

1.5 双链表检查是否为空

intLTEmpty(LTNode* phead){assert(phead);return phead->next == phead ?0:1;}

为了方便在增删查改中避免链表为空的情况,and多处需使用到,所以特别写一个判空函数,因为是循环链表,所以指向自己表明链表只有一个哨兵位,则返回0,反则返回1

1.6 双链表尾插

voidLTPushBack(LTNode* phead, LTDataType x){assert(phead); LTNode* newnode =BuyListNode(x); phead->prev->next = newnode; newnode->prev = phead->prev; newnode->next = phead; phead->prev = newnode;}

由于是带头循环,所以每个节点都有4条逻辑线连接着,通常先修改两个节点间的链接,再修改循环的链接

1.7 双链表头插

voidLTPushFront(LTNode* phead, LTDataType x){assert(phead); LTNode* newnode =BuyListNode(x); newnode->next = phead->next; phead->next->prev = newnode; phead->next = newnode; newnode->prev = phead;}

无论是什么情况下,都要注意连接的顺序是否会影响到其他步骤的正常赋值,所以头插就需要先链接newnode的next指针,即通常先链接后面的节点

1.8 双链表尾删

voidLTPopBack(LTNode* phead){assert(phead);assert(LTEmpty(phead)); LTNode* tail = phead->prev; LTNode* tailPrev = tail->prev; tailPrev->next = phead; phead->prev = tailPrev;free(tail); tail =NULL;}

首先遇到删除就要注意是不是空链表,存不存在节点来删除,然后要分别存储最后一个节点和倒数第二个节点,方便进行链接释放的操作

1.9 双链表头删

voidLTPopFront(LTNode* phead){assert(phead);assert(LTEmpty(phead)); LTNode* first = phead->next; phead->next = first->next; first->next->prev = phead;free(first); first =NULL;}

相同的操作,无论是删除还是销毁,都要记得存储需要释放的节点相关指针,因为需要在删除之后将其连接的的节点连接起来

1.10 双链表查找

LTNode*LTFind(LTNode* phead, LTDataType x){assert(phead); LTNode* cur = phead->next;while(cur != phead){if(cur->data == x){return cur;} cur = cur->next;}returnNULL;}

我们在查找时肯定是查找有效的数据,所以我们查找是从哨兵位后一个节点开始的,查找方法也很简单,遍历整个双链表,看看能否找到相应的数据,找得到就返回对应节点,找不到就返回空

1.11 双链表在pos位置插入x

voidLTInsert(LTNode* pos, LTDataType x){assert(pos); LTNode* newnode =BuyListNode(x); pos->prev->next = newnode; newnode->prev = pos->prev; pos->prev = newnode; newnode->next = pos;}

因为双向链表有prev,就不用在意是pos前还是pos后插入,这里用的是pos前插入

1.12 双链表在pos位置删除x

voidLTErase(LTNode* phead, LTNode* pos){assert(LTEmpty(phead)); pos->prev->next = pos->next; pos->next->prev = pos->prev;free(pos);}

其实和头删尾删的原理是一样的,直接删除,然后将剩余两个断开的节点链接就行了

2.顺序表和链表对比

在这里插入图片描述

顺序表和链表各有各的好处

🚩顺序表: 适用于对随机访问性能要求高,元素数量可预估,且不需要频繁进行插入和删除操作的场景

🚩链表: 适用于需要频繁插入和删除元素,元素数量动态变化,难以预估元素数量的场景

3.代码展示

传送门:Gitee双链表代码

3.1 List.h

#pragmaonce#include<stdio.h>#include<stdlib.h>#include<assert.h>typedefint LTDataType;typedefstructListNode{structListNode* next;structListNode* prev; LTDataType data;}LTNode; LTNode*LTInit();voidLTDestroy(LTNode** pphead);voidLTPrint(LTNode* phead); LTNode*BuyListNode(LTDataType x);intLTEmpty(LTNode* phead);voidLTPushBack(LTNode* phead, LTDataType x);voidLTPopBack(LTNode* phead);voidLTPushFront(LTNode* phead, LTDataType x);voidLTPopFront(LTNode* phead); LTNode*LTFind(LTNode* phead, LTDataType x);voidLTInsert(LTNode* pos, LTDataType x);voidLTErase(LTNode* pos);

3.2 List.c

#define_CRT_SECURE_NO_WARNINGS#include"List.h"//初始化 LTNode*LTInit(){ LTNode* phead =BuyListNode(-1); phead->next = phead; phead->prev = phead;return phead;}//销毁voidLTDestroy(LTNode* phead){assert(phead); LTNode* cur = phead->next;while(cur != phead){ LTNode* next = cur->next;free(cur); cur = next;}free(phead); phead =NULL;}//打印voidLTPrint(LTNode* phead){assert(phead);printf("<= head =>"); LTNode* cur = phead->next;while(cur != phead){printf(" %d =>", cur->data); cur = cur->next;}printf("\n");}//创建节点 LTNode*BuyListNode(LTDataType x){ LTNode* newnode =(LTNode*)malloc(sizeof(LTNode));if(newnode ==NULL){perror("malloc fail");returnNULL;} newnode->data = x; newnode->prev =NULL; newnode->next =NULL;return newnode;}//判空intLTEmpty(LTNode* phead){assert(phead);return phead->next == phead ?0:1;}//尾插voidLTPushBack(LTNode* phead, LTDataType x){assert(phead); LTNode* newnode =BuyListNode(x); LTNode* tail = phead->prev; tail->next = newnode; newnode->prev = tail; newnode->next = phead; phead->prev = newnode;}//尾删voidLTPopBack(LTNode* phead){assert(phead);assert(LTEmpty(phead)); LTNode* tail = phead->prev; LTNode* tailPrev = tail->prev; tailPrev->next = phead; phead->prev = tailPrev;free(tail); tail =NULL;}//头插voidLTPushFront(LTNode* phead, LTDataType x){assert(phead); LTNode* newnode =BuyListNode(x); newnode->next = phead->next; phead->next->prev = newnode; phead->next = newnode; newnode->prev = phead;}//头删voidLTPopFront(LTNode* phead){assert(phead);assert(LTEmpty(phead)); LTNode* first = phead->next; phead->next = first->next; first->next->prev = phead;free(first); first =NULL;}//查找 LTNode*LTFind(LTNode* phead, LTDataType x){assert(phead); LTNode* cur = phead->next;while(cur != phead){if(cur->data == x){return cur;} cur = cur->next;}returnNULL;}//在pos处插入xvoidLTInsert(LTNode* pos, LTDataType x){assert(pos); LTNode* newnode =BuyListNode(x); pos->prev->next = newnode; newnode->prev = pos->prev; pos->prev = newnode; newnode->next = pos;}//在pos处删除xvoidLTErase(LTNode* phead, LTNode* pos){assert(LTEmpty(phead)); pos->prev->next = pos->next; pos->next->prev = pos->prev;free(pos);}

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

在这里插入图片描述

Read more

可以在命令行通过大模型使用上下文协议(MCP)与外部工具交互的软件:小巧的MCPHost

可以在命令行通过大模型使用上下文协议(MCP)与外部工具交互的软件:小巧的MCPHost

小巧的MCPHost MCPHost 可以在命令行下使用,使大型语言模型(LLM)能够通过模型上下文协议(MCP)与外部工具进行交互。目前支持Claude 3.5 Sonnet和Ollama等。本次实践使用自己架设的Deepseek v3模型,跑通了Time MCP服务。  官网:GitHub - mark3labs/mcphost: A CLI host application that enables Large Language Models (LLMs) to interact with external tools through the Model Context Protocol (MCP). 下载安装 使用非常方便,直接下载解压即可使用。官网提供Windows、Linux和MacOS三个系统的压缩包: https://github.com/

By Ne0inhk
实战篇:Python开发monogod数据库mcp server看完你就会了

实战篇:Python开发monogod数据库mcp server看完你就会了

原创不易,请关注公众号:【爬虫与大模型开发】,大模型的应用开发之路,整理了大模型在现在的企业级应用的实操及大家需要注意的一些AI开发的知识点!持续输出爬虫与大模型的相关文章。 前言 目前mcp协议是给deepseek大模型插上工具链的翅膀,让大模型不仅拥有超高的推理和文本生成能力,还能具备执行大脑意识的工具能力! 如何开发一个mcp? mcp是一种协议,指的是模型上下文协议 (Model Context Protocol)。 官方结成的mcp https://github.com/modelcontextprotocol/python-sdk mcp库 pip install mcp from mcp.server.fastmcp import FastMCP 我们先来做一个简单的案例 from mcp.server.fastmcp import FastMCP import requests mcp = FastMCP("spider") @mcp.tool() def crawl(

By Ne0inhk
【大模型实战篇】基于Claude MCP协议的智能体落地示例

【大模型实战篇】基于Claude MCP协议的智能体落地示例

1. 背景         之前我们在《MCP(Model Context Protocol) 大模型智能体第一个开源标准协议》一文中,介绍了MCP的概念,虽然了解了其概念、架构、解决的问题,但还缺少具体的示例,来帮助进一步理解整套MCP框架如何落地。         今天我们基于claude的官方例子--获取天气预报【1】,来理解MCP落地的整条链路。 2. MCP示例         该案例是构建一个简单的MCP天气预报服务器,并将其连接到主机,即Claude for Desktop。从基本设置开始,然后逐步发展到更复杂的使用场景。         大模型虽然能力非常强,但其弊端就是内容是过时的,这里的过时不是说内容很旧,只是表达内容具有非实时性。比如没有获取天气预报和严重天气警报的能力。因此我们将使用MCP来解决这一问题。         构建一个服务器,该服务器提供两个工具:获取警报(get-alerts)和获取预报(get-forecast)。然后,将该服务器连接到MCP主机(在本例中为Claude for Desktop)。         首先我们配置下环

By Ne0inhk
AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 作者:高瑞冬 本文目录 * AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 * 一、MCP协议简介 * 二、创建MCP工具集 * 1. 获取MCP服务地址 * 2. 在FastGPT中创建MCP工具集 * 三、测试MCP工具 * 四、AI模型调用MCP工具 * 1. 调用单个工具 * 2. 调用整个工具集 * 五、私有化部署支持 * 1. 环境准备 * 2. 修改docker-compose.yml文件 * 3. 修改FastGPT配置 * 4. 重启服务 * 六、使用MCP-Proxy集成多个MCP服务 * 1. MCP-Proxy简介 * 2. 安装MCP-Proxy * 3. 配置MCP-Proxy * 4. 将MCP-Proxy与FastGPT集成 * 5. 高级配置

By Ne0inhk