【单链表增删查改接口的实现】

简介: 【单链表增删查改接口的实现】

1 尾插和头插

首先我们自己先定义一个结构体类型:

typedef struct SListNode
{
  SLTDateType data;
  struct SListNode* next;
}SListNode;

尾插的具体实现:

SListNode* SLCreatNode(x)
{
  SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
  if (tmp != NULL)
  {
    tmp->data = x;
    tmp->next = NULL;
    return tmp;
  }
}
void SLPushBack(SListNode** pNode, SLTDateType x)
{
  SListNode* newnode=SLCreatNode(x);
  if (*pNode == NULL)
  {
    *pNode = newnode;
  }
  else
  {
    SListNode* tail = *pNode;
    while (tail->next)
    {
      tail = tail->next;
    }
    tail->next = newnode;
  }
}

因为头插和尾插都需要malloc出空间,所以为了代码的简练我们就分装了一个函数SLCreatNode来完成空间的动态开辟。尾插中值得注意的小细节是当*pNode为NULL的时候要单独处理,不然执行tail->next就会出错。

头插的具体实现:

void SLPushFront(SListNode** pNode, SLTDateType x)
{
  SListNode* newnode = SLCreatNode(x);
  newnode->next = *pNode;
  *pNode = newnode;
}

头插比较简单,这里就不多讲了。为了能够看见具体效果,我们可以实现一个打印接口。

具体代码:

void SListPrint(SListNode* pNode)
{
  SListNode* cur = pNode;
  while (cur)
  {
    printf("%d->", cur->data);
    cur = cur->next;
  }
  printf("NULL");
}

现在我们可以来看结果了:

2e004e6ab38c441aaa5564715c42a7ff.png

通过效果图可以看出尾插和头插都是正确的。

2 尾删和头删

尾删的具体代码:

void SLPopBack(SListNode** pNode)
{
  //methon1:
  /*if (*pNode == NULL)
  {
    return;
  }*/
  //method2:
  assert(*pNode != NULL);//(结点为空)
  if (((*pNode)->next) == NULL) //(一个结点)
  {
    free(*pNode);
    *pNode = NULL;
  }
  //method1:
  /*else
  {
    SListNode* prev = NULL;
    SListNode* tail = *pNode;
    while (tail->next)
    {
      prev = tail;
      tail = tail->next;
    }
    free(tail);
    tail = NULL;
    prev->next = NULL;
  }*/
  //method2:
  else//(多个结点)
  {
    SListNode* tail = *pNode;
    while (tail->next->next)
    {
      tail = tail->next;
    }
    free(tail->next);
    tail->next = NULL;
  }
}

尾删的细节处理比较多,细节有时候没有处理好就会导致程序挂掉。我们首先来看当1 结点为空的情况:处理这种情况一般有两种方法

温柔版:if (*pNode == NULL)

                   {

                        return;

                  }

暴力版:assert(*pNode != NULL);

具体哪种方法可以凭借自己喜好选择。

2 结点为多个:

第一种方法是不创建额外的临时变量:判断tail->next->next是否为空,为空就停止,然后free掉tail->next,再将tail->next置为NULL,第二种方法是创建临时变量prev,具体代码可以参照上面。其实无论是哪一种方法,都是要记住要free掉的空间的上一个地址,通过这个地址将其改为NULL。

3 结点为一个:

当结点为一个的时候无论采取上面的哪一种方法都会造成对NULL的访问,这样肯定是行不通的,所以结点为一个的时候就要单独处理。

头删的具体代码:

void SLPopFront(SListNode** pNode)
{
  if (*pNode==NULL)
  {
    return;
  }
  //一个结点与多个结点的情况可以同时处理
  else
  {
    SListNode* head = (*pNode)->next;
    free(*pNode);
    *pNode = head;
  }
}

整体思路与尾删差不多,有些不同的是当结点为一个或者多个的时候可以同时处理。

来看一下结果:

f43d360bd2a54a389a36f559070e048d.png

如果再尾删一个的话:


55595d6229984f379cc2231efa52853f.png

由于尾删我们采取的是暴力的方式,编译器会直接给我们报出错误在哪一行。


3 查找(查增和查删)

首先来看一看查找的具体代码:

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

为什么查找的返回值要用SListNode*呢?

这样的好处是为了方便与查增与查删一起使用,并且还很方便修改值:

例如:

8984ea4fe9d64060a90c28b6b7f8aa21.png

这样我们就将3改为了我们想要的300,那如果我们想要在3的前面或者后面插入一个数呢?

