当前位置: 首页 > news >正文

网站上线盈利广州seo排名优化

网站上线盈利,广州seo排名优化,关键词排名代做,安徽省住房和城乡建设厅网站首页引言 在 数据结构——lesson3.单链表中我们学习了数据结构中的单链表,它解决了顺序表中插入删除需要挪动大量数据的缺点。但同时也有需要改进的地方。 比如说:当我们需要寻找某个节点的前一个节点,对于单链表而言只能遍历,这样就…

引言

在 数据结构——lesson3.单链表中我们学习了数据结构中的单链表,它解决了顺序表中插入删除需要挪动大量数据的缺点。但同时也有需要改进的地方。

比如说:当我们需要寻找某个节点的前一个节点,对于单链表而言只能遍历,这样就可能造成大量时间的浪费。为了解决这个问题,我们需要学习新的内容——带头双向循环链表
 

注意:

带头链表里的头节点,实际为“哨兵点”哨兵节点(Sentinel Node)是一个附加的节点,它不存储实际数据,通常作为链表的 "伪头" 或 "伪尾" 存在。

哨兵位(哨兵节点)存在的核心意义简化边界处理,统一操作逻辑,具体体现在:

  1. 消除空链表 / 边界节点的特殊判断
    无论链表是否为空,哨兵节点始终存在(如作为伪头),避免了对 “头节点是否为空”“链表是否只有一个节点” 等场景的单独处理。

  2. 统一插入 / 删除操作的逻辑
    例如删除节点时,无需区分 “删除的是头节点还是中间节点”;插入时,无需判断 “链表是否为空”,所有操作都能以相同的代码逻辑实现。

  3. 减少条件分支,降低出错概率
    省去大量边界条件的判断(如if (head == null)),使代码更简洁,逻辑更清晰,减少因漏判边界导致的 bug。

简言之,哨兵位通过 “增加一个无意义的节点”,换取了链表操作的统一性和简洁性。

双向链表的定义

双向链表是一种链式存储结构,其每个节点包含三个部分:

  • 数据域:存储节点的实际数据
  • 前驱指针(prev):指向当前节点的前一个节点
  • 后继指针(next):指向当前节点的后一个节点

如下图:

双向链表的节点定义:

struct ListNode {int data;           // 数据域struct Node* prev;  // 前驱指针struct Node* next;  // 后继指针
};

双向链表的功能

我们今天学习的双链表要实现一下几个功能:

初始化双向链表中的数据。
打印双向链表中的数据。
对双向链表进行尾插(末尾插入数据)。
对双向链表进行头插(开头插入数据)。
对双向链表进行尾删(末尾删除数据)。
对双向链表进行头删(开头删除数据)。
对双向链表数据进行查找。
对双向链表数据进行修改。
对指定位置的数据删除
对指定位置的数据插入。
销毁双向链表。

双向链表的功能实现

1.初始化双向链表中的数据

在初始化双向链表时,我们需要创建一个头节点,也就是我们常说的哨兵位头节点

(1)创建头节点

//申请节点
LTNode* LTCreateNode(LTDataType x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));// 如果node为NULL,说明内存分配失败if (node == NULL){perror("malloc fail");exit(1);}node->data = x;// 将前后节点都指向自己node->next = node->prev = node;return node;
}
(2)初始化

在初始化的时候我们已经将前后指针指向自己本身了,我们在这里把哨兵位头节点设置为-1

LTNode* LTInit()
{LTNode* phead = LTCreateNode(-1);return phead;
}
2.打印双向链表中的数据
核心原理

双向链表的每个节点包含 数据域(存数据)、前驱指针(指向前一个节点)和 后继指针(指向后一个节点),打印的核心是 “按顺序遍历节点并输出数据”,关键逻辑如下:

  1. 确定遍历起点:通常从 “头节点的后继节点” 开始(头节点是哨兵节点,不存有效数据,仅用于简化链表操作);
  2. 控制遍历终止:遍历到 “回到头节点” 时停止(避免循环遍历);
  3. 遍历与输出:从起点开始,依次通过节点的 next 指针访问下一个节点,同时打印当前节点的有效数据,直到触发终止条件。
