数据结构----链表头插中插尾插

news2024/12/20 19:56:27

一、链表的基本概念

链表是一种线性数据结构,它由一系列节点组成。每个节点包含两个主要部分:

  1. 数据域:用于存储数据元素,可以是任何类型的数据,如整数、字符、结构体等。
  2. 指针域:用于存储下一个节点(在单链表中)或前一个节点与下一个节点(在双链表中)的地址。

与数组不同,链表中的节点在内存中不是连续存储的。它们通过指针链接在一起,形成一个链状结构。这种非连续存储方式使得链表在插入和删除操作上具有一定的优势。

二、单链表

  1. 结构
    • 单链表节点的定义(以C语言为例):
typedef struct ListNode {
    int data;       // 数据域,可以根据需要存储不同类型的数据
    struct ListNode *next; // 指针域,指向下一个节点
} ListNode;
  • 这里,data用于存储数据,next是一个指向同类型结构体的指针,用于指向下一个节点。
  1. 基本操作
    • 创建节点
      • 函数实现:
ListNode* createNode(int value) {
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    if (newNode == NULL) {
        // 内存分配失败处理
        return NULL;
    }
    newNode->data = value;
    newNode->next = NULL;
    return newNode;
}
 - 解释:
   - 首先使用`malloc`函数分配足够的内存来存储一个`ListNode`结构体。如果内存分配成功,将传入的值赋给新节点的数据域,并将指针域初始化为`NULL`,表示这是一个孤立的节点,目前没有下一个节点。最后返回新创建的节点。
  • 插入节点
    • 头插法
      • 函数实现:
ListNode* insertAtHead(ListNode* head, int value) {
    ListNode* newNode = createNode(value);
    newNode->next = head;
    return newNode;
}
   - 解释:
     - 首先调用`createNode`函数创建一个新节点。然后将新节点的`next`指针指向当前的头节点(如果`head`为`NULL`,则新节点成为唯一的节点)。最后,将新节点作为新的头节点返回。这意味着每次插入操作都会使新节点成为链表的第一个节点。
 - **尾插法**
   - 函数实现:
ListNode* insertAtTail(ListNode* head, int value) {
    ListNode* newNode = createNode(value);
    if (head == NULL) {
        return newNode;
    }
    ListNode* current = head;
    while (current->next!= NULL) {
        current = current->next;
    }
    current->next = newNode;
    return head;
}
   - 解释:
     - 同样先创建新节点。如果链表为空(`head`为`NULL`),则直接返回新节点作为头节点。如果链表不为空,通过一个循环找到链表的最后一个节点(即当前节点的`next`指针为`NULL`的节点)。找到后,将最后一个节点的`next`指针指向新节点,从而将新节点插入到链表的尾部。最后返回原头节点,因为尾插法不会改变头节点。
 - **中插法(在指定位置插入)**
   - 函数实现:
ListNode* insertAtPosition(ListNode* head, int value, int position) {
    if (position == 0) {
        return insertAtHead(head, value);
    }
    ListNode* newNode = createNode(value);
    ListNode* current = head;
    for (int i = 0; i < position - 1 && current!= NULL; i++) {
        current = current->next;
    }
    if (current == NULL) {
        return head;
    }
    newNode->next = current->next;
    current->next = newNode;
    return head;
}
   - 解释:
     - 首先判断要插入的位置是否为0,如果是,则调用头插法函数进行插入。如果不是0,先创建新节点。然后通过一个循环找到要插入位置的前一个节点(循环条件确保不会超出链表长度且能找到正确位置)。如果在遍历过程中发现当前节点为`NULL`,说明已经到达链表末尾但还没找到插入位置,此时直接返回原链表。找到插入位置的前一个节点后,将新节点的`next`指针指向当前节点的下一个节点,再将当前节点的`next`指针指向新节点,完成插入操作。最后返回原头节点。
  • 删除节点
    • 函数实现:
ListNode* deleteNode(ListNode* head, int target) {
    if (head == NULL) {
        return NULL;
    }
    if (head->data == target) {
        ListNode* temp = head;
        head = head->next;
        free(temp);
        return head;
    }
    ListNode* current = head;
    while (current->next!= NULL && current->next->data!= target) {
        current = current->next;
    }
    if (current->next!= NULL) {
        ListNode* temp = current->next;
        current->next = current->next->next;
        free(temp);
    }
    return head;
}
 - 解释:
   - 首先判断链表是否为空,如果为空则无法删除,直接返回`NULL`。如果头节点的数据就是要删除的数据,那么将头节点的下一个节点作为新的头节点,并释放原来的头节点,然后返回新头节点。如果头节点不是要删除的节点,通过循环找到要删除节点的前一个节点(通过判断当前节点的下一个节点的数据是否为目标数据)。找到后,将前一个节点的`next`指针指向要删除节点的下一个节点,然后释放要删除的节点,最后返回原头节点。
  • 遍历链表
    • 函数实现:
