首页 > 其他分享 >数据结构之单向不循环链表

数据结构之单向不循环链表

时间:2023-03-02 16:00:39浏览次数:42  
标签:pphead cur 单向 next 链表 SListNode 数据结构 节点

链表是一种物理存储结构上非连续、非顺序的线性存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表中的元素(节点)中记录了与其他元素的连接关系,链表的存储方式相比于顺序表更加灵活。

链表结构多样,分带头/不带头、单向/双向和循环/不循环,相互组合可以有8种结构。本文实现不带头单向不循环链表。


逻辑结构

链表的逻辑结构是线性和连续的,各个节点通过指针进行连接。

数据结构之单向不循环链表_数据结构



物理结构

链表的物理结构是离散的,存储链表的内存空间是非连续的

数据结构之单向不循环链表_数据结构_02



链表的定义

链表的每个节点包含两部分信息:有效数据(Value)和指向下一节点的指针(Next)。其中尾节点的​​Next​​​指针为​​NULL​​。

typedef int SListDataType;

typedef struct SListNode
{
SListDataType data; //有效数据
struct SListNode* next; //Next指针
}SListNode;

void SLPrint(SListNode* phead); //链表打印
void SLPushBack(SListNode** phead, SListDataType x); //尾插数据
void SLPopBack(SListNode** pphead); //尾删数据
void SLPushFront(SListNode** pphead, SListDataType x); //头插数据
void SLPopFront(SListNode** pphead); //头删数据
SListNode* SListFind(SListNode* plist, SListDataType x); //查找数据
void SListInsertAfter(SListNode* pos, SListDataType x); //(后方)插入数据
void SListEraseAfter(SListNode* pos); //(后方)删除数据
void SListDestroy(SListNode* plist); //链表销毁


链表的初始化

通常用指针维护一个链表,这个指针通常指向一个链表的头结点,即phead->list。事实上,我们通常将头结点作为一个链表的代称,例如头结点head链表head实际上是同义的。

建立一个链表通常分为两步,第一步是初始化各个节点,第二是在各个节点之间建立连接关系。完成后,即可从头结点出发,访问其余所有节点。

SListNode* plist = NULL; //用plist维护链表


接口实现和注意事项

创建节点

在下面的接口中往往需要申请一个新节点,此处将创建节点的功能单独封装成一个函数,返回新节点的地址。

SListNode* BuySLNode(const int x)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
perror("BuySLNode::malloc");
return NULL;
}
else
{
newNode->data = x;
newNode->next = NULL;
return newNode;
}
}


链表打印

打印链表便于调试和直观显示数据。从头结点开始向后遍历链表以实现链表数据的打印。

void SLPrint(SListNode* phead)
{
SListNode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}


尾部插入数据

在尾部插入数据时,首先要创建一个新节点,再寻找尾节点并将尾节点的Next修改为新节点的地址以实现链表和新节点的链接。

另外,需要额外考虑空表情况。当链表为空时,需要将新节点的地址赋值给plist,所以在传参时需要传plist的地址以顺利修改链表的头结点。另外,为了检查使用者的传参错误,需要对​​pphead​​进行断言。

void SLPushBack(SListNode** pphead, SListDataType x)
{
assert(pphead);
SListNode* newNode = BuySLNode(x);
if (*pphead == NULL)
{
*pphead = newNode;
}
else
{
SListNode* cur = *pphead;
while (cur->next)
{
cur = cur->next;
}
cur->next = newNode;
}
}

一种常见的错误实现如下:

void SLPushBack(SListNode** pphead, SListDataType x)
{
assert(pphead);
SListNode* newNode = BuySLNode(x);
if (*pphead == NULL) {
*pphead = newNode;
}
else
{
SListNode* cur = *pphead;
while (cur)
{
cur = cur->next;
}
cur = newNode;
}
}

出现上面错误的原因是对链表的物理结构理解不透彻,当循环停止时,​​tail​​​的值为​​NULL​​​,当赋值新节点的指针给​​tail​​​时,尾节点的​​Next​​并未被改变,即这个操作不能将新节点链接起来。

注意深刻理解链表的物理结构:临时指针变量的地址不变,临时指针变量存储的地址在变。


尾部删除数据

空表情况和只有一个节点的情况需要额外考虑。

数据结构之单向不循环链表_数据结构_03

void SLPopBack(SListNode** pphead)
{
assert(pphead);
assert(*pphead);
SListNode* cur = *pphead;
if (cur->next == NULL)
{
free(cur);
*pphead = NULL;
}
else
{
while (cur->next->next)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
}
}


头部插入数据

数据结构之单向不循环链表_单向链表_04

void SLPushFront(SListNode** pphead, SListDataType x)
{
assert(pphead);
SListNode* newNode = BuySLNode(x);
newNode->next = *pphead;
*pphead = newNode;
}


头部删除数据

数据结构之单向不循环链表_单向链表_05

