【数据结构和算法】认识线性表中的链表,并实现单向链表(上)

简介: 【数据结构和算法】认识线性表中的链表,并实现单向链表(上)

前言

我们知道了数据结构中线性表的概念,我们应该会感觉比较好理解,因为顺序表的建立主要涉及到结构体和动态内存管理函数,是类似于数组的一种形式。

我们要思考这样一个问题

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

2.增容一般都是2倍扩容,有时候也会浪费一定的空间

于是,为了解决上面这样的问题,我们引入了线性表中的链表,这一概念。


一、链表是什么?

1.链表的概念和结构

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

图示如下:

由上图可知,链表的特征为:

1.链式结构在逻辑上是连续的,但是在物理上是不一定连续的。        (每一个结点的地址是不一定的)

2.现实中的结点一般都是从堆中申请出来的。

3.从堆上申请的空间,是按照一定策略来分配,根据编辑器的不同而不同,再次申请的空间可能连续,也可能不连续。

2.链表的分类

实际上链表的结构有很多中,以下组合起来有8种主要的链表结构的情况

1.单向或者双向

2.带头或者不带头

头节点使用的话,就不需要对其数据域赋值,只起到一个成为建立链表的基点的作用,不使用的话,第一个结点存储数值就可以,创建一个新节点给这个第一个结点phead即可,这样头节点链表,就变成了非头结点的链表    

主要就是看第一个结点是否用到了其数值域

用第一结点数据域        非头节点

没用                             头节点

3.循环或者非循环

以上这些类型情况,我们常用的有两种,无头单向非循环链表,带头双向循环链表

如图所示

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

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

二、链表的实现

1.无头单向非循环链表

结构体为:

typedef int SLDataType;
//单向链表的实现、
typedef struct ListNode {
  SLDataType data;//数据域
  struct ListNode* next;//指针域
}List;

要实现的函数为:

//打印单链表
void ListPrint(List* ps);
//单链表的尾插
void ListPushBack(List** ps, SLDataType data);
//单链表的头插
void ListPushFront(List** ps, SLDataType data);
//单链表的尾删
void ListPopBack(List** ps);
//单链表的头删
void ListPopFront(List** ps);
//单链表的查找
List* ListFind(List* ps);
//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos);
//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos);
//在pos位子删除数据
void ListErase(List** ps, List* pos);
//在pos位置之后一位删除数据
void ListEraseAfter(List* pos);
//单链表的摧毁
void ListDestory(List** ps);

2.函数功能的实现

1.初始化和打印链表

//初始化链表
void InitList(List* ps) {
  ps->data = 0;
  ps->next = NULL;
}
//打印单链表
void ListPrint(List* ps) {
  List* cur = ps;
  while ((cur) != NULL) {
    printf("%d -> ", cur->data);
    cur = cur->next;
  }
  printf("NULL\n");
}

2.头插和尾插

尾部插入图示如下:

代码如下:

//创建一个新节点
List* CreateNode(SLDataType x) {
  List* newNode = (List*)malloc(sizeof(List));
  if (newNode == NULL) {
    perror("malloc fail\n");
    exit(-1);
  }
  else {
    newNode->data = x;
    newNode->next = NULL;
  }
  return newNode;
}
//单链表的尾插
void ListPushBack(List** ps, SLDataType data) {
  //创建新的节点
  assert(ps);//断言
  List* newNode = CreateNode(data);
  if (*ps == NULL) {
    //说明是空链表
    *ps = newNode;
  }
  else {
    List* tail = *ps;
    while (tail->next != NULL) {
      tail = tail->next;
    }
    tail->next = newNode;
  }
}

头部插入如图所示:

代码如下:

//单链表的头插
void ListPushFront(List** ps, SLDataType data) {
  //先断言是否为空
  assert(ps);
  //将新地址指向头结点下一个next结点的地址,然后在用头结点指向新节点
  List* newNode = CreateNode(data);
  newNode->next = (*ps);  //new指向ps当前的位置,然后new是第一个位置了,将new赋值给ps,这样new就作为头部连接链表了
  (*ps) = newNode;//原本ps位置的数值不变,这样的话就成 new->next=ps,new数值在前,ps的数值在后
}

3.头删和尾删

尾部删除如图所示:

代码演示:

