【数据结构】单链表的增删查改(C语言实现)(2)

简介: 【数据结构】单链表的增删查改(C语言实现)(2)

8、在头部删除数据

特别注意: 和插入数据一样,因为我们删除的可能是链表中的最后一个数据,即可能会改变 plist 的指向 (让 plist 重新指向 NULL),所以不管我们在什么地方删除数据,都需要传递二级指针。


其次,由于我们这里是删除数据,所以函数调用者需要保证调用此函数时链表中至少是含有一个数据的;所以我们对 *pphead (等价于 plist) 进行断言,当调用者错误使用此函数时,我们直接报错并介绍程序。

//在头部删除数据
void SListPopFront(SLTNode** pphead)
{
  assert(pphead);
  assert(*pphead);  //当链表为空时,删除元素报错
  SLTNode* tmp = (*pphead)->next;  //注意:* 和 -> 优先级一样,要加括号
  free(*pphead);
  *pphead = tmp;
}

9、在尾部删除数据

在尾部删除数据面临着和尾插一样的问题,需要改变前一个节点的next指针,所以时间复杂度也为O(N)。

//在尾部删除数据
void SListPopBack(SLTNode** pphead)
{
  assert(pphead);
  assert(*pphead);  //当链表为空时,删除元素报错
  if ((*pphead)->next == NULL)  //当链表只有一个元素时,相当于头删
  {
    SListPopFront(pphead);
    return;
  }
  //先找到链表的尾及其前一个节点
  SLTNode* cur = *pphead;
  SLTNode* prev = cur;
  while (cur->next != NULL)
  {
    prev = cur;
    cur = cur->next;
  }
  prev->next = NULL;
  free(cur);
  cur = NULL;
}

10、删除pos位置前的数据

//删除pos位置前的数据
void SListErase(SLTNode** pphead, SLTNode* pos)
{
  assert(pphead && pos);
  assert(*pphead);  //当链表为空时,删除元素报错
  if (pos == *pphead)  //pos等于*pphead时相当于头删
  {
    SListPopFront(pphead);
    return;
  }
  //找到pos的前一个节点
  SLTNode* prev = *pphead;
  while (prev->next != pos)
  {
    assert(prev);  //如果prev为空循环还没停止,说明在链表中找不到pos,直接报错
    prev = prev->next;
  }
  SLTNode* tmp = pos->next;
  prev->next = tmp;
  free(pos);
  pos = NULL;
}

11、删除pos位置后的数据

和在pos位置后插入数据一样,为了提高效率,人们也设计了一个在pos位置后删除数据的函数。

//删除pos位置后的数据
void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
{
  assert(pphead && pos);
  assert(*pphead);  //当链表为空时,删除元素报错
  assert((*pphead)->next);  //当链表只有一个元素时,也不能调用此函数
  SLTNode* tmp = pos->next->next;
  free(pos->next);
  pos->next = tmp;
}

12、修改pos位置处的数据

修改数据也不会改变头指针,所以这里传一级指针;同时,为了保证代码的鲁棒性,我们这里对 phead 和 pos 断言一下。

//修改pos位置处的数据
void SListModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
{
  assert(phead && pos);
  SLTNode* cur = phead;
  while (cur != pos)
  {
    assert(cur);  //如果cur为空循环还没停止,说明在链表中找不到pos,直接报错
    cur = cur->next;
  }
  cur->data = x;
}

13、打印链表中的数据

打印数据也不会改变头指针,所以这里传一级指针;但是这里和修改数据不一样的地方是,当链表为空的时候我们打印的逻辑也是正常的,只是说调用此函数什么都不打印而已,但是我们不能对其断言让其为空时报错。

//打印链表
void SListPrint(SLTNode* phead)  //打印不需要改变plist
{
  //不用对Phead进行断言,当链表为空时打印的逻辑是正常的
  SLTNode* cur = phead;
  while (cur != NULL)
  {
    printf("%d->", cur->data);  //-> 和 NULL 是为了让我们的链表更形象化
    cur = cur->next;
  }
  printf("NULL\n");
}

14、销毁链表

销毁链表需要将 plist 值为空,所以这里我们传递二级指针。

