链表详讲(附代码)

简介: 链表详讲(附代码)

1.什么是链表

链表是一种非常常见的数据结构,在程序设计中经常被使用。它由一系列节点组成,每个节点都有用来存放数据的数据区以及存放下一个节点地址指针的地址区。跟顺序表不同的是,链表的节点之间的空间并非是连续的,依靠地址区的值找到下一个节点的位置,这样相互链接成的链状结构我们称之为链表。

2.链表的优缺点

链表和顺序表其实是互补的好兄弟,换句话来说,顺序表的优点是链表的缺点,而链表的优点又恰恰是顺序表的缺点。

2.1链表的优点

链表的优点在于它的灵活性,相比于数组,链表可以更方便地进行插入和删除操作,这是因为链表的节点可以在内存中任意位置创建和删除,而数组需要通过整体移动来实现插入和删除操作,效率较低。

相比于顺序表,链表一般不会造成空间的浪费,而顺序表则会因为扩容的问题往往开辟出较大的空间,会存在一定的浪费。

2.2链表的缺点

链表的缺点在于查找,它在随机访问和查找元素时效率不如数组,因为每个节点只能通过指针一步一步向下查找。

先比于顺序表,顺序表查找的时间可以达到O(1),而链表通常是O(n).

2.3总结

根据链表的优点,我们可以在频繁插入、删除操作但不需要随机访问和查找元素时使用这种数据结构。

例如,在计算机科学中,许多常见问题,如哈希表、图、堆栈、队列等,都可以使用链表作为基础数据结构来实现。

3.代码模拟链表(c语言)

3.1功能函数的声明:

SList.h头文件:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SListNode
{
  SLTDateType data;
  struct SListNode* next;
}SListNode;
 
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
 
// 在pos的前面插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x);
// 删除pos位置
void SLTErase(SListNode** pphead, SListNode* pos);
//销毁链表
void SLTDestroy(SListNode** pphead);

功能呢,无非就是常见的各种增、删、查、改。

但是值得注意的是,由于我这里链表的节点是结构体,用head结构体指针来指向该链表的头节点,所以在传参的时候要注意一级指针和二级指针的区别。

比如说假如我们要修改head指向的节点,这个时候我们修改的值是一个指针,所以传参进来要用二级指针。

3.2功能函数的实现

SList.c:

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
 
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x) {
  SListNode* res = (SListNode*)malloc(sizeof(SListNode));
  res->data = x;
  res->next = NULL;
  return res;
}
 