代码实现
//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;// 使用while循环遍历链表,直到回到哨兵节点while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}
3.双向链表尾插

我们这里还要了解双向链表的插入过程

代码实现
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTCreateNode(x);// 新节点的prev指针指向当前链表的最后一个节点//(即phead的prev指向的节点)newnode->prev = phead->prev;// 新节点的next指针指向头节点phead,这样就形成了双向循环newnode->next = phead;// 更新当前链表最后一个节点的next指针,使其指向新节点phead->prev->next = newnode;// 更新头节点phead的prev指针,使其指向新节点phead->prev = newnode;
}
4.双向链表头插

这里头插和尾插方法是一样的,不过要区分插入的位置是在节点前还是节点后!!!

代码实现
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTCreateNode(x);// 新节点的next指针指向当前头节点的下一个节点  newnode->next = phead->next;// 新节点的prev指针指向头节点phead newnode->prev = phead;// 更新原来头节点的下一个节点的prev指针phead->next->prev = newnode;// 更新头节点phead的next指针phead->next = newnode;
}
5.双向链表尾删

同样,我们要了解双向链表的删除过程

要注意,千万不要把头节点给删了。

//尾删
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.双向链表头删
代码实现
//头删
void LTPopFront(LTNode* phead)
{// 断言检查phead是否为空// 并且链表是否只有一个哨兵节点(即链表为空)assert(phead && phead->next != phead);// 指向要删除的节点,即当前头节点的下一个节点LTNode* del = phead->next;// 更新头节点的next指针,使其指向要删除节点的下一个节点phead->next = del->next;// 更新被删除节点的下一个节点的prev指针del->next->prev = phead;free(del);del = NULL;
}
7.双向链表数据查找

遍历整个链表,如果找到则返回当前节点的指针,如果遍历完整个链表都没找到则返回NULL。

//查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{LTNode* pcur = phead->next;// 遍历链表,直到回到哨兵节点(表示已经遍历了整个链表)while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}
8.双向链表数据修改
代码实现
//修改数据
void LTModify(LTNode* phead, LTNode* pos, LTDataType x)
{assert(phead);assert(pos != phead);LTNode* pcur = phead->next;// 遍历链表,直到回到头节点while (pcur != phead){if (pcur == pos){pcur->data = x;}pcur = pcur->next;}
}
9.指定位置数据删除

代码实现

//删除pos节点
void LTErase(LTNode* pos)
{assert(pos);// 将pos的下一个节点的prev指针指向pos的前一个节点pos->next->prev = pos->prev;// 将pos的前一个节点的next指针指向pos的下一个节点pos->prev->next = pos->next;free(pos);pos = NULL;
}
10.指定位置数据插入

指定位置数据插入分为指定位置前后插入数据。

(1)在指定位置前插入数据
//在pos之前插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTCreateNode(x);// 设置新节点的next指针指向pos,这样新节点就“指向”了posnewnode->next = pos;// 设置新节点的prev指针指向pos的前一个节点newnode->prev = pos->prev;// 更新pos前一个节点的next指针,使其指向新节点pos->prev->next = newnode;// 更新pos的prev指针,使其指向新节点pos->prev = newnode;
}
(2)在指定位置后插入数据
//在pos之后插入数据
void LTInsertAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTCreateNode(x);// 设置新节点的next指针指向pos的下一个节点newnode->next = pos->next;// 设置新节点的prev指针指向posnewnode->prev = pos;// 更新pos的下一个节点的prev指针pos->next->prev = newnode;// 更新pos的next指针pos->next = newnode;
}
11.销毁双向链表
代码实现
//销毁数据
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;// 遍历整个链表while (pcur != phead){if (pcur == NULL){exit(1);}LTNode* next = pcur->next;free(pcur);pcur = NULL;}free(phead);phead = NULL;
}