//销毁链表
void SListDestory(SLTNode** pphead)
{
  assert(pphead);
  SLTNode* cur = *pphead;
  while (cur != NULL)
  {
    SLTNode* tmp = cur->next;  //保存下一个节点的地方
    free(cur);  //释放
    cur = tmp;
  }
  *pphead = NULL;  //将plist置为空
}

三、完整代码

1、SList.h

#pragma once  //防止头文件重复包含
//头文件的包含
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//符号和结构的声明
typedef int SLTDataType;  //数据类型重命名
typedef struct SListNode   //链表的一个节点
{
  SLTDataType data;
  struct SListNode* next;  //存放下一个节点的地址
}SLTNode;
//函数的声明
//创建新建节点
SLTNode* BuySLTNode(SLTDataType x);
//在头部插入数据
void SListPushFront(SLTNode** pphead, SLTDataType x);
//销毁链表
void SListDestory(SLTNode** pphead);
//在尾部插入数据
void SListPushBack(SLTNode** pphead, SLTDataType x);
//查找数据
SLTNode* SListFind(SLTNode* phead, SLTDataType x);
//在pos之前插入数据
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在pos之后插入数据
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//打印链表
void SListPrint(SLTNode* phead);
//在头部删除数据
void SListPopFront(SLTNode** pphead);
//在尾部删除数据
void SListPopBack(SLTNode** pphead);
//删除pos位置处的数据
void SListErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置后的数据
void SListEraseAfter(SLTNode** pphead, SLTNode* pos);
//修改pos位置处的函数
void SListModify(SLTNode* phead, SLTNode* pos, SLTDataType x);