// 单链表打印
void SListPrint(SListNode* plist) {
  while (plist != NULL) {
    printf("%d ->", plist->data);
    plist = plist->next;
  }
  /*printf("%d->", plist->data);
  plist = plist->next;*/
 
  printf("NULL \n");
}
 
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x) {
  assert(pplist);
  SListNode* temp = BuySListNode(x);
  if (*pplist == NULL) {//当前链表没有节点
    *pplist = temp;
  }
  else {
    SListNode* tail = *pplist;
    while (tail->next != NULL) {
      tail = tail->next;
    }
    temp->next = NULL;
    tail->next = temp;
 
    //*pplist = tail;
  }
  return;
 
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x) {
  assert(pplist);
  SListNode* temp = BuySListNode(x);
  if (*pplist == NULL) {//当前链表没有节点
    *pplist = temp;
  }
  else {
    temp->next = *pplist;
    *pplist = temp;
  }
  return;
}
// 单链表的尾删
void SListPopBack(SListNode** pplist) {
  assert(pplist && *pplist);
  SListNode* tail = *pplist;
  if ((*pplist)->next == NULL) {//只有一个节点
    free(*pplist);
    *pplist = NULL;
  }
  else {
    SListNode* pre = NULL;
    while (tail->next != NULL) {
      pre = tail;
      tail = tail->next;
    }
    free(tail);
    pre->next = NULL;
  }
}
// 单链表头删
void SListPopFront(SListNode** pplist) {
  assert(pplist&&*pplist);
  if ((*pplist)->next==NULL) {//只有一个节点
    free(*pplist);
    *pplist = NULL;
  }
  else {
    SListNode* temp = (*pplist)->next;
    free(*pplist);
    *pplist = temp;
  }
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {
  assert(plist);
  while (plist!=NULL) {
    if (plist->data == x) {
      return plist;
    }
    plist = plist->next;
  }
  return NULL;
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x) {
  assert(pos);
  SListNode* temp = BuySListNode(x);
  if (pos->next == NULL) {
    pos->next = temp;
    temp->next = NULL;
  }
  else {
    temp->next = pos->next;
    pos->next = temp;
  }
}
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos) {
  assert(pos&&pos->next);
  SListNode* temp = pos->next->next;
  free(pos->next);
  pos->next = temp;
}
// 在pos的前面插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x) {
  assert(*pphead && pos);
  SListNode* temp = BuySListNode(x);
  if ((*pphead)->next == NULL||(*pphead)==pos) {
    temp->next = (*pphead);
    *pphead = temp;
  }
  else {
    SListNode* pre = NULL;
    SListNode* cur = *pphead;
    while (cur->next!= NULL) {
      if (cur== pos)break;
      pre = cur;
      cur = cur->next;
    } 
    temp->next = pre->next;
    pre->next = temp;
  }
 
}
// 删除pos位置
void SLTErase(SListNode** pphead, SListNode* pos) {
  assert(*pphead && pos);
  if ((*pphead) == NULL) {
    free(*pphead);
    *pphead = NULL;
  }
  else {
    SListNode* pre = NULL;
    SListNode* cur = *pphead;
    while (cur->next != NULL) {
      if (cur == pos)break;
      pre = cur;
      cur = cur->next;
    }
    SListNode* temp = cur->next;
    free(cur);
    if (pre == NULL) {
      *pphead = temp;
    }
    else {
      pre ->next= temp;
    }
  }
}
//销毁链表
void SLTDestroy(SListNode** pphead) {
  assert(*pphead);
  SListNode* ph = *pphead;
  while (ph!= NULL) {
    SListNode* temp = ph->next;
    free(ph);
    ph = temp;
  }
  *pphead = NULL;
}

由于是用malloc动态开辟空间,在删除操作的时候我们要使用free及时释放,避免照成空间泄露的问题。

代码经过测试应该是没有太大的问题,基本功能也都能实现。

这里我想强调一点,为什么我们要模拟链表的功能呢?

模拟各种数据结构的功能可以让我们理解得更加透彻,能较为清晰的明白各种数据结构内部代码大概是怎么实现的。我认为,这对数据结构的熟练运用是有非常大的帮助的。

相关文章
|
算法
带你读《图解算法小抄》二十三、树(1)
带你读《图解算法小抄》二十三、树(1)
|
3月前
【刷题记录】详谈设计循环队列
【刷题记录】详谈设计循环队列
|
6月前
|
算法 C++ 容器
黑马c++ STL常用算法 笔记(4) 常用拷贝和替换算法
黑马c++ STL常用算法 笔记(4) 常用拷贝和替换算法
|
6月前
|
存储 缓存 算法
C++链表解析:从基础原理到高级应用,全面掌握链表的使用
C++链表解析:从基础原理到高级应用,全面掌握链表的使用
544 0
|
12月前
|
存储
数据结构之手撕链表(讲解➕源代码)-1
数据结构之手撕链表(讲解➕源代码)
|
12月前
|
缓存
数据结构之手撕链表(讲解➕源代码)-2
数据结构之手撕链表(讲解➕源代码)-2
|
算法
带你读《图解算法小抄》十六、链表
带你读《图解算法小抄》十六、链表
|
算法 C++
带你读《图解算法小抄》二十三、树(2)
带你读《图解算法小抄》二十三、树(2)