【数据结构】链表的学习和介绍

简介: 【数据结构】链表的学习和介绍

前言

今天,我们来学习,数据结构中的链表

链表是什么

链表,就是多个结构体变量之间,通过结构体指针连接在一起的一种数据结构

提示:

本篇文章主要讲解动态链表,对于静态链表不做过多介绍

链表的分类

链表可分为静态链表动态链表

静态链表

只是初步了解,更详细的操作(比如插入节点、删除节点)在这篇文章中不做说明

静态链表,实际上就是一个结构体数组

分配一整片连续的内存空间,各个结构体变量集中安置,逻辑结构上相邻的数据元素,存储在指定的一块内存空间中,数据元素只允许在这块内存空间中随机存放,这样的存储结构生成的链表称为静态链表。也就是说静态链表是用数组来实现链式存储结构

下面,给出一段例子

这就是静态链表的定义和初始化

#include<iostream>
using namespace std;
struct Node
{
  int data;//数据域
  int next;//指针域
  //注释1
  //静态链表的节点是索引值 不是结构体指针
};
int main()
{
  struct Node n[10];
  for (int i = 0; i < 10; i++)
  {
    n[i].data = i;
    n[i].next = i + 1;//每个节点的下一个节点位当前索引值+1
  }
  n[9].next = -1;
  //注释2
  return 0;
}

注释1:

相信大家都知道,链表这种数据结构,前面一部分存储的是数据,后面一部分存储的是指向下一个变量(一般称为节点)的指针

存储数据的区域,就称为数据域

存储指针的区域,就称为指针域

注释2:

n[9].next = -1;

这行代码的意思是:将这个链表的最后一个节点 的指针(索引值)赋为-1

为什么要这么做呢?

首先:

当我们已经遍历到最后一个节点时,没有下一个节点了,那么此时的next指针指向的就是一个随机值,这可能会导致发生不可预期的行为,比如程序崩溃

其次,将最后一个节点的指针赋为-1,有便于我们检查对于链表的遍历是否结束,这一点在后面会详细说明

最后,当我们在处理链表的最后一个字节时,可以通过-1来直接找到它

静态链表的特点

静态链表需要预先开辟一块空间来存储,并且,静态链表的大小是固定的,(毕竟它没用到动态内存分配 没法变大),以及,静态链表在存储时,在内存中是连续存储的

静态链表也有一些好处,比如:

静态链表可以直接通过索引访问,不需要指针(好像这二者差不多)

动态链表

接下来,就进入到我们这篇文章的重头戏:动态链表了

要实现动态链表,有两个东西比较重要:

动态内存的申请,是动态链表之所以被称为动态链表的原因

模块化设计,可以使代码可读性和简洁性提升(当然,都写在一起也不是不行…)

下文为了便于理解,对于数据域只使用一个int类型的变量

动态链表的实现思路

本篇文章将动态链表的实现分为五个步骤,

1.创建链表表头
2.创建节点
3.插入节点
4.删除节点
5.打印链表

前四步比较关键,最后一步一般用于测试自己创建的链表是否符合自己的预期

创建链表表头

要创建链表,我们首先需要创建一个表头,来指向整个链表,可以将表头理解为第一个节点,

想指向一个结构体变量(也就是节点),那表头应该是结构体指针c

问题来了,如何把一个结构体指针,变成结构体变量呢

这时候,我们就要学习动态内存申请,即malloc函数

(注意:使用new也可以,会在本节的末尾附上使用new创建动态链表的代码)

给出一个示例来说明如何创建头节点

struct Node* createList()
{
  struct Node* headNode = (struct Node*)malloc
  (sizeof(struct Node));
  //
  //用malloc申请内存
  headNode->next = nullptr;
  //注释3
  return headNode;
}

注释3

有的同学可能会有疑问,为什么要将头节点初始化为空指针

首先,现在链表里只有一个变量(节点),不初始化成空指针,那不就变成野指针了