2、SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
//创建新节点
SLTNode* BuySLTNode(SLTDataType x)
{
  SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
  if (newNode == NULL)
  {
    perror("malloc fail");
    return NULL;  //如果malloc失败,返回NULL
  }
  newNode->data = x;
  newNode->next = NULL;
  return newNode;
}
//销毁链表
void SListDestory(SLTNode** pphead)
{
  assert(pphead);
  SLTNode* cur = *pphead;
  while (cur != NULL)
  {
    SLTNode* tmp = cur->next;  //保存下一个节点的地方
    free(cur);  //释放
    cur = tmp;
  }
  *pphead = NULL;  //将plist置为空
}
//在头部插入数据
void SListPushFront(SLTNode** pphead, SLTDataType x)  //要改变plist,所以用二级指针来接收plist的地址
{
  assert(pphead);  //pphead为plist的地址,一定不为空
  //assert(*pphead);  //error:*pphead得到plist,而链表可能没有节点,所以plist可以为空,不用断言
  SLTNode* newNode = BuySLTNode(x);  //开辟新节点
  newNode->next = *pphead;
  *pphead = newNode;
}
//打印链表
void SListPrint(SLTNode* phead)  //打印不需要改变plist
{
  //不用对Phead进行断言,当链表为空时打印的逻辑是正常的
  SLTNode* cur = phead;
  while (cur != NULL)
  {
    printf("%d->", cur->data);  //-> 和 NULL 是为了让我们的链表更形象化
    cur = cur->next;
  }
  printf("NULL\n");
}
//在尾部插入数据
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
  assert(pphead);  //plist的地址一定不为空
  SLTNode* newNode = BuySLTNode(x);
  if (*pphead == NULL)  //如果链表为空
  {
    newNode->next = *pphead;
    *pphead = newNode;
    return;
  }
  //如果链表不为空,我们需要找到链表的尾
  SLTNode* tail = *pphead;
  while (tail->next != NULL)
  {
    tail = tail->next;
  }
  tail->next = newNode;
}
//查找数据
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
  assert(phead);  //链表为空查找直接报错
  SLTNode* cur = phead;
  while (cur != NULL)
  {
    if (cur->data == x)
      return cur;  //找到返回节点地址
    cur = cur->next;
  }
  return NULL;  //找不到返回空
}
//在pos位置前插入数据
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
  assert(pphead);
  assert(pos);
  if (pos == *pphead)  //如果pos等于*pphead,相当于头插
  {
    SListPushFront(pphead, x);
    return;
  }
  SLTNode* newNode = BuySLTNode(x);
  //找到pos位置的前一个节点
  SLTNode* prev = *pphead;
  while (prev->next != pos)
  {
    assert(prev);  //如果prev为空循环还没停止,说明在链表中找不到pos,直接报错
    prev = prev->next;
  }
  prev->next = newNode;
  newNode->next = pos;
}
//在pos位置之后插入数据
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
  assert(pphead && pos);
  SLTNode* next = pos->next;
  SLTNode* newNode = BuySLTNode(x);
  pos->next = newNode;
  newNode->next = next;
}
//在头部删除数据
void SListPopFront(SLTNode** pphead)
{
  assert(pphead);
  assert(*pphead);  //当链表为空时,删除元素报错
  SLTNode* tmp = (*pphead)->next;  //注意:* 和 -> 优先级一样,要加括号
  free(*pphead);
  *pphead = tmp;
}
//在尾部删除数据
void SListPopBack(SLTNode** pphead)
{
  assert(pphead);
  assert(*pphead);  //当链表为空时,删除元素报错
  if ((*pphead)->next == NULL)  //当链表只有一个元素时,相当于头删
  {
    SListPopFront(pphead);
    return;
  }
  //先找到链表的尾及其前一个节点
  SLTNode* cur = *pphead;
  SLTNode* prev = cur;
  while (cur->next != NULL)
  {
    prev = cur;
    cur = cur->next;
  }
  prev->next = NULL;
  free(cur);
  cur = NULL;
}
//删除pos位置处的数据
void SListErase(SLTNode** pphead, SLTNode* pos)
{
  assert(pphead && pos);
  assert(*pphead);  //当链表为空时,删除元素报错
  if (pos == *pphead)  //pos等于*pphead时相当于头删
  {
    SListPopFront(pphead);
    return;
  }
  //找到pos的前一个节点
  SLTNode* prev = *pphead;
  while (prev->next != pos)
  {
    assert(prev);  //如果prev为空循环还没停止,说明在链表中找不到pos,直接报错
    prev = prev->next;
  }
  SLTNode* tmp = pos->next;
  prev->next = tmp;
  free(pos);
  pos = NULL;
}
//删除pos位置后的数据
void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
{
  assert(pphead && pos);
  assert(*pphead);  //当链表为空时,删除元素报错
  assert((*pphead)->next);  //当链表只有一个元素时,也不能调用此函数
  SLTNode* tmp = pos->next->next;
  free(pos->next);
  pos->next = tmp;
}
//修改pos位置处的数据
void SListModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
{
  assert(phead && pos);
  SLTNode* cur = phead;
  while (cur != pos)
  {
    assert(cur);  //如果cur为空循环还没停止,说明在链表中找不到pos,直接报错
    cur = cur->next;
  }
  cur->data = x;
}

