数据结构之单链表及其实现!

简介: 数据结构之单链表及其实现!

1.  顺序表的问题及思考


问题:


1. 中间/头部的插入删除,时间复杂度为O(N)

2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。


思考:如何解决以上问题呢?下面给出了链表的结构来看看。


2.链表的概念结构和分类


2.1 概念及结构


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


2.2 分类


实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:


1. 单项或者双向


2. 带头或者不带头


3. 循环或者非循环


虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:


1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。


下面我就来实现一下无头单向非循环链表~


3. 单链表的实现


这里我就具体实现一下接口~(SList.h)

#define _CRT_SECURE_NO_WARNINGS
#pragma once//避免头文件被多次引用
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SListDataType;//便于改变数据类型
 
//定义一个结构体类型的节点
typedef struct SListNode
{
  SListDataType data;
  struct SListNode* next;//存储下一个节点的地址
}SListNode;
 
//1. 新节点的创建
SListNode* SListCreatNode(SListDataType x);
 
//2. 打印单链表
void PrintSList(SListNode* phead);
 
//3. 头插
void SListPushFront(SListNode** phead, SListDataType x);
 
//4. 头删
void  SListPopFront(SListNode** phead);
 
//5. 尾差
void SListPushBack(SListNode** phead, SListDataType x);
 
//6. 尾删
void  SListPopBack(SListNode** phead);
 
//7. 查找元素X
SListNode* SListFind(SListNode* phead, SListDataType x);
 
//8. 在pos位置修改
void SListModify(SListNode* pos, SListDataType x);
 
//9. 在任意位置之前插入
void SListInsert(SListNode** phead, SListNode* pos, SListDataType x);
 
//10. 在任意位置删除
void SListErase(SListNode** phead, SListNode* pos);
 
//11. 销毁单链表
void SListDestroy(SListNode** phead);


3.1 新节点的创建


(1)代码实现

//1. 新节点的创建
SListNode*  SListCreatNode(SListDataType x)
{
  SListNode* NewNode= (SListNode*)malloc(sizeof(SListNode));//开辟空间
  if (NewNode == NULL)//判断空间是否开辟成功
  {
    perror("malloc fail");
    return NULL;
  }
  NewNode->data = x;//赋值
  NewNode->next = NULL;//置空
  return NewNode;
}


3.2 打印单链表


(1)代码实现

//2. 打印单链表
void PrintSList(SListNode* phead)
{
  if (phead == NULL)
  {
    printf("NULL");//如果链表没有元素就打印NULL
    return;
  }
  SListNode* cur = phead;
  //循环单链表打印
  while (cur != NULL)
  {
    printf("%d->", cur->data);
    cur = cur->next;
  }
  printf("NULL\n");
}


(2) 复杂度分析

  • 时间复杂度:因为需要遍历整个链表,显然时间复杂度为O(N)。
  • 空间复杂度:打印链表不需要格外的空间,所以空间复杂度为O(1).


3.3 头插


(1)代码实现

//3. 头插
void SListPushFront(SListNode** phead, SListDataType x)
{
  assert(phead);
  SListNode* newnode =SListCreatNode(x);//创建一个新节点
  newnode->next =*phead;
  *phead = newnode;
}


(2) 复杂度分析

  • 时间复杂度:不需要额外时间的消耗,时间复杂度为O(1)。
  • 空间复杂度:固定创造一个节点,空间复杂度为O(1)。


3.4 头删


(1)代码实现

//4. 头删
void  SListPopFront(SListNode** phead)
{
  assert(phead);
  assert(*phead);//如果没有数据就不用头删,并报错
  SListNode* cur = (*phead)->next;
  free(*phead);
  *phead = cur;
}


(2) 复杂度分析

  • 时间复杂度:不需要额外时间的消耗,时间复杂度为O(1)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。


3.5 尾插


(1)代码实现