其次,初始化成空指针的话,当我们后续无论是查找头节点还是继续插入新的节点的时候,都是能很轻松的找到头节点

创建节点(一个)
/*

创建节点的这个函数

/*/

下面给出一个例子:

将创建节点的内容模块化城createNode函数

struct Node* createNode(int data)
{
  struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
  newNode->data = data;
  //传入数据
  newNode->next = nullptr;
  //注释4
  return newNode;
}

注释4:

类似的,在创建新的节点时,将next赋值为空指针,

是因为这个新节点并不知道链表中下一个节点是什么,所以只能赋值为空指针

插入节点

对于插入,有三种方式:头插法,尾插法,指定位置插入

下面给出一个使用头插法例子:

将创建节点的内容模块化城insertNodeByHead函数

(代码中给出了相应的注释)

void insertNodeByHead(struct Node* headNode, int data)
{
  struct Node* newNode = createNode(data);
  //创建插入的节点
  newNode->next = headNode->next;
  //修改指针域
  headNode->next = newNode;
  //指向要插入的数据
}

注释5:

在编写创建节点和插入节点的时候,我感觉这两步有一些联系,在此说明一下:

这两个步骤通常是相互关联的,因为通常我们需要先创建一个新的节点,然后将其插入到链表中

删除节点

下面给出一个例子:

将创建节点的内容模块化城deleteNodeByAppoin函数

(代码中给出了相应的注释)

//删除节点
void deleteNodeByAppoin(struct Node* headNode, int posData)
//headNode只是一个传入的参数名,不是真的头节点 不要弄混
//posData是要删除的数据
{
  struct Node* posNode = headNode->next;
  struct Node* posNodeFront = headNode;
  //分别对于链表是否为空和posData是否存在进行判断
  if (posNode == nullptr)
  {
    cout << "链表为空 操作错误" << endl;
  }
  else
  {
    while (posNode->data != posData)
    //遍历查找
    {
      posNodeFront = posNode;
      posNode = posNodeFront->next;
      if (posNode == nullptr)
      {
        cout << "未找到要删除的数据" << endl;
        return;
      }
    }
    posNodeFront->next = posNode->next;
    //用free函数释放内存
    free(posNode);
  }
}
遍历打印链表

通过遍历打印链表

void PrintList(struct Node* headNode)
{
  struct Node* pMove = headNode->next;
  while (pMove)
  //当pmove不为空指针时 持续遍历
  //这里就可以联系到创建节点时,为什么要将next初始化为空指针
  //巧妙 妙
  //while这里还能这么写 学到了 学到了
  {
    cout << pMove->data;
    pMove = pMove->next;
  }
  cout << endl;
}
主函数
int main()
{
  struct Node* list = createList();
  insertNodeByHead(list, 1);
  insertNodeByHead(list, 2);
  insertNodeByHead(list, 3);
  PrintList(list);
  return 0;
}

运行结果:

注释6:

因为用的是头插法,所以先插入的变量在遍历输出的时候,在后面