完整代码

List.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
//定义双链表的结构
typedef struct ListNode {LTDataType data;struct ListNode* next;struct ListNode* prev;
}LTNode;//初始化
//void LTInit(LTNode** pphead);
LTNode* LTInit();
//打印
void LTPrint(LTNode* phead);//插入数据之前,链表必须初始化到只有一个头节点的情况
//不改变哨兵位的地址,因此只需要传一级指针//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//在pos之前插入数据
void LTInsert(LTNode* pos, LTDataType x);
//在pos之后插入数据
void LTInsertAfter(LTNode* pos, LTDataType x);
//删除pos节点
void LTErase(LTNode* pos);
//查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);
//销毁数据
void LTDestroy(LTNode* phead);
//修改数据
void LTModify(LTNode* phead, LTNode* pos, LTDataType x);

List.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"//申请节点
LTNode* LTCreateNode(LTDataType x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));// 如果node为NULL,说明内存分配失败if (node == NULL){perror("malloc fail");exit(1);}node->data = x;// 将前后节点都指向自己node->next = node->prev = node;return node;
}//初始化
//void LTInit(LTNode** pphead)
//{
//	//给双向链表创建一个哨兵位
//	*pphead = LTBuyNode(-1);
//}
LTNode* LTInit()
{LTNode* phead = LTCreateNode(-1);return phead;
}//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;// 使用while循环遍历链表,直到回到哨兵节点while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTCreateNode(x);// 新节点的prev指针指向当前链表的最后一个节点//(即phead的prev指向的节点)newnode->prev = phead->prev;// 新节点的next指针指向头节点phead,这样就形成了双向循环newnode->next = phead;// 更新当前链表最后一个节点的next指针,使其指向新节点phead->prev->next = newnode;// 更新头节点phead的prev指针,使其指向新节点phead->prev = newnode;
}//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTCreateNode(x);// 新节点的next指针指向当前头节点的下一个节点  newnode->next = phead->next;// 新节点的prev指针指向头节点phead newnode->prev = phead;// 更新原来头节点的下一个节点的prev指针phead->next->prev = newnode;// 更新头节点phead的next指针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)
{// 断言检查phead是否为空// 并且链表是否只有一个哨兵节点(即链表为空)assert(phead && phead->next != phead);// 指向要删除的节点,即当前头节点的下一个节点LTNode* del = phead->next;// 更新头节点的next指针,使其指向要删除节点的下一个节点phead->next = del->next;// 更新被删除节点的下一个节点的prev指针del->next->prev = phead;free(del);del = NULL;
}//查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{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 = LTCreateNode(x);// 设置新节点的next指针指向pos,这样新节点就“指向”了posnewnode->next = pos;// 设置新节点的prev指针指向pos的前一个节点newnode->prev = pos->prev;// 更新pos前一个节点的next指针,使其指向新节点pos->prev->next = newnode;// 更新pos的prev指针,使其指向新节点pos->prev = newnode;
}//在pos之后插入数据
void LTInsertAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTCreateNode(x);// 设置新节点的next指针指向pos的下一个节点newnode->next = pos->next;// 设置新节点的prev指针指向posnewnode->prev = pos;// 更新pos的下一个节点的prev指针pos->next->prev = newnode;// 更新pos的next指针pos->next = newnode;
}//删除pos节点
void LTErase(LTNode* pos)
{assert(pos);// 将pos的下一个节点的prev指针指向pos的前一个节点pos->next->prev = pos->prev;// 将pos的前一个节点的next指针指向pos的下一个节点pos->prev->next = pos->next;free(pos);pos = NULL;
}//销毁数据
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;// 遍历整个链表while (pcur != phead){if (pcur == NULL){exit(1);}LTNode* next = pcur->next;free(pcur);pcur = NULL;}free(phead);phead = NULL;
}//修改数据
void LTModify(LTNode* phead, LTNode* pos, LTDataType x)
{assert(phead);assert(pos != phead);LTNode* pcur = phead->next;// 遍历链表,直到回到头节点while (pcur != phead){if (pcur == pos){pcur->data = x;}pcur = pcur->next;}
}

