【奇妙的数据结构世界】用图像和代码对链表的使用进行透彻学习 | C++

简介: 简单来说,数据结构是一种辅助程序设计并且进行优化的方法论,它不仅讨论数据的存储与处理的方法,同时也考虑到了数据彼此之间的关系与运算,从而极大程度的提高程序执行的效率,减少对内存空间的占用等。不同种类的数据结构适用于不同的程序应用,选择合适正确的数据结构,可以让算法发挥出更大的性能,给设计的程序带来更高效率的算法。

前言

 简单来说,数据结构是一种辅助程序设计并且进行优化的方法论,它不仅讨论数据的存储与处理的方法,同时也考虑到了数据彼此之间的关系与运算,从而极大程度的提高程序执行的效率,减少对内存空间的占用等。不同种类的数据结构适用于不同的程序应用,选择合适正确的数据结构,可以让算法发挥出更大的性能,给设计的程序带来更高效率的算法。

一、链表是什么?

1.简要介绍

链表也被称为动态数据结构,它使用不连续的内存空间来存储数据元素,由许多相同数据类型的数据项按特定顺序排列而成的线性表。链表中的各个数据在计算机的内存中是不连续且随机存放的,所以对它进行插入和删除操作不需要移动大量的数据,因此是相当方便的。如果要有新的元素加入链表,那么就需要向系统去申请内存空间。如果数据被删除后,就把这块内存空间释放还给系统。但是在代码中对其数据结构进行设计时较为麻烦,以及查询数据元素时不能像静态数据结构那样按索引值去直接进行访问,而是需要从前到后依次遍历查找,直到找到该数据元素为止。

2.具体情况

   在日常生活中,链表有许多抽象概念的运用。例如以前的火车,当乘坐的人多时就添加一些车厢;而当人少时就减少一些车厢;这就很像一个典型的单向链表的抽象运用。生活中还有很多链表这类抽象概念的运用。

在动态分配内存空间的时候,单向链表是我们经常使用到的。每个单向链表由多个结点组成的,每个结点又是由数据域和指针域组成的。数据域中存储数据,指针域中放着指向下一个元素在内存中的地址。在单向链表中第一个结点是链表的头结点,并且头指针指向头结点。最后一个结点是该链表的尾结点。具体情况如下图所示:

因为在单链表中,所有结点都知道自己下一个结点在哪个位置。但前一个的结点信息确无法得知,所以头指针L在链表的各项操作中就显得非常重要。只要链表中头指针存在就可以访问遍历整个链表的具体情况,并且进行一系列相关的操作。单链表是我们在数据结构中比较常用的,用它就可以去解决绝大部分的链表类程序设计的题型。但是链表中除了单链表,还有双向链表和循环链表这两类相对于单向链表是不常用的两种链表数据结构。具体如下图所示:

二、链表操作的关键代码段

1.类型定义

①单链表存储结构的定义(重点)

typedef struct lnode{
  elemtype data;   //数据域
  struct lnode *next;  //指针域
}lnode,*linklist;   
//lnode *p 结点指针定义
//linklist &L  单链表L定义
 ②双向链表存储结构的定义
