1、链表的概念及结构

1.1链表的概念

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


1.2 链表的结构

链表是由一个个结点组成的,结点如下图所示

【数据结构入门】单链表(SList)详解(增、删、查、改)_#c语言

注意:链表中的最后一个结点的next指向空,next=NULL

一个个结点串成了链表,如下图所示:

【数据结构入门】单链表(SList)详解(增、删、查、改)_#数据结构_02

以上为逻辑结构图,画逻辑结构图形是我们所更容易理解链表结构的一种方式,其实物理结构上,各个结点之间是间隔很远的存储单元,哪儿有碎片内存空间我们计算机就会从哪儿开辟出空间拿出使用权出来。逻辑结构是通过结构体指针来存储地址访问,实现逻辑结构的连续性。



2、链表的实现

2.1 定义结点

介绍一下单链表的英文名——single linked list,我们用typedef定义成SLTode(区别于顺序表的SeqList或者SQL)。

注意:next指针的类型是struct SListNode*,千万不要写成SLTDateType*。

typedef int SLTDataType;

typedef struct SListNode
{
  SLTDataType data;
  struct SListNode* next;   //结构体指针


  
}SLTNode;


2.2 链表的功能

链表要实现那些功能呢?其实这些功能我们都很熟悉,数据结构无非是对数据进行管理,要实现数据的增删查改,因此链表的基本功能也都是围绕着数据的增删查改展开。

注意:链表是不需要进行初始化的(看后面结点的创建就明白了)

//单链表打印输出
void SLTPrint(SLTNode* phead);
//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

//单链表尾删
void SLTPopBack(SLTNode** pphead);
//单链表头删
void SLTPopFront(SLTNode** pphead);

//pos 之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//在pos位置之前删除
void SLTErase(SLTNode** pphead, SLTNode* pos);

//单链表查询
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

//在pos位置后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//在pos位置后面删除
void SListEraseAfter( SLTNode* pos );

// 单链表的销毁     
void SListDestroy(SLTNode* plist);


2.3 链表功能的实现

2.3.1 打印单链表

注意:链表和顺序不同的是,顺序表传过来的指针是肯定不会为空的,而链表传过来的指针是可能为空的,比如说当链表中没有元素时,头指针所指向的就是NULL,如果在第一行写上断言就会有问题。

当cur指向空的时候就可以停止打印了。

void SLTPrint(SLTNode* phead)
{
  
  //注意:不需要断言assert(phead);
  SLTNode* cur = phead;
  while (cur)
  {
    printf("%d->", cur->data);
    cur = cur->next;     //cur->next 指向下一个结构体指针 结构体指针存放地址访问到下一个结构体
  }

}

2.3.2 创建一个新结点

后面我们要在单链表中进行头插和尾插,此时插入的不再是像顺序表一样简单的SLDateType数据了,而是一个结点,这个结点是包括SLTDateType数据以及SLTDateType*的指针,因此,为了方便和减少代码的重复度,我们另写一个函数用来专门创建新结点。 (方便复用)

SLTNode* BuySLTNode(SLTDataType x)
{
  // 增加一个newnode结点
    SLTNode * newnode = (SLTNode*)malloc(sizeof(SLTNode));  //计算机在内存中找到一个零碎的空间将使用权(地址)给我这个指针
    newnode->data = x;  //传值
    newnode->next = NULL;  //将newnode的指针域置空

  return newnode;

}


2.3.3 单链表尾插

注意:在创建结点时,已经让 结点.next=NULL,所以不需要在插入完结点后,再让新结点的next指针为NULL。

有人可能会有疑问,为什么之前打印链表的时候不用断言指针,而在尾插时就要断言指针,以及为什么函数的形参是二级指针,而不使用一级指针。

因为,尾插分为两种情况(下面有写),当链表为空时,头指针phead指向NULL,尾插相当于头插,此时要改变phead的指向,让phead指向这个新结点,此时就需要二级指针来改变一级指针的值(如果我们用一级指针做形参,只是一级指针的一份临时拷贝,形参的改变不会影响实参,那么一级指针phead就不会被改变)。

至于这个什么时候要断言指针,什么时候不用断言指针:一级指针也就是phead,当链表为空的时候,phead就是为NULL,而二级指针永远指向phead,phead的地址是永远存在的,那么pphead就一定不可能为空,所以需要断言pphead。

void SLTPushBack(SLTNode** pphead, SLTDataType x)    //尾插本质:原尾节点中要存储新的尾节点的地址
{
  SLTNode* newnode=BuySLTNode(x);

  if (*pphead == NULL)
  {
    *pphead = newnode;
  }
  
  else
  {

    //找尾
    SLTNode* tail = *pphead;  //*pphead 就是plist     
    while (tail->next != NULL)   //比如存的是1 2 3 4  4下面存的next是NULL
    {
      tail = tail->next; //前一个tail是尾节点前一个节点 通过赋值找到最后一个节点    总结尾节点的特点是自己的 next==NULL;

    }
    tail->next = newnode;   //让tail指向的最后一个节点的next结构体指针存储 newnode的地址 这样才算是链接上
    //赋完值之后进行销毁这块内存

  }
      

}