总结:顺序表和链表的区别

1. 存储方式

  • 顺序表
    采用连续的内存空间存储数据,元素在内存中紧密排列(如数组)。
    特点:通过下标(索引)可直接定位元素位置,内存地址连续。

  • 链表
    采用离散的内存空间存储数据,每个元素(节点)包含数据域和指针域(指向下一个 / 上一个节点地址)。
    特点:元素在内存中不连续,依赖指针关联前后元素。

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续但物理上不一定连续
随机访问支持O(1)不支持:O(N)
任意位置插入或者删除元 素可能需要搬移元素,效率低O(N)只需修改指针指向
插入动态顺序表空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率

其他关键区别:

  • 内存利用率
    顺序表可能存在内存浪费(预分配空间未用完),或因空间不足需整体扩容;
    链表内存利用率更高,按需分配节点,但指针域会额外消耗少量内存。

  • 缓存友好性
    顺序表的连续内存布局更符合 CPU 缓存机制,访问速度更快;

 适用场景

  • 顺序表
    适合频繁访问元素(如随机读写场景)、元素数量固定或变化不大的情况(如数据库索引、数组)。

  • 链表
    适合频繁插入 / 删除元素(如链表式队列、栈)、元素数量动态变化较大的场景(如链表式哈希表)。

简言之,顺序表是 “以空间换时间”(连续存储提升访问速度),链表是 “以时间换空间”(灵活存储优化插入删除效率)。

结束语

这一节内容我们学习到了带头循环双向链表的结构和功能以及它的实现方式。

感谢您的三连支持!!!

http://www.dtcms.com/a/580124.html

相关文章:

  • 做网站 难票务系统网站模板
  • 莱西网站制作联赛与超针对网站开发者的问答网站
  • 12网站免费建站自己做网站麻烦吗
  • 温岭网站设计商务平台
  • 重庆网站建设外包wordpress开发文档(chm)
  • 南海建设工程交易网站网站备案问题
  • 沈阳市建设工程信息网招标公告搜索引擎优化seo专员招聘
  • 邯郸去哪做网站改版产品推广方案要包含哪些内容
  • 南充 网站建设学校网站建设总结
  • 怎么找网站的根目录百度文档怎么免费下vvv
  • 如何让百度新闻收录网站文章wordpress空页面模板
  • 工信部 网站备案 上传电子网站建设哪家好采用苏州久远网络
  • 保定定兴网站建设做公司网站需要会什么
  • 南京市秦淮区建设局网站wordpress本文地址
  • 大厂建设局网站网络产品及其推广方法
  • wordpress 段子主题简单的网站更新 关键词优化 关键词互联
  • 石家庄网站设计公司排名网站建设公司 校园网站
  • 做网站虚拟主机要多大微信超市小程序
  • 济南网站设计制作要多久产品备案号查询官网
  • 网站案例英文网页设计与网站建设习题答案
  • 网站建设和网页设计视频教程品牌建设的本质英语作文
  • 快速建站服务济南做网站公司xywlcn
  • 同域名网站改版需要把之前网站里的文章都拷贝过来吗?做平行进口的汽车网站
  • 天猫网站建设可行性分析筑楼人官方网
  • 公司网站的重要性a站免费最好看的电影片推荐
  • 在百度做网站需要什么资料网站建设表格的属性
  • 微信网站页面设计建设网站商城后台系统
  • 做环评需要关注哪些网站陕西住房城乡建设厅网站
  • 简单网站制作教程怎么做网站数据分析
  • 石家庄哪里有做网站的南昌企业自助建站