//5. 尾插
void SListPushBack(SListNode** phead, SListDataType x)
{
  assert(phead);
  if (*phead == NULL)
  {
    *phead = SListCreatNode(x);//创建新节点并插入
  }
  else
  {
    SListNode* tail = *phead;
    while (tail->next != NULL)//找到尾节点
    {
      tail = tail->next;
    }
    tail->next = SListCreatNode(x);//创建新节点并插入
  }
}


(2) 复杂度分析

  • 时间复杂度:需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:固定创造一个节点,空间复杂度为O(1)。


3.6 尾删


(1)代码实现

//6. 尾删
void  SListPopBack(SListNode** phead)
{
  assert(phead);
  assert(*phead);//链表为空就不进行尾删
  SListNode* tail = *phead;
  if (tail->next == NULL)//如果链表就只有一个元素就进行头删
  {
    SListPopFront(phead);
  }
  else
  {
    while (tail->next->next != NULL)
    {
      tail = tail->next;
    }
    free(tail->next);
    tail->next = NULL;
  }
}


(2) 复杂度分析

  • 时间复杂度:需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。


3.7 查找元素X


(1)代码实现

//7. 查找元素X
SListNode* SListFind(SListNode* phead, SListDataType x)
{
  assert(phead);
  while (phead->next!=NULL)//注意最后一个节点是没有查找的
  {
    if (phead->data == x)
      return phead;
      phead = phead->next;
  }
  if (phead->data == x)
    return phead;//最后一个节点没有查找
  else
  return NULL;//没找到
}


(2) 时间复杂度

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。


3.8 在pos位置修改


(1)代码实现

 
//8. 在pos位置修改
void SListModify( SListNode* pos, SListDataType x)
{
  assert(pos);
  pos->data = x;
}


(2) 时间复杂度

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。


3.9 在任意位置之前插入


(1)代码实现

//9. 在任意位置之前插入
void SListInsert(SListNode** phead, SListNode* pos, SListDataType x)
{
  assert(phead);
  assert(*phead);
  if (pos==*phead)//如果pos位置刚好是第一个节点就进行头插
  {
    SListPushFront( phead,x);
  }
  else
  {
    SListNode* newnode = SListCreatNode(x);
    SListNode* cur = *phead;
    while (cur->next != pos)//找到pos前一个节点
    {
      cur = cur->next;
    }
    cur->next = newnode;
    newnode->next  = pos;
  }
}


(2) 复杂度分析

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。


3.10 在任意位置删除


(1)代码实现

//10. 在任意位置删除
void SListErase(SListNode** phead, SListNode* pos)
{
  assert(phead && *phead && pos);
  if (pos==*phead)//如果pos位置就是第一个节点就进行头删
  {
    SListPopFront(phead);
  }
  else
  {
    SListNode* cur = *phead;
    while (cur->next != pos)//找到pos前一个节点
    {
      cur = cur->next;
    }
    cur->next = pos->next;
    free(pos);
  }
}


(2) 复杂度分析

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。


3.11 单链表的销毁


(1)代码实现

//11. 销毁单链表
void SListDestroy(SListNode** phead)
{
  assert(*phead && phead);
  SListNode* cur = *phead;
  while (cur!= NULL)
  {
    SListNode* tmp = cur->next;
    free(cur);
    cur = tmp;
  }
  *phead = NULL;
}


(2) 复杂度分析

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。


4. 完整代码


SList.c

#define _CRT_SECURE_NO_WARNINGS
 
#include"SList.h"
 
//1. 新节点的创建
SListNode*  SListCreatNode(SListDataType x)
{
  SListNode* NewNode= (SListNode*)malloc(sizeof(SListNode));//开辟空间
  if (NewNode == NULL)//判断空间是否开辟成功
  {
    perror("malloc fail");
    return NULL;
  }
  NewNode->data = x;//赋值
  NewNode->next = NULL;//置空
  return NewNode;
}
 
