数据结构——单链表(C语言)

简介: 数据结构——单链表(C语言)

链表的概念和结构:

概念:链表是一种物理存储结构上非连续,非顺序的结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

下面是我们想象出来的图:


而实际上的图:

 

链表的结构多样,第一个就是不带头节点的链表,第二个是带哨兵位的头节点,而哨兵位是没有任何有效数据的。


下面我将讲解链表的各个实现,源码如下:

SListNode* BuySListNode(SListDateType x)
{
  SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  newnode->date = x;
  newnode->next = NULL;
  return newnode;
}
void SListPrint(SListNode* plist)
{
  SListNode* cur = plist;
  while(cur)
  {
    printf("%d->", cur->date);
    cur = cur->next;
  }
  printf("NULL\n");
}
void SListPushBack(SListNode** pplist, SListDateType x)
{
  SListNode* newnode = BuySListNode(x);
  if (*pplist == NULL)
  {
    *pplist = newnode;
  }
  else
  {
    SListNode* tail = *pplist;
    //β
    while (tail->next != NULL)
    {
      tail = tail->next;
    }
    tail->next = newnode;
  }
}
void SListPushFront(SListNode** pplist, SListDateType x)
{
  SListNode* newnode = BuySListNode(x);
  newnode->next = *pplist;
  *pplist = newnode;
}
void SListPopFront(SListNode** pplist)
{
  assert(*pplist);
  SListNode* next = (*pplist)->next;
  free(*pplist);
  *pplist = next;
}
void SListPopBack(SListNode** pplist)
{
  assert(*pplist);
  SListNode* tail = *pplist;
  SListNode* newnode = NULL;
  if ((*pplist)->next == NULL)
  {
    free(*pplist);
    *pplist = newnode;
  }
  else
  {
    while (tail->next)
    {
      newnode = tail;
      tail = tail->next;
    }
    free(tail);
    newnode->next = NULL;
  }
}
SListNode* SListFind(SListNode* plist, SListDateType x)
{
  SListNode* tail = plist;
  while (tail->next)
  {
    if (tail->date == x)
    {
      return tail;
    }
    tail = tail->next;
  }
  return NULL;
}
SListNode* SListInsertAfter(SListNode* pos, SListDateType x)
{
  assert(pos);
  SListNode* newnode = BuySListNode(x);
  SListNode* posnext = pos->next;
  pos->next = newnode;
  newnode->next = posnext;
}
void SListDestroy(SListNode* plist)
{
  assert(plist);
  free(plist);
}
void SListEraseAfter(SListNode* pos)
{
  assert(pos);
  SListNode* posnext = pos->next->next;
  free(pos->next);
  pos->next = posnext;
}

ps:1.这里我设计的链表函数时没有返回值的,所以我用到了二级指针,因为如果我们传一级指针的话,形参只是实参的一份临时拷贝,当出了作用域后, 创建的newnode等等这些在栈上开辟的变量就会不存在了,不会影响到要改变的plist。

2.当然你也可以设计一个返回结构体,这样就可以直接传值,最后也可以改变链表的结构。

链表的尾插:

  • 这里我是调用了一个BuySListNode的函数来创建一个节点,BuySListNode的实现就是用malloc开辟了结构体类型的空间,然后把SListDateType的类型数据给了结构体中的date,然后把指针域赋为了NULL
  • 尾插的思想:分为2种,第一种是一开始传的plist为NULL时,第二种就是plist不为NULL
  • 第一种情况:当传的plist为NULL时,说明我们链表还没数据,直接把创还能得newnode给给plist就行了。
  • 第二种情况:当plist不为NULL时,我们就用while循环找尾,然后把尾的next给给newnode,就实现了链表的尾插。
SListNode* BuySListNode(SListDateType x)
{
  SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  newnode->date = x;
  newnode->next = NULL;
  return newnode;
}
void SListPushBack(SListNode** pplist, SListDateType x)
{
  SListNode* newnode = BuySListNode(x);
  if (*pplist == NULL)
  {
    *pplist = newnode;
  }
  else
  {
    SListNode* tail = *pplist;
    while (tail->next != NULL)
    {
      tail = tail->next;
    }
    tail->next = newnode;
  }
}

链表的头插:

  • 头插的思想:
  • 我们直接让创建出来的newnode指向*pplist,然后更新头结点就可以了。
void SListPushFront(SListNode** pplist, SListDateType x)
{
  SListNode* newnode = BuySListNode(x);
  newnode->next = *pplist;
  *pplist = newnode;
}

链表的尾删:

  • 一开始用断言检查一下,*pplist不能为NULL,这里的意思是如果是NULL,就代表了没有数据了,不能删除
  • 第一种情况:当只有一个节点的时候,我就创建了newnode的节点,只需要free第一个节点,然后把newnode给给它就行了。
  • 第二种情况:当有二个或多个节点的时候,我就定义了一个tail指针来记录尾节点的位置,然后用newnode来记录尾结点前一个的位置,最后free掉tail节点,然后把前一个置为NULL。
void SListPopBack(SListNode** pplist)
{
  assert(*pplist);
  SListNode* tail = *pplist;
  SListNode* newnode = NULL;
  if ((*pplist)->next == NULL)
  {
    free(*pplist);
    *pplist = newnode;
  }
  else
  {
    while (tail->next)
    {
      newnode = tail;
      tail = tail->next;
    }
    free(tail);
    newnode->next = NULL;
  }
}

