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

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

前言

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

我们要思考这样一个问题

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;
}


相关文章
|
25天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
38 1
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
92 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
26天前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
26天前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
1月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
99 23
|
1月前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
60 20
|
1月前
|
机器学习/深度学习 算法 C++
数据结构之鲸鱼算法
鲸鱼算法(Whale Optimization Algorithm,WOA)是由伊朗研究员Seyedali Mirjalili于2016年提出的一种基于群体智能的全局优化算法,灵感源自鲸鱼捕食时的群体协作行为。该算法通过模拟鲸鱼的围捕猎物和喷出气泡网的行为,结合全局搜索和局部搜索策略,有效解决了复杂问题的优化需求。其应用广泛,涵盖函数优化、机器学习、图像处理等领域。鲸鱼算法以其简单直观的特点,成为初学者友好型的优化工具,但同时也存在参数敏感、可能陷入局部最优等问题。提供的C++代码示例展示了算法的基本实现和运行过程。
51 0
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
1月前
|
算法 vr&ar 计算机视觉
数据结构之洪水填充算法(DFS)
洪水填充算法是一种基于深度优先搜索(DFS)的图像处理技术,主要用于区域填充和图像分割。通过递归或栈的方式探索图像中的连通区域并进行颜色替换。本文介绍了算法的基本原理、数据结构设计(如链表和栈)、核心代码实现及应用实例,展示了算法在图像编辑等领域的高效性和灵活性。同时,文中也讨论了算法的优缺点,如实现简单但可能存在堆栈溢出的风险等。
43 0