//单链表的尾删
void ListPopBack(List** ps) {
  assert(ps);//断言
  //三种情况
  //1.空链表
  //2.一个节点
  //3.多个节点
  if (*ps == NULL) {
    return;
  }
  //只有一个节点的情况为
  else if ((*ps)->next == NULL) {
    free(*ps); //如果只有一个头节点的话
    *ps = NULL;
  }
  else {
    //多个节点的情况下、
    List* tail = *ps;
    while (tail->next->next!= NULL) {
      tail = tail->next;
    }
    free(tail->next);
    tail->next= NULL;
  }
}

头部删除如图所示:

代码如下:

//单链表的头删
void ListPopFront(List** ps) {
  assert(ps);
  //1.空
  //2.非空
  if (*ps == NULL) {
    //为空
    return;
  }
  else {
    List* tail = (*ps)->next;//创建临时变量tail,将头节点之后的地址给tail
    free(*ps);//滞空头节点
    *ps = NULL;//可有可不有,接下来也要用
    *ps = tail;//将tail也就是ps的下一个List节点给ps
  }
}

4.单链表的查找

代码如下:

//单链表的查找
List* ListFind(List* ps,SLDataType data) {
  //进行查找就是进行判断是否为空链表,为空直接返回
  if (ps == NULL) {
    printf("链表为空、无法查找\n");
    return;
  }
  List* tail = ps;
  while (tail != NULL) {//从头节点开始,进行循环,
    if (tail->data == data) {
      return tail;
    }
    tail = tail->next;
  }
  return tail;//最后还找不到data,tail就为NULL了
}

5.在pos结点位置之前或之后插入数据

在pos结点位置之前插入数据,如图所示:

代码如下:

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos) {
  //先判断是否为空
  assert(ps);
  assert(pos);
  //空链表排除
  //1.pos是第一个节点
  //2.pos不是第一个节点
  if (*ps == pos) {
    //是第一个节点,那就直接头插
    ListPushFront(ps, x);
  }
  else {
    List* prev = *ps;
    while (prev->next != pos) {
      prev = prev->next;
    }
    List* newnode = CreateNode(x);
    prev->next = newnode;
    newnode->next = pos;
  }
}

在pos结点位置之后插入结点,如图所示:

代码如下:

 

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos) {
  assert(ps);
  //assert(pos);//断言
  List* newnode = CreateNode(x);
  newnode->next = pos->next;
  pos->next = newnode;
}


目录
打赏
0
0
0
0
0
分享
相关文章
|
28天前
|
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
47 9
 算法系列之数据结构-二叉树
|
26天前
|
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
41 3
 算法系列之数据结构-Huffman树
|
28天前
|
算法系列之数据结构-二叉搜索树
二叉查找树(Binary Search Tree,简称BST)是一种常用的数据结构,它能够高效地进行查找、插入和删除操作。二叉查找树的特点是,对于树中的每个节点,其左子树中的所有节点都小于该节点,而右子树中的所有节点都大于该节点。
66 22
|
21天前
|
员工电脑监控系统中的 C# 链表算法剖析-如何监控员工的电脑
当代企业管理体系中,员工电脑监控已成为一个具有重要研究价值与实践意义的关键议题。随着数字化办公模式的广泛普及,企业亟需确保员工对公司资源的合理利用,维护网络安全环境,并提升整体工作效率。有效的电脑监控手段对于企业实现这些目标具有不可忽视的作用,而这一过程离不开精妙的数据结构与算法作为技术支撑。本文旨在深入探究链表(Linked List)这一经典数据结构在员工电脑监控场景中的具体应用,并通过 C# 编程语言给出详尽的代码实现与解析。
38 5
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
98 29
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
29 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
127 25
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
84 23
【C++数据结构——线性表】求集合的并、交和差运算(头歌实践教学平台习题)【合集】
本任务要求编写程序求两个集合的并集、交集和差集。主要内容包括: 1. **单链表表示集合**:使用单链表存储集合元素,确保元素唯一且无序。 2. **求并集**:遍历两个集合,将所有不同元素加入新链表。 3. **求交集**:遍历集合A,检查元素是否在集合B中存在,若存在则加入结果链表。 4. **求差集**:遍历集合A,检查元素是否不在集合B中,若满足条件则加入结果链表。 通过C++代码实现上述操作,并提供测试用例验证结果。测试输入为两个集合的元素,输出为有序集合A、B,以及它们的并集、交集和差集。 示例测试输入: ``` a c e f a b d e h i ``` 预期输出:
81 7
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
162 5
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等