完整代码
#include<iostream>
using namespace std;
struct Node
{
  int data;
  struct Node* next;//静态链表的节点是索引值 不是结构体指针
};
//创建表头
struct Node* createList()
{
  struct Node* headNode = (struct Node*)malloc
  (sizeof(struct Node));
  //用malloc申请内存
  headNode->next = nullptr;
  //注释3
  return headNode;
}
//创建节点(一个)
struct Node* createNode(int data)
{
  struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
  newNode->data = data;
  //传入数据
  newNode->next = nullptr;
  return newNode;
}
//插入节点
void insertNodeByHead(struct Node* headNode, int data)
{
  struct Node* newNode = createNode(data);
  //创建插入的节点
  newNode->next = headNode->next;
  //修改指针域
  headNode->next = newNode;
  //指向要插入的数据
}
//删除节点
void deleteNodeByAppoin(struct Node* headNode, int posData)
//headNode只是一个传入的参数名,不是真的头节点 不要弄混
//posData是要删除的数据
{
  struct Node* posNode = headNode->next;
  struct Node* posNodeFront = headNode;
  //分别对于链表是否为空和posData是否存在进行判断
  if (posNode == nullptr)
  {
    cout << "链表为空 操作错误" << endl;
  }
  else
  {
    while (posNode->data != posData)
    {
      posNodeFront = posNode;
      posNode = posNodeFront->next;
      if (posNode == nullptr)
      {
        cout << "未找到要删除的数据" << endl;
        return;
      }
    }
    posNodeFront->next = posNode->next;
    //用free函数释放内存
    free(posNode);
  }
}
//遍历打印链表
void PrintList(struct Node* headNode)
{
  struct Node* pMove = headNode->next;
  while (pMove)
  //当pmove不为空指针时 持续遍历
  //这里就可以联系到创建节点时,为什么要将next初始化为空指针
  //巧妙 妙
  //while这里还能这么写 学到了 学到了
  {
    cout << pMove->data;
    pMove = pMove->next;
  }
  cout << endl;
}
int main()
{
  struct Node* list = createList();
  insertNodeByHead(list, 1);
  insertNodeByHead(list, 2);
  insertNodeByHead(list, 3);
  PrintList(list);
  return 0;
}
使用new

使用new的话,只需要在创建节点和插入节点那里修改一下就可以了

Node* createList()  
{  
 Node* headNode = new Node;  
 headNode->next = nullptr;  
 return headNode;  
}  
Node* createNode(int data)  
{  
 Node* newNode = new Node;  
 newNode->data = data;  
 newNode->next = nullptr;  
 return newNode;  
}  
实际项目中可能会遇到的小问题

在编写如通讯录、图书馆信息管理系统的时候,data一般都是结构体,那么在输入的时候,就可能会遇到输入完数字,再输入字符串,

那么就存在需要清空缓冲区的问题

此时,我们可以用setbuf函数

使用格式如下:

setbuf(stdin,NULL);清空缓冲区函数

(在此只是介绍一下,作者也不会)

结语

算是初步把链表中的静态和动态链表了解和学习了一下,希望这篇文章对你有帮助,我们下篇文章见~~



相关文章
|
2天前
|
存储
数据结构第二课 -----线性表之单向链表
数据结构第二课 -----线性表之单向链表
|
2天前
|
存储 算法 Java
数据结构与算法 数组和链表
数据结构与算法 数组和链表
11 0
|
2天前
|
存储 Java
深入浅出数据结构之链表
深入浅出数据结构之链表
|
2天前
|
C++
数据结构(双链表
数据结构(双链表
9 1
|
2天前
|
存储 索引 Python
python数据结构知识学习
【5月更文挑战第6天】Python提供四种核心数据结构:列表(List)——可变有序集合,支持索引和切片;元组(Tuple)——不可变有序集合;字典(Dictionary)——键值对结构,通过键访问值;集合(Set)——无序不重复元素集合,支持数学运算。此外,Python允许自定义数据结构,如链表、树、图,以适应不同问题需求。
19 0
|
2天前
|
存储 缓存
[数据结构]~双向+循环链表从(0~1)
[数据结构]~双向+循环链表从(0~1)
|
2天前
|
存储
数据结构第三课 -----线性表之双向链表
数据结构第三课 -----线性表之双向链表
|
2天前
|
存储 Java
数据结构奇妙旅程之顺序表和链表
数据结构奇妙旅程之顺序表和链表
|
2天前
|
存储 C语言
数据结构基础:双链表结构、实现
数据结构基础:双链表结构、实现
|
2天前
|
存储
数据结构基础:一篇文章教你单链表(头插,尾插,查找,头删等的解析和代码)
数据结构基础:一篇文章教你单链表(头插,尾插,查找,头删等的解析和代码)