//2. 打印单链表
void PrintSList(SListNode* phead)
{
  if (phead == NULL)
  {
    printf("NULL");//如果链表没有元素就打印NULL
    return;
  }
  SListNode* cur = phead;
  //循环单链表打印
  while (cur != NULL)
  {
    printf("%d->", cur->data);
    cur = cur->next;
  }
  printf("NULL\n");
}
 
//3. 头插
void SListPushFront(SListNode** phead, SListDataType x)
{
  assert(phead);
  SListNode* newnode =SListCreatNode(x);//创建一个新节点
  newnode->next =*phead;
  *phead = newnode;
}
 
//4. 头删
void  SListPopFront(SListNode** phead)
{
  assert(phead);
  assert(*phead);//如果没有数据就不用头删,并报错
  SListNode* cur = (*phead)->next;
  free(*phead);
  *phead = cur;
}
 
//5. 尾插
void SListPushBack(SListNode** phead, SListDataType x)
{
  assert(phead);
  if (*phead == NULL)
  {
    *phead = SListCreatNode(x);//创建新节点并插入
  }
  else
  {
    SListNode* tail = *phead;
    while (tail->next != NULL)//找到尾节点
    {
      tail = tail->next;
    }
    tail->next = SListCreatNode(x);//创建新节点并插入
  }
}
 
//6. 尾删
void  SListPopBack(SListNode** phead)
{
  assert(phead);
  assert(*phead);//链表为空就不进行尾删
  SListNode* tail = *phead;
  if (tail->next == NULL)//如果链表就只有一个元素就进行头删
  {
    SListPopFront(phead);
  }
  else
  {
    while (tail->next->next != NULL)
    {
      tail = tail->next;
    }
    free(tail->next);
    tail->next = NULL;
  }
}
 
//7. 查找元素X
SListNode* SListFind(SListNode* phead, SListDataType x)
{
  assert(phead);
  while (phead->next!=NULL)//注意最后一个节点是没有查找的
  {
    if (phead->data == x)
      return phead;
      phead = phead->next;
  }
  if (phead->data == x)
    return phead;//最后一个节点没有查找
  else
  return NULL;//没找到
}
 
//8. 在pos位置修改
void SListModify( SListNode* pos, SListDataType x)
{
  assert(pos);
  pos->data = x;
}
 
//9. 在任意位置之前插入
void SListInsert(SListNode** phead, SListNode* pos, SListDataType x)
{
  assert(phead);
  assert(*phead);
  if (pos==*phead)//如果pos位置刚好是第一个节点就进行头插
  {
    SListPushFront( phead,x);
  }
  else
  {
    SListNode* newnode = SListCreatNode(x);
    SListNode* cur = *phead;
    while (cur->next != pos)//找到pos前一个节点
    {
      cur = cur->next;
    }
    cur->next = newnode;
    newnode->next  = pos;
  }
}
 
//10. 在任意位置删除
void SListErase(SListNode** phead, SListNode* pos)
{
  assert(phead && *phead && pos);
  if (pos==*phead)//如果pos位置就是第一个节点就进行头删
  {
    SListPopFront(phead);
  }
  else
  {
    SListNode* cur = *phead;
    while (cur->next != pos)//找到pos前一个节点
    {
      cur = cur->next;
    }
    cur->next = pos->next;
    free(pos);
  }
}
 
//11. 销毁单链表
void SListDestroy(SListNode** phead)
{
  assert(*phead && phead);
  SListNode* cur = *phead;
  while (cur!= NULL)
  {
    SListNode* tmp = cur->next;
    free(cur);
    cur = tmp;
  }
  *phead = NULL;
}
相关文章
|
29天前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
53 4
|
21天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
43 5
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
76 4
|
2月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
46 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
29天前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
47 0
|
2月前
|
存储
[数据结构] -- 单链表
[数据结构] -- 单链表
26 1
|
2月前
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
29 7
|
2月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
27 3