void SLPopFront(SListNode** pphead)
{
assert(pphead);
assert(*pphead);
SListNode* nextNode = (*pphead)->next;
free(*pphead);
*pphead = nextNode;
}


查找数据

SListNode* SListFind(SListNode* plist, SListDataType x)
{
SListNode* cur = plist;
while (cur)
{
if (cur->data == x) {
return cur;
}
cur = cur->next;
}
return NULL;
}


在指定位置后方插入数据

数据结构之单向不循环链表_单向链表_06

void SListEraseAfter(SListNode* pos)
{
assert(pos);
SListNode* DelNode = pos->next;
assert(DelNode);//最后一个节点后面没有节点
SListNode* nextNode = DelNode->next;
free(DelNode);
DelNode = NULL;
pos->next = nextNode;
}


删除指定位置后方的一个数据

数据结构之单向不循环链表_单向链表_07

void SListEraseAfter(SListNode* pos)
{
assert(pos);
SListNode* DelNode = pos->next;
assert(DelNode);//最后一个节点后面没有节点
SListNode* nextNode = DelNode->next;
free(DelNode);
DelNode = NULL;
pos->next = nextNode;
}


链表销毁

void SListDestroy(SListNode* plist)
{
while (plist->next != NULL)
{
SListEraseAfter(plist);
}
free(plist);
}

无头单向不循环链表的缺点

可以注意到,上述实现中间插入和删除数据都是在指定位置的后方进行操作,这是因为由于在单向不循环链表中,无法直接找到一个节点的前驱节点和后继节点,若要实现删除指定位置和在指定位置的前面插入一个节点,需要从头遍历链表找到​​pos​​位置的前驱节点,效率较低。STL库中对单向链表这种接口的实现的原因也是如此。

STL中单向链表中的部分接口:数据结构之单向不循环链表_单向链表_08

链表具有以下缺点:

  1. 链表的随机访问效率较低。当需要寻找一个节点时,要从链表的头结点开始遍历寻找。
  2. 链表占用的内存较多。因为每个链表节点不仅要存储有效数据,还要额外存储指针。不过这些额外的内存在如今已经不值一提,所以这个缺点几乎可以忽略不计

标签:pphead,cur,单向,next,链表,SListNode,数据结构,节点
From: https://blog.51cto.com/158SHI/6096003

相关文章

  • c语言实现有头单向链表
    #include<stdio.h>#include<stdlib.h>#include<string.h>//采用有头链表,头节点不存数据,所以数据操作都从头节点所指的下一节点开始,这样就不会误操作到头节点。typed......
  • 【基本数据结构】栈
    一、后进先出(LIFO)栈是一种操作受限的线性表,只允许在一端插入和删除数据,这一端叫做栈顶,对应的另一端叫做栈底。向栈中添加元素也叫进栈、入栈、压栈,从栈中删除元素也叫出......
  • 代码随想录训练营day 4|链表基础理论,移除链表元素,设计链表,反转链表
    链表理论基础链表是一种由指针串联在一起的线性结构,每一个节点都由一个数据域和一个指针域组成。链表的类型有:单链表、双链表、循环链表。链表的存储方式:在内存中不连续......
  • 数据结构与算法-其它
    二叉树、B树、B+树、红黑树B树B树与二叉树的区别1、B树与二叉树的区别二叉树最多能有2个子节点,B树最多有M个子节点,最少有3个子节点。2、B树和B+树的比较:1)B树的关键字......
  • pat乙级:模拟链表问题(汇总,包含所有pat中链表题目分析)
    更新:优化文章结构,增加了部分内容如(1110区块反转)和自己代码和他人代码分析。看完你就懂了转载请注明出处和链接地址:(https://www.cnblogs.com/ahappyfool/p/17156470.htm......
  • (转)数据结构和算法(Golang实现)(8.2)基础知识-分治法和递归
    原文:https://juejin.cn/post/6844904132378263565分治法和递归在计算机科学中,分治法是一种很重要的算法。字面上的解释是分而治之,就是把一个复杂的问题分成两个或更多......
  • tree数据结构处理
    importReactfrom'react';interfaceRootObject{createUserId?:any;createUserName?:any;createTime?:any;updateUserId?:any;updateUserName?:a......
  • 四种语言刷算法之删除链表的倒数第 N 个结点
    力扣19.删除链表的倒数第N个结点1、C/** *Definitionforsingly-linkedlist. *structListNode{ *  intval; *  structListNode*next;......
  • Redis 五种数据结构以及三种高级数据结构解析
    Redis五种数据结构以及三种高级数据结构解析硬核资源!Redis五种数据结构以及三种高级数据结构解析(详解)(baidu.com) Redis五种数据结构以及三种高级数据结构解析(38......
  • 【基本数据结构】链表
    链表通过指针将一组零散的内存串联在一起,也是一种非常基础、非常常用的数据结构。 一、常见的3种链表从内存的角度来看,数组需要一块连续的内存空间,对内存的要求比较高......