3、test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void test1()   //测试插入
{
  SLTNode* plist = NULL;  //指向链表第一个节点的指针,因为链表没有节点,所以初始化为NULL;
  //头插
  SListPushFront(&plist, 1);
  SListPushFront(&plist, 2);
  SListPushFront(&plist, 3);
  SListPrint(plist);
  //尾插
  SListPushBack(&plist, 4);
  SListPushBack(&plist, 5);
  SListPushBack(&plist, 6);
  SListPrint(plist);
  //在pos位置前插入
  SLTNode* pos = SListFind(plist, 5);
  if (pos != NULL)
  {
    SListInsert(&plist, pos, 50);
  }
  pos = SListFind(plist, 3);
  if (pos != NULL)
  {
    SListInsert(&plist, pos, 30);
  }
  SListPrint(plist);
  pos = SListFind(plist, 6);
  if (pos != NULL)
  {
    SListInsert(&plist, pos, 60);
  }
  SListPrint(plist);
  //在pos位置后插入
  pos = SListFind(plist, 1);
  if (pos != NULL)
  {
    SListInsertAfter(&plist, pos, 10);
  }
  pos = SListFind(plist, 3);
  if (pos != NULL)
  {
    SListInsertAfter(&plist, pos, 30);
  }
  SListPrint(plist);
  //销毁
  SListDestory(&plist);
}
void test2()  //测试删除
{
  SLTNode* plist = NULL;  //指向链表第一个节点的指针,因为链表没有节点,所以初始化为NULL;
  //头插
  SListPushFront(&plist, 1);
  SListPushFront(&plist, 2);
  SListPushFront(&plist, 3);
  SListPrint(plist);
  //在头部删除数据
  //SListPopFront(&plist);
  //SListPopFront(&plist);
  //SListPopFront(&plist);
  //SListPrint(plist);
  //在尾部删除数据
  //SListPopBack(&plist);
  //SListPopBack(&plist);
  //SListPopBack(&plist);
  //SListPrint(plist);
  //删除pos位置处的数据
  //SLTNode* pos = SListFind(plist, 1);
  //if (pos != NULL)
  //{
  //  SListErase(&plist, pos);
  //}
  //pos = SListFind(plist, 3);
  //if (pos != NULL)
  //{
  //  SListErase(&plist, pos);
  //}
  //SListPrint(plist);
  //pos = SListFind(plist, 2);
  //if (pos != NULL)
  //{
  //  SListErase(&plist, pos);
  //}
  //SListPrint(plist);
  //删除pos位置后的数据
  SLTNode* pos = SListFind(plist, 2);
  if (pos != NULL)
  {
    SListEraseAfter(&plist, pos);
  }
  SListPrint(plist);
  pos = SListFind(plist, 3);
  if (pos != NULL)
  {
    SListEraseAfter(&plist, pos);
  }
  SListPrint(plist);
  //销毁
  SListDestory(&plist);
}
void test3()  //测试查和改
{
  SLTNode* plist = NULL;  //指向链表第一个节点的指针,因为链表没有节点,所以初始化为NULL;
  //头插
  SListPushFront(&plist, 1);
  SListPushFront(&plist, 2);
  SListPushFront(&plist, 3);
  SListPrint(plist);
  //查找并修改pos位置处的数据
  SLTNode* pos = SListFind(plist, 3);
  if (pos != NULL)
  {
    SListModify(plist, pos, 30);
  }
  SListPrint(plist);
  pos = SListFind(plist, 1);
  if (pos != NULL)
  {
    SListModify(plist, pos, 10);
  }
  SListPrint(plist);
  //销毁
  SListDestory(&plist);
}
int main()
{
  //test1();  //测试插入
  //test2();  //测试删除
  test3();  //测试查和改
}


相关文章
|
5天前
|
存储 编译器 C语言
C语言的联合体:一种节省内存的数据结构
C语言的联合体:一种节省内存的数据结构
9 0
|
7天前
|
存储 算法 搜索推荐
【数据结构和算法】--- 基于c语言排序算法的实现(2)
【数据结构和算法】--- 基于c语言排序算法的实现(2)
6 0
|
7天前
|
算法 程序员 数据处理
【数据结构与算法】使用单链表实现队列:原理、步骤与应用
【数据结构与算法】使用单链表实现队列:原理、步骤与应用
|
7天前
|
算法 C语言
【数据结构与算法 经典例题】返回单链表的倒数第 k 个节点
【数据结构与算法 经典例题】返回单链表的倒数第 k 个节点
|
7天前
|
存储 人机交互 C语言
【C语言项目实战】使用单链表实现通讯录
【C语言项目实战】使用单链表实现通讯录
|
7天前
|
存储 缓存 前端开发
【数据结构/C语言】深入理解 双向链表
【数据结构/C语言】深入理解 双向链表
|
7天前
|
存储 算法 C语言
【数据结构与算法】深入理解 单链表
【数据结构与算法】深入理解 单链表
|
7天前
|
搜索推荐 算法 C语言
【数据结构和算法】--- 基于c语言排序算法的实现(1)
【数据结构和算法】--- 基于c语言排序算法的实现(1)
14 0
|
7天前
|
存储 测试技术
【数据结构】最最基础的链式结构——单链表,还不会你就吃大亏了!
【数据结构】最最基础的链式结构——单链表,还不会你就吃大亏了!
16 5
|
19天前
|
C语言 C++
【数据结构】C语言实现:栈(Stack)与队列(Queue)
【数据结构】C语言实现:栈(Stack)与队列(Queue)