2.3.4 单链表尾删

要想删除链表中的元素,就必须保证原链表就有元素,因此要断言assert(*pphead)

尾删需要分情况去讨论

void SLTPopBack(SLTNode** pphead)   //尾删
{
  //1.只有一个节点
  //2.多个节点
  assert(pphead);
  assert(*pphead);

  if ((*pphead)->next == NULL)
  {
    free(*pphead);
    *pphead = NULL;
  }
  else
  {
    //找尾
    SLTNode* prev = NULL;
    SLTNode* tail = *pphead;  //*pphead 就是plist     
    while (tail != NULL)   
    {
      prev = tail->next; 
  
    }
    free(tail);
    tail = NULL;
    prev->next = NULL;
  }

}

2.3.5  单链表头删

头删没什么好说的,记住要断言*pphead,保证链表内容不为空。

void SLTPopFront(SLTNode** pphead)   //头删
{
  //防空链表
  assert(pphead);
  assert(*pphead);
  SLTNode* first = *pphead;
  *pphead = first->next;

  free(first);  //释放空间
  first = NULL;  //first 置空

}

2.3.6 顺序表头插

void SLTPushFront(SLTNode** pphead, SLTDataType x) //头插
{
  //申请一个新结点
  SLTNode* newnode = BuySLTNode(x);
  //让新结点指向头指针
  newnode->next = *pphead;
  //头指针指向更新
  *pphead = newnode;
}

2.3.7 查找某个结点

这个函数返回值不再是void,而是SListNode*,把找到的结点的地址返回去,这个函数一般会跟结点修改之类的函数一起使用。

//单链表查询
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
  SLTNode* val = phead;
  while (val)
  {
    if (val->data == x)
    {
      return val;
    }
    else
    {
      val = val->next;
    }
  }
  return NULL;

}

2.3.8 删除某个结点

2.3.8.1 在pos位置之前删除
//在pos位置之前删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
  //断言
  assert(pphead);
  //assert(*pphead);
  assert(pos);
  //在头就直接头删
  if (pos == *pphead)
  {
    SLTPopFront(pphead);
  }
  SLTNode* prev = *pphead;
  while (prev->next != pos)
  {
    prev = prev->next;

  }
  prev->next = pos->next;
  free(pos);
}
2.3.8.2在pos位置后面删除
void SListEraseAfter(SLTNode*  pos)
{
  assert(pos);
  assert(pos->next);

  SLTNode* del = pos->next;
  pos->next = pos->next->next;
  free(del);
  del = NULL;
}

注意:

  1. pos也要断言,pos可不能为空呀!
  2. cur->next也要断言,因为当cur->next为NULL时,说明整个链表的结点都排查完了,最后还是没有找到地址为pos的结点,证明pos传值有误。


2.3.9 插入某一个结点

2.3.9.1在pos位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
  assert(pos);
  if (pos == *pphead)
  {
    SLTPushFront(pphead, x);


  }
  else
  {
    //找到pos的前一个位置
    SLTNode* prev = *pphead;
    while (prev->next != pos)
    {
      prev = prev->next;
    }

    SLTNode* newnode = BuySLTNode(x);
    prev->next = newnode;
    newnode->next = pos;

  }
}
2.3.9.2在pos位置后面插入
void SLTInsertAfter( SLTNode* pos, SLTDataType x)
{
  assert(pos);
  
  SLTNode* newnode = BuySLTNode(x);
  newnode->next = pos->next;
  pos->next = newnode;

 
}


2.3.10 单链表的销毁

// 单链表的销毁
void SListDestroy(SLTNode* plist)
{
  SLTNode* cur = plist;
  while (cur)
  {
    SLTNode* tmp = cur->next;
    free(cur);
    cur = tmp;
  }
  plist = NULL;
}

2.3.11 单链表结点修改

// 单链表结点修改
void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
{
  SLTNode* cur = phead;
  while (cur != pos)
  {
    cur = cur->next;
    assert(cur);
  }
  pos->data = x;
}


3、单链表实现效果

如图:


【数据结构入门】单链表(SList)详解(增、删、查、改)_结构体指针_03

4、单链表总代码

test.c ----函数的调用

#include"SList.h"
#include<stdio.h>



void TestSList1()
{
  SLTNode* plist = NULL;
  SLTPushBack(&plist, 1); //如果要改变结构体指针就要传地址
  SLTPushBack(&plist, 2);
  SLTPushBack(&plist, 3);
  SLTPushBack(&plist, 4);
  SLTPushBack(&plist, 5);
  SLTPushBack(&plist, 6);
  SLTPrint(plist);

  SLTPopFront(&plist);
  SLTPopFront(&plist);
  SLTPopFront(&plist);
  SLTPopFront(&plist);
  printf("\n");
  SLTPrint(plist);
  printf("\n");
  SLTNode* val= SLTFind(plist, 5);
  val->data *= 2;
  printf("%p", val->next);
  //val = val->next;
  //printf("\n");
  //printf("%p", val->next);
  SLTInsertAfter(plist, 1);
  printf("\n");
  SLTPrint(plist);
  SListEraseAfter(plist);
  printf("\n");
  SLTPrint(plist);

  SListDestroy( plist);


}