如果是想在3的前面插入一个数,这个时候我们就必须一个一个从头遍历,找到要插入的pos位的上一位地址,还要分类讨论第一个位置是否为要查找的位置,代码量也比较大,这也是单链表的缺陷。

在pos位前插入代码具体实现:

void SListInsert(SListNode** pNode, SListNode* pos, SLTDateType x)
{
  SListNode* newnode = SLCreatNode(x);
  if (*pNode == pos)
  {
    newnode->next = *pNode;
    *pNode = newnode;
  }
  else
  {
    SListNode* posProve = *pNode;
    while (posProve->next != pos)
    {
      posProve = posProve->next;
    }
    posProve->next = newnode;
    newnode->next = pos;
  }
}

其中需要注意的是当第一个元素就是查找元素的时候,这个就要分开判断,这种情况就相当于头插。

来看看这种查找后插入的效果:


1af3e4d1c3b24ba08c769eb2188fbf61.png

那如果是直接后插呢?

直接上代码:

void SLInsertAfter(SListNode* pos, SLTDateType x)
{
  if (pos != NULL)
  {
    SListNode* newnode = SLCreatNode(x);
    newnode->next = pos->next;
    pos->next = newnode;
  }
}

结果展示:

2e2526b4a2b24178992085e976cfb5b3.png

这样在后面插入的话代码量会少很多。

删除pos位的具体代码:

void SListErase(SListNode** pNode, SListNode* pos)
{
  assert(pNode);
  if (*pNode == pos)//头删
  {
    *pNode = pos->next;
    free(pos);
  }
  else
  {
    SListNode* posPrev = *pNode;
    while (posPrev->next != pos)
    {
      posPrev = posPrev->next;
    }
    posPrev->next = pos->next;
    free(pos);
    pos = NULL;
  }
}

要想删除pos位,就必须知道前一位的地址,所以只能从头开始遍历,而当第一位就是pos位的时候,就相当于头删,所以就要分开讨论,具体代码可以参考上面。

结果展示:

c32ab5c0490846a19efa9fb4c057ed26.png

其实单链表进行删除pos位是比较麻烦的,要从头开始遍历,但是如果只是想删除pos位后面的一位就很容易,而且可以不用传入plist的地址。

具体代码实现:

void SLiEraseAfter(SListNode* pos)
{
  assert(pos->next);
  SListNode* posNext = pos->next;
  pos->next = posNext->next;
  free(posNext);
  posNext = NULL;
}

效果图:60758677103f45958d0e34fa6133e5a6.png


4 释放

既然空间是动态开辟的,那么当不用该空间时就要还给操作系统,那么直接free掉*pNode不就好了吗?如果直接free掉*pNode会导致后面空间的地址丢失,那不就造成内存泄漏了吗,所以这样肯定是行不通的,正确的做法应该是用一个临时指针来保存当前地址。

具体代码:

void SListDestroy(SListNode** pNode)
{
  SListNode* cur = *pNode;
  while (cur)
  {
    SListNode* Next = cur->next;
    free(cur);
    cur = NULL;
    cur = Next;
  }
  *pNode = NULL;
}

好了,今天的分享就到这里了,如果该文对你有帮助的话能不能3连支持一下博主呢?

目录
相关文章
|
9月前
|
存储
单链表————单链表的构建,增删查改功能的实现
单链表————单链表的构建,增删查改功能的实现
不带头非循环的单向链表的增删查改的实现(代码版)
不带头非循环的单向链表的增删查改的实现(代码版)
|
5月前
|
存储 算法 搜索推荐
【数据结构】单链表的增删查改(C实现)
【数据结构】单链表的增删查改(C实现)
47 0
|
9月前
|
存储
【双链表增删查改接口的实现】
【双链表增删查改接口的实现】
29 0
|
11月前
|
存储
单链表(增删查改)
单链表(增删查改)
|
11月前
|
存储
顺序表(增删查改)
顺序表(增删查改)
|
12月前
|
存储 前端开发 Java
【数据结构】—— 单链表的增删改查
【数据结构】—— 单链表的增删改查
42 0
|
算法
详细解析单链表带头节点的结构体定义,普通单链表与有序单链表的创建等操作(含创建步骤与码源)
详细解析单链表带头节点的结构体定义,普通单链表与有序单链表的创建等操作(含创建步骤与码源)
|
存储 C语言
什么是链表,如何实现?(单链表篇)
什么是链表,如何实现?(单链表篇)
53 0
什么是链表,如何实现?(单链表篇)
|
存储 算法 Linux
双向链表增删查改详解及完整代码
双向链表增删查改详解及完整代码
55 0
双向链表增删查改详解及完整代码