typedef struct lnode {
  elemtype data;   数据域
  struct lnode* prior, * next;  前后指针域
}lnode,*linklist;
//lnode *p 结点指针定义
//linklist &L  单链表L定义
③循环链表存储结构的定义
(将单链表的头尾用指针进行相连)
typedef struct lnode {
  elemtype data;
  struct lnode* next;
}lnode,*linklist;
linklist connect(linklist ta,linklist tb)  //ta为头 tb为尾
{
  lnode* p;
  p = ta->next;   //将头结点的指针域保存
  ta->next=tb->next->next;  //将尾结点指针域的下一个区域连接到头结点的指针域
  delete tb->next;  //将尾结点的指针域删除,因为他将连接到头结点那里
  tb->next = p;   //将头结点的指针再次绑定到已经循环回来的tb上
  return tb;

2.常用操作

①链表初始化


初始化的链表为空链表

void initlist(linklist& L)
{
  L = new lnode;   //创建一块新的结点区域
  L->next = NULL;   //将这个结点区域的指针域赋为空
}
 ②判断链表是否为空
bool listempty(linklist& L)
{ 
  //常规判断
  if (L->next)
  return true;
  else
  return false;
}

③ 链表的清空

void clearlist(linklist& L)
{
  lnode* p;
  lnode* q; //定义两个结点指针
  p=L->next;  //将链表头结点的指针域指向的下个结点赋给指针p
  while (p)  //如果指针p存在,则开始循环判断
  {
  q = p->next;   //循环中事先将p的再下一个指针域指向的元素赋给指针q
  delete p;   //删除此时的指针p,此时p将无内容
  p = q;   //再将q中的内容赋给p,进行继续的循环判断即可
  }
  L->next = NULL;    //将链表修改成了初始化下的状态
}

④链表的销毁

void destorylist(linklist& L)
{
  lnode* p;
  L=L->next;
  while (L != NULL)  //循环判断,直到链表的结点被删除为空为止
  {
  p = L;   
  L = L->next;
  delete p;   //删除结点
  }
}

⑤ 链表的表长

int lengthlist(linklist& L)
{
  lnode* p;
  p=L->next;
  int i = 0;  //将用来展示链表的长度
  while (p)  //如果结点存在,从前到后遍历循环判断
  {
  i++;  //进行计数
  p = p->next;   //指针继续向后指向链表后面的指针域,用于循环的下一次判断
  }
  return 1;
}

⑥取链表中的第i个数据域的内容

int getelem(linklist& L,int i,elemtype e)
{
  lnode* p;
  p = L->next;
  int j = 1;
  //要取第i个元素的内容,首先进行循环判断,让指针p从前向后移动,移动i次
  while (p && j < i)   //两个条件:p存在,计数的j小于i
  {
  p = p->next;
  j++;
  }
  //进行一个判断,判断循环完的p是否存在,j是否满足条件
  if (!p || j > i) {
  return 0;
  }
  else {
  e = p->data;
  return 1;
  }
}

⑦在第i个结点前插入值为e的新结点

int insertlist(linklist& L,int i,elemtype e)
{
  lnode* p;
  p = L->next;
  int j = 0;
  while (p&&j<i-1)
  {
  p = p->next;
  j++;
  }
  if (!p || j > i - 1) {
  return 0;
  }
  else {
  linklist s;//因为需要重新添加一个结点信息,所以需要new一块新的结点区域
  s = new lnode;
  s->data = e;   //将数据元素插入该结点的数据域
  s->next=p->next;  //将最初p结点的下一个结点的地址赋给新的结点s的指针域
  p->next = s;//直接将s的地址,赋给p的指针域
  return 1;
  } 
}

 删除第i个结点

int deletelist(linklist& L, int i, elemtype e)
{
  lnode* p;
  lnode* q;
  p = L->next;
  int j = 0;
  while (p && j< i - 1)
  {
  p = p->next;
  j++;
  }
  if (!p || j > i - 1) {
  return 0;
  }
  else {
  q = p->next;   //此时结点p的下一个结点就是要去删除的结点,将这个位置赋给指针q
  p->next=q->next;//将要删除的前后结点连起来,不去影响该链表
  e=q->data;  //用e先将要删除结点中的数据元素保存下来
  delete q;  //删除要删除的结点
  return 1;
  }
}

⑨头插法(前提是已经通过初始化创建了一个空链表)

void insertlist_head(linklist& L,int n)
{
  linklist head_s;
  for (int i = n; i > 0; i--)
  {
  head_s = new lnode;     //创建新的结点
  cin >> head_s->data;   //输入数据域的数据
  head_s->next=L->next;  //进行结点间的连接
  L->next = head_s;   //进行结点间的连接
  }
}

⑩尾插法(前提是已经通过初始化创建了一个空链表)

void insertlist_tail(linklist& L, int n)
{
  linklist tail_s;
  lnode* p;
  p = L;
  for (int i = 1; i <= n; i++)
  {
  tail_s = new lnode;
  cin >> tail_s->data;
  tail_s->next = NULL;  //每次都将新创建结点的指针域置为空
  p->next = tail_s;
  p = tail_s;
  }
}

总结

  以上就是我们对链表关键操作代码段的讲解。如果你可以完全掌握上面的代码段,那么你就可以利用这些数据结构算法来解决关于链表这一类的相关问题了,并且如果你能够在一些其他类的程序设计问题中巧用链表,那么你就可以更快、更好、更方便的去解决你所要去解决的问题。

目录
相关文章
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
609 1
|
10月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
264 0
|
算法 网络安全 区块链
2023/11/10学习记录-C/C++对称分组加密DES
本文介绍了对称分组加密的常见算法(如DES、3DES、AES和国密SM4)及其应用场景,包括文件和视频加密、比特币私钥加密、消息和配置项加密及SSL通信加密。文章还详细展示了如何使用异或实现一个简易的对称加密算法,并通过示例代码演示了DES算法在ECB和CBC模式下的加密和解密过程,以及如何封装DES实现CBC和ECB的PKCS7Padding分块填充。
399 4
2023/11/10学习记录-C/C++对称分组加密DES
|
C++ 开发者
C++学习之继承
通过继承,C++可以实现代码重用、扩展类的功能并支持多态性。理解继承的类型、重写与重载、多重继承及其相关问题,对于掌握C++面向对象编程至关重要。希望本文能为您的C++学习和开发提供实用的指导。
235 16
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
332 4
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
616 1
|
编译器 C语言 C++
配置C++的学习环境
【10月更文挑战第18天】如果想要学习C++语言,那就需要配置必要的环境和相关的软件,才可以帮助自己更好的掌握语法知识。 一、本地环境设置 如果您想要设置 C++ 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C++ 编译器。 二、文本编辑器 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。 C++ 程序的源文件通常使用扩展名 .cpp、.cp 或 .c。 在开始编程之前,请确保您有一个文本编辑器,且有足够的经验来编写一个计算机程序,然后把它保存在一个文件中,编译并执行它。 Visual Studio Code:虽然它是一个通用的文本编辑器,但它有很多插
613 6
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
224 1
|
Java 编译器 C++
c++学习,和友元函数
本文讨论了C++中的友元函数、继承规则、运算符重载以及内存管理的重要性,并提到了指针在C++中的强大功能和使用时需要注意的问题。
217 1
|
存储 算法 索引
HashMap底层数据结构及其增put删remove查get方法的代码实现原理
HashMap 是基于数组 + 链表 + 红黑树实现的高效键值对存储结构。默认初始容量为16,负载因子为0.75。当存储元素超过容量 * 负载因子时,会进行扩容。HashMap 使用哈希算法计算键的索引位置,通过链表或红黑树解决哈希冲突,确保高效存取。插入、获取和删除操作的时间复杂度接近 O(1)。
410 0