int main()
{

  TestSList1();

  return 0;
}



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;


//单链表打印输出
void SLTPrint(SLTNode* phead);
//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

//单链表尾删
void SLTPopBack(SLTNode** pphead);
//单链表头删
void SLTPopFront(SLTNode** pphead);

//pos 之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//在pos位置之前删除
void SLTErase(SLTNode** pphead, SLTNode* pos);

//单链表查询
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

//在pos位置后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//在pos位置后面删除
void SListEraseAfter( SLTNode* pos );

// 单链表的销毁     
void SListDestroy(SLTNode* plist);

void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType  x);

SList.c     ------函数的实现

#include"SList.h"


void SLTPrint(SLTNode* phead)
{
  SLTNode* cur = phead;
  while (cur)
  {
    printf("%d->", cur->data);
    cur = cur->next;     //cur->next 指向下一个结构体指针 结构体指针存放地址访问到下一个结构体
  }

}
SLTNode* BuySLTNode(SLTDataType x)
{
  // 增加一个newnode节点
    SLTNode * newnode = (SLTNode*)malloc(sizeof(SLTNode));
  newnode->data = x;
  newnode->next = NULL;

  return newnode;

}

void SLTPushBack(SLTNode** pphead, SLTDataType x)    //尾插本质:原尾节点中要存储新的尾节点的地址
{
  SLTNode* newnode=BuySLTNode(x);

  if (*pphead == NULL)
  {
    *pphead = newnode;
  }
  
  else
  {

    //找尾
    SLTNode* tail = *pphead;  //*pphead 就是plist     
    while (tail->next != NULL)   //比如存的是1 2 3 4  4下面存的next是NULL
    {
      tail = tail->next; //前一个tail是尾节点前一个节点 通过赋值找到最后一个节点    总结尾节点的特点是自己的 next==NULL;

    }
    tail->next = newnode;   //让tail指向的最后一个节点的next结构体指针存储 newnode的地址 这样才算是链接上
    //赋完值之后进行销毁这块内存

  }
      

}

void SLTPushFront(SLTNode** pphead, SLTDataType x) //头插
{
  SLTNode* newnode = BuySLTNode(x);
  
  newnode->next = *pphead;
  *pphead = newnode;
}


void SLTPopBack(SLTNode** pphead)   //尾删
{
  //1.只有一个节点
  //2.多个节点
  assert(pphead);
  assert(*pphead);

  if ((*pphead)->next == NULL)
  {
    free(*pphead);
    *pphead = NULL;
  }
  else
  {
    //找尾
    SLTNode* prev = NULL;
    SLTNode* tail = *pphead;  //*pphead 就是plist     
    while (tail != NULL)   
    {
      prev = tail->next; 
  
    }
    free(tail);
    tail = NULL;
    prev->next = NULL;
  }

}



void SLTPopFront(SLTNode** pphead)   //头删
{
  //防空链表
  assert(pphead);
  assert(*pphead);
  SLTNode* first = *pphead;
  *pphead = first->next;

  free(first);  //释放空间
  first = NULL;  //first 置空

}
//单链表查询
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
  SLTNode* val = phead;
  while (val)
  {
    if (val->data == x)
    {
      return val;
    }
    else
    {
      val = val->next;
    }
  }
  return NULL;

}

//在pos位置之前删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
  assert(pphead);
  //assert(*pphead);
  assert(pos);
  if (pos == *pphead)
  {
    SLTPopFront(pphead);
  }
  SLTNode* prev = *pphead;
  while (prev->next != pos)
  {
    prev = prev->next;

  }
  prev->next = pos->next;
  free(pos);
}


//在pos位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
  assert(pos);
  if (pos == *pphead)
  {
    SLTPushFront(pphead, x);


  }
  else
  {
    //找到pos的前一个位置
    SLTNode* prev = *pphead;
    while (prev->next != pos)
    {
      prev = prev->next;
    }

    SLTNode* newnode = BuySLTNode(x);
    prev->next = newnode;
    newnode->next = pos;

  }
}

//在pos位置后面插入
void SLTInsertAfter( SLTNode* pos, SLTDataType x)
{
  assert(pos);
  
  SLTNode* newnode = BuySLTNode(x);
  newnode->next = pos->next;
  pos->next = newnode;

 
}
//在pos位置后面删除
void SListEraseAfter(SLTNode*  pos)
{
  assert(pos);
  assert(pos->next);

  SLTNode* del = pos->next;
  pos->next = pos->next->next;
  free(del);
  del = NULL;
}
// 单链表的销毁
void SListDestroy(SLTNode* plist)
{
  SLTNode* cur = plist;
  while (cur)
  {
    SLTNode* tmp = cur->next;
    free(cur);
    cur = tmp;
  }
  plist = NULL;
}

// 单链表结点修改
void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
{
  SLTNode* cur = phead;
  while (cur != pos)
  {
    cur = cur->next;
    assert(cur);
  }
  pos->data = x;
}