链表的头删:

  • 一开始也是和尾删一样的思路,用assert断言一下,防止没节点了还删除。
  • 头删的思路:记录头结点下一个节点的位置,然后free掉头结点,然后让下一个节点当头节点。
void SListPopFront(SListNode** pplist)
{
  assert(*pplist);
  SListNode* next = (*pplist)->next;
  free(*pplist);
  *pplist = next;
}

链表的查找:

  • 如果我们要查找链表中某个节点,直接遍历链表,当我们查找的数据和X相同时,就是我们要找的节点了,如果没有,就返回NULL。
SListNode* SListFind(SListNode* plist, SListDateType x)
{
  SListNode* tail = plist;
  while (tail->next)
  {
    if (tail->date == x)
    {
      return tail;
    }
    tail = tail->next;
  }
  return NULL;
}

链表指定位置之后的插入:

  • 首先先断言一下,pos是有效的节点
  • 我先创建了一个节点,然后用posnext来记录pos之后的节点,最后我让pos指向newnode,然后用newnode的next指向posnext。这样我就实现了链表指定位置之后的插入了。
SListNode* SListInsertAfter(SListNode* pos, SListDateType x)
{
  assert(pos);
  SListNode* newnode = BuySListNode(x);
  SListNode* posnext = pos->next;
  pos->next = newnode;
  newnode->next = posnext;
}

链表指定位置之前的插入:

  • 首先也是老套路,先断言一下,让pos和pplist为有效。
  • 这里分为二种情况
  1. 第一种:当pos是第一个节点的时候,就复用头插的函数。
  2. 第二种:当pos指向第二个或后面的节点的时候,用prev来记录pos之前节点的位置,然后让prev指向创建的newnode,newnode指向pos。
void SLTInsert(SLTNode** pplist, SLTNode* pos, SLTDataType x)
{
  assert(pplist);
  assert(pos);
  if (pos == *pplist)
  {
    SLTPushFront(pplist, x);
  }
  else
  {
    SLTNode* prev = *pplist;
    while (prev->next != pos)
    {
      prev = prev->next;
    }
    SLTNode* newnode = BuySListNode(x);
    prev->next = newnode;
    newnode->next = pos;
  }
}

链表指定位置之后的删除:

  • 老套路,先断言一下(保持好习惯)。
  • 用posnext来记录pos的下下的节点,free掉pos之后的节点,然后让pos指向posnext。
void SListEraseAfter(SListNode* pos)
{
  assert(pos);
  SListNode* posnext = pos->next->next;
  free(pos->next);
  pos->next = posnext;
}

链表指定位置的删除:

  • 先断言,这里就不再叙述了。
  • 第一种情况:当pos为第一个节点的时候,就是头删,复用头删的函数就可以了。
  • 第二种情况:用prev来记录pos之前的节点,然后让prev指向pos之后的节点,最后free掉pos节点。
void SLTErase(SLTNode** pplist, SLTNode* pos)
{
  assert(pplist);
  assert(pos);
  if (pos == *pplist)
  {
    SLTPopFront(pplist);
  }
  else
  {
    SLTNode* prev = *pplist;
    while (prev->next != pos)
    {
      prev = prev->next;
    }
    prev->next = pos->next;
    free(pos);
    //pos = NULL;
  }

链表最后的处理:

  • 还是先断言。
  • 然后用把第一个节点赋给cur,迭代往后走,用next来记录cur的下一个节点,free掉cur节点,最后让把cur下一个节点给给cur。
void SListDestroy(SListNode** pplist)
{
  assert(pplist);
  SListNode* cur = *pplist;
  while (cur)
  {
    SListNode* next = cur->next;
    free(cur);
    cur = next;
  }
}
#pragma once
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef int SListDateType;
typedef struct SListNode
{
  SListDateType date;
  struct SListNode* next;
}SListNode;
SListNode* BuySListNode(SListDateType x);
void SListPrint(SListNode* plist);
void SListPushBack(SListNode** pplist, SListDateType x);
void SListPushFront(SListNode** pplist, SListDateType x);
void SListPopFront(SListNode** pplist);
void SListPopBack(SListNode** pplist);
SListNode* SListFind(SListNode* plist, SListDateType x);
void SListInsertAfter(SListNode* pos, SListDateType x);
void SLTInsert(SLTNode** pplist, SLTNode* pos, SLTDataType x);
void SListEraseAfter(SListNode* plist);
void SLTErase(SLTNode** pplist, SLTNode* pos);
void SListDestroy(SListNode** pplist);
相关文章
|
4月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
393 9
|
2月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
4月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
173 16
|
4月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
86 4
|
4月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
241 8
|
5月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
86 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
4月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
205 4
|
4月前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
123 3
|
6月前
|
存储 Java
java数据结构,线性表链式存储(单链表)的实现
文章讲解了单链表的基本概念和Java实现,包括头指针、尾节点和节点结构。提供了实现代码,包括数据结构、接口定义和具体实现类。通过测试代码演示了单链表的基本操作,如添加、删除、更新和查找元素,并总结了操作的时间复杂度。
java数据结构,线性表链式存储(单链表)的实现
|
5月前
|
存储
[数据结构] -- 单链表
[数据结构] -- 单链表
39 1