void traverseList(ListNode* head) {
    ListNode* current = head;
    while (current!= NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}
 - 解释:
   - 从链表的头节点开始,通过循环依次访问每个节点的数据域,并将其打印出来。每次循环将当前节点更新为下一个节点,直到当前节点为`NULL`,表示已经遍历完整个链表。

在这里插入图片描述

三、双链表

  1. 结构
    • 双链表节点的定义(以C语言为例):
typedef struct DoubleListNode {
    int data;
    struct DoubleListNode *prev;
    struct DoubleListNode *next;
} DoubleListNode;
  • 这里,data用于存储数据,prev是指向前一个节点的指针,next是指向后一个节点的指针。
  1. 基本操作
    • 创建节点
      • 函数实现:
DoubleListNode* createDoubleNode(int value) {
    DoubleListNode* newNode = (DoubleListNode*)malloc(sizeof(DoubleListNode));
    if (newNode == NULL) {
        return NULL;
    }
    newNode->data = value;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}
 - 解释:
   - 与单链表创建节点类似,使用`malloc`分配内存。成功分配后,将数据赋给新节点的数据域,并将`prev`和`next`指针都初始化为`NULL`,因为新创建的节点暂时没有前后连接的节点。最后返回新节点。
  • 插入节点
    • 例如,在双链表头部插入节点:
      • 函数实现:
DoubleListNode* insertAtHeadDouble(DoubleListNode* head, int value) {
    DoubleListNode* newNode = createDoubleNode(value);
    if (head == NULL) {
        return newNode;
    }
    newNode->next = head;
    head->prev = newNode;
    return newNode;
}
   - 解释:
     - 先创建新节点。如果链表为空,直接返回新节点作为头节点。如果链表不为空,将新节点的`next`指针指向当前头节点,同时将当前头节点的`prev`指针指向新节点,最后将新节点作为新的头节点返回。
  • 删除节点
    • 例如,删除双链表中值为target的节点:
      • 函数实现:
DoubleListNode* deleteDoubleNode(DoubleListNode* head, int target) {
    if (head == NULL) {
        return NULL;
    }
    if (head->data == target) {
        DoubleListNode* temp = head;
        if (head->next!= NULL) {
            head->next->prev = NULL;
        }
        head = head->next;
        free(temp);
        return head;
    }
    DoubleListNode* current = head;
    while (current->next!= NULL && current->next->data!= target) {
        current = current->next;
    }
    if (current->next!= NULL) {
        DoubleListNode* temp = current->next;
        if (temp->next!= NULL) {
            temp->next->prev = current;
        }
        current->next = temp->next;
        free(temp);
    }
    return head;
}
   - 解释:
     - 首先判断链表是否为空,为空则无法删除,返回`NULL`。如果头节点的数据是要删除的数据,判断头节点的下一个节点是否存在,如果存在则将其`prev`指针置为`NULL`,然后将头节点更新为下一个节点,并释放原来的头节点,最后返回新头节点。如果头节点不是要删除的节点,通过循环找到要删除节点的前一个节点(通过判断当前节点的下一个节点的数据是否为目标数据)。找到后,如果要删除的节点的下一个节点存在,则将其下一个节点的`prev`指针指向当前节点,然后将当前节点的`next`指针指向要删除节点的下一个节点,最后释放要删除的节点,返回原头节点。
  • 遍历链表
    • 例如,从头部开始向前遍历:
      • 函数实现:
void traverseDoubleListForward(DoubleListNode* head) {
    DoubleListNode* current = head;
    while (current!= NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}
   - 解释:
     - 与单链表的遍历类似,从头节点开始,通过循环依次访问每个节点的数据域并打印,每次将当前节点更新为下一个节点,直到当前节点为`NULL`。双链表还可以从尾部开始向后遍历,原理类似,只是使用`prev`指针。

四、循环链表

  1. 结构

    • 循环链表分为循环单链表和循环双链表。
    • 循环单链表是最后一个节点的next指针指向头节点,形成一个环。
    • 循环双链表是最后一个节点的next指针指向头节点,头节点的prev指针指向最后一个节点,形成一个双向的环。
  2. 基本操作

    • 创建循环单链表
      • 例如,通过尾插法创建循环单链表:
        • 函数实现:
ListNode* createCircularList(int values[], int size) {
    if (size == 0) {
        return NULL;
    }
    ListNode* head = createNode(values[0]);
    ListNode* current = head;
    for (int i = 1; i < size; i++) {
        ListNode* newNode = createNode(values[i]);
        current->next = newNode;
        current = newNode;
    }
    current->next = head;
    return head;
}
   - 解释:
     - 首先判断数组大小是否为0,如果是则返回`NULL`。如果不为0,先创建头节点,然后通过循环依次创建新节点并将其插入到链表尾部。最后将最后一个节点的`next`指针指向头节点,形成循环链表,并返回头节点。
  • 遍历循环单链表
    • 例如:
      • 函数实现:
void traverseCircularList(ListNode* head) {
    if (head == NULL) {
        return;
    }
    ListNode* current = head;
    do {
        printf("%d ", current->data);
        current = current->next;
    } while (current!= head);
    printf("\n");
}
   - 解释:
     - 首先判断链表是否为空,如果为空则直接返回。如果不为空,从头节点开始遍历,通过`do - while`循环依次访问每个节点的数据域并打印。循环条件是当前节点不等于头节点,因为是循环链表,当再次回到头节点时表示遍历结束。最后换行。

循环链表在一些特定的应用场景中非常有用,例如操作系统中的资源分配循环队列等。双链表在需要双向访问数据的场景下有优势,而单链表则在空间利用和简单操作场景下较为常用。在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2262883.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Service Discovery in Microservices 客户端/服务端服务发现

原文链接 Client Side Service Discovery in Microservices - GeeksforGeeks 原文链接 Server Side Service Discovery in Microservices - GeeksforGeeks 目录 服务发现介绍 Server-Side 服务发现 实例&#xff1a; Client-Side 服务发现 实例&#xff1a; 服务发现介绍…

Git连接远程仓库(超详细)

目录 一、Gitee 远程仓库连接 1. HTTPS 方式 2. SSH公钥方式 &#xff08;1&#xff09;账户公钥 &#xff08;2&#xff09;仓库公钥 仓库的 SSH Key 和账户 SSH Key 的区别&#xff1f;​ 二、GitHub远程仓库连接 1. HTTPS方式 2.SSH公钥方式 本文将介绍如何通过 H…

系列4:基于Centos-8.6 Kubernetes多网卡节点Calico选择网卡配置

每日禅语 不动心”是一个人修养和定力的体现&#xff0c;若一个人心无定力&#xff0c;就会被外界环境左右&#xff0c;随外界的境遇而动摇。佛家认为&#xff0c;心是一切的基础&#xff0c;一个人如果想要真正入定&#xff0c;必须先从修心开始。修心即是净心&#xff0c;心灵…

Docker:Dockerfile(补充四)

这里写目录标题 1. Dockerfile常见指令1.1 DockerFile例子 2. 一些其他命令 1. Dockerfile常见指令 简单的dockerFile文件 FROM openjdk:17LABEL authorleifengyangCOPY app.jar /app.jarEXPOSE 8080ENTRYPOINT ["java","-jar","/app.jar"]# 使…

98. 验证二叉搜索树(java)

题目描述&#xff1a; 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 …

微软 Phi-4:小型模型的推理能力大突破

在人工智能领域&#xff0c;语言模型的发展日新月异。微软作为行业的重要参与者&#xff0c;一直致力于推动语言模型技术的进步。近日&#xff0c;微软推出了最新的小型语言模型 Phi-4&#xff0c;这款模型以其卓越的复杂推理能力和在数学领域的出色表现&#xff0c;引起了广泛…

libaom 源码分析:熵编码模块介绍

AV1 熵编码原理介绍 关于AV1 熵编码原理介绍可以参考:AV1 编码标准熵编码技术概述libaom 熵编码相关源码介绍 函数流程图 核心函数介绍 av1_pack_bitstream 函数:该函数负责将编码后的数据打包成符合 AV1 标准的比特流格式;包括写入序列头 OBU 的函数 av1_write_obu_header…

JAVA基于百度AI人脸识别签到考勤系统(开题报告+作品+论文)

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育、辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…

go 中使用redis 基础用法

1、安装redis 参考链接&#xff1a;https://www.codeleading.com/article/98554130215/ 1.1 查看是否有redis yum 源 yum install redis没有可用的软件包&#xff0c;执行1.2 1.2下载fedora的epel仓库 yum install epel-release --下载fedora的epel仓库1.3启动redis s…

postman添加cookie

点击cookies 输入域名&#xff0c;添加该域名下的cookies 发送改域名下的请求&#xff0c;cookie会自动追加上

简易记事本开发-(SSM+Vue)

目录 前言 一、项目需求分析 二、项目环境搭建 1.创建MavenWeb项目&#xff1a; 2.配置 Spring、SpringMVC 和 MyBatis SpringMVC 配置文件 (spring-mvc.xml)&#xff1a; 配置视图解析器、处理器映射器&#xff0c;配置了CORS&#xff08;跨源资源共享&#xff09;&#x…

vsCode 报错[vue/no-v-model-argument]e‘v-model‘ directives require no argument

在vue3中使用ui库中的组件语法v-model:value时会提示[vue/no-multiple-template-root]The template root requires exactly one element. 引入组件使用单标签时会提示[vue/no-multiple-template-root]“The template root requires exactly one element. 原因&#xff1a; 1.可…

初学stm32 -- SysTick定时器

以delay延时函数来介绍SysTick定时器的配置与使用 首先是delay_init()延时初始化函数&#xff0c;这个函数主要是去初始化SysTick定时器&#xff1b; void delay_init() {SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8fac_usSystemCoreCloc…

Gitlab 数据备份全攻略:命令、方法与注意事项

文章目录 1、备份命令2、备份目录名称说明3、手工备份配置文件3.1 备份配置文件3.2 备份ssh文件 4、备份注意事项4.1 停止puma和sicdekiq组件4.2 copy策略需要更多磁盘空间 5、数据备份方法5.1 docker命令备份5.2 kubectl命令备份5.3 参数说明5.4、选择性备份5.5、非tar备份5.6…

selenium工作原理

原文链接&#xff1a;https://blog.csdn.net/weixin_67603503/article/details/143226557 启动浏览器和绑定端口 当你创建一个 WebDriver 实例&#xff08;如 webdriver.Chrome()&#xff09;时&#xff0c;Selenium 会启动一个新的浏览器实例&#xff0c;并为其分配一个特定的…

Docker--Docker Registry(镜像仓库)

什么是Docker Registry&#xff1f; 镜像仓库&#xff08;Docker Registry&#xff09;是Docker生态系统中用于存储、管理和分发Docker镜像的关键组件。 镜像仓库主要负责存储Docker镜像&#xff0c;这些镜像包含了应用程序及其相关的依赖项和配置&#xff0c;是构建和运行Doc…

OpenEuler Linux上怎么测试Nvidia显卡安装情况

当安装好显卡驱动后怎么样知道驱动程序安装好了,这里以T400 OpenEuler 正常情况下,我们只要看一下nvidia-smi 状态就可以确定他已经正常了 如图: 这里就已经确定是可以正常使用了,这里只是没有运行对应的程序,那接来下我们就写一个测试程序来测试一下:以下代码通过AI给出然后…

【python虚拟环境安装】linux centos 下的python虚拟环境配置

linux centos 下的python虚拟环境配置 在 CentOS 环境中处理 pip 安装警告的方法1. 创建并使用虚拟环境2. 忽略警告并继续使用 root 用户安装&#xff08;不推荐&#xff09;报错问题处理 在 CentOS 环境中处理 pip 安装警告的方法 当在 CentOS 环境中遇到 pip 安装警告时&…

Excel根据身份证号,计算退休日期和剩余天数!

大家好&#xff0c;我是小鱼。 日常工作中&#xff0c;有时我们需要使用Excel表格统计男女员工退休日期或者退休剩余天数&#xff0c;很多新手小伙伴可能不知道如何下手。今天就跟大家分享一下WPS中的Excel表格数据如果根据身份证号&#xff0c;自动批量计算退休日期和剩余天数…

排序算法(3)——归并排序、计数排序

目录 1. 归并排序 1.1 递归实现 1.2 非递归实现 1.3 归并排序特性总结 2. 计数排序 代码实现 3. 总结 1. 归并排序 基本思想&#xff1a; 归并排序&#xff08;merge sort&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&#xff0…