数据结构——(单)链表

news2024/11/28 2:52:31

文章目录

1. 结构

2. 链表的分类

1. 单链表

2. 双链表

3. 循环单链表

4. 循环双链表

3. 优缺点

4. 单链表函数

5. 单链表代码实现


1. 结构

逻辑结构

链表是一种线性结构,由一系列结点(Node)组成。每个结点包含一个数据元素和一个指向下一个结点的指针(Pointer)。所有结点通过指针相连,形成一个链式结构。通常,我们将指向链表中第一个结点的指针称为“头结点”。

  • data:存放结点的数据值。
  • next:存放结点的直接后继的地址(指针)。

物理结构

与数组不同,链表中的结点需要自行组织,向系统申请很多分散在内存各处的结点。每个结点都保存在当前结点的数据和下一个结点的地址(指针)中,通过指针将结点串成一链。

  • 逻辑结构:展示了结点之间通过指针连接的链表结构。
  • 物理结构:展示了结点在内存中的分布和通过指针相互连接的方式。

2. 链表的分类

链表分为单链表、双链表、循环单链表、循环双链表和静态链表。

1. 单链表

单链表是一种线性数据结构,每个结点包含一个数据元素和一个指向下一个结点的指针。单链表的特点是每个结点只有一个前驱结点和一个后继结点,头结点没有前驱结点,尾结点没有后继结点。

结构

  • 每个结点包含两个部分:数据域和指针域。
  • 指针域存放着下一个结点的地址。

示例

head -> [data|next] -> [data|next] -> [data|next] -> NULL

2. 双链表

双链表是一种每个结点包含两个指针的链表结构,一个指向前驱结点,另一个指向后继结点。双链表的特点是可以在链表中双向遍历,既可以从头结点遍历到尾结点,也可以从尾结点遍历到头结点。

结构

  • 每个结点包含三个部分:数据域、前驱指针域和后继指针域。
  • 前驱指针域存放着前一个结点的地址,后继指针域存放着后一个结点的地址。

示例

NULL <- [prev|data|next] <-> [prev|data|next] <-> [prev|data|next] -> NULL
3. 循环单链表

循环单链表是一种特殊的单链表,其中尾结点的指针指向头结点,形成一个循环。循环单链表的特点是链表中的任何一个结点都可以作为起点进行遍历,最终会回到该结点。

结构

  • 每个结点包含两个部分:数据域和指针域。
  • 尾结点的指针域指向头结点。

示例

head -> [data|next] -> [data|next] -> [data|next] --|
|--------------------------------------------------|
4. 循环双链表

循环双链表是一种特殊的双链表,其中尾结点的后继指针指向头结点,头结点的前驱指针指向尾结点,形成一个双向循环。循环双链表的特点是可以从链表中的任何一个结点进行双向遍历,最终会回到该结点。

结构

  • 每个结点包含三个部分:数据域、前驱指针域和后继指针域。
  • 尾结点的后继指针指向头结点,头结点的前驱指针指向尾结点。

示例

head <-> [prev|data|next] <-> [prev|data|next] <-> [prev|data|next] <-> head

 

结点离散分配与指针连接

链表中的结点是离散分配的,彼此通过指针相连。每个结点只有一个前驱结点和一个后继结点。确定一个链表只需要头指针,通过头指针可以遍历整个链表。

3. 优缺点

优点

  • 插入和删除操作效率高

    • 链表的插入和删除操作非常高效,因为只需要修改相关结点的指针,不需要像数组那样移动大量的元素。因此,链表在频繁的插入和删除操作场景中具有明显的优势。
  • 动态扩展性能更好

    • 链表不需要像数组那样预先指定固定的大小,而是可以随时动态的增加或缩小。链表是真正的动态数据结构,不需要处理固定容量的问题。当需要增加新的元素时,只需要分配新的结点并调整指针即可。

缺点

  • 查找慢

    • 由于链表中的结点不是连续存储的,无法像数组一样根据索引直接计算出每个结点的地址。必须从头结点开始遍历链表,直到找到目标结点,这导致了链表的随机访问效率较低。在链表中查找某个特定元素需要遍历链表,时间复杂度为 O(n)。
  • 额外的存储空间

    • 链表的每个结点都需要存储指向下一个结点的指针,这会占用额外的存储空间。因此,相比于数组,链表需要更多的内存空间来存储相同数量的数据元素。对于每个结点,需要额外存储指针的空间,这在内存使用方面可能会造成一定的开销。

4. 单链表函数

单链表的特性

  1. 节点的定义:每个节点包含数据部分和指向下一个节点的指针部分。
  2. 头节点:链表的头节点是链表的起点,通过头节点可以遍历整个链表。
  3. 末尾节点:链表的末尾节点指向 NULL,表示链表的结束。
  4. 动态扩展:节点在内存中不必是连续的,可以动态分配和释放内存。

为了实现链表的操作,需要实现以下函数:

  • 初始化链表

    void initLinkedList(LinkedList *list);
    
  • 返回链表的长度:返回链表中元素的个数。

    size_t getLength(const LinkedList *list);
    
  • 在指定位置插入元素:在链表的指定位置插入一个元素。

    void insertAt(LinkedList *list, size_t index, int element);
    
  • 在末尾插入元素

    void insertEnd(LinkedList *list, int element);
    
  • 删除指定位置的元素并返回被删除的元素

    int deleteAt(LinkedList *list, size_t index);
    
  • 删除末尾元素

    int deleteEnd(LinkedList *list);
    
  • 获取指定位置的元素:返回链表中指定位置的元素。

    int getElementAt(const LinkedList *list, size_t index);
    
  • 修改指定位置的元素

    void modifyAt(LinkedList *list, size_t index, int newValue);
    
  • 释放链表内存

void destroyLinkedList(LinkedList *list);

5. 单链表代码实现

代码定义并实现了单向链表的基本操作,包括初始化、插入、删除、获取和修改元素,以及释放链表的内存。

#include <stdio.h>
#include <stdlib.h>

// 链表节点结构定义
typedef struct Node {
    int data;           // 节点存储的数据
    struct Node *next;  // 指向下一个节点的指针
} Node;

// 链表结构定义
typedef struct {
    Node *head;  // 链表头节点指针
    size_t size; // 链表中的节点个数
} LinkedList;

// 初始化链表
void initLinkedList(LinkedList *list) {
    list->head = NULL; // 初始化头节点为空
    list->size = 0;    // 初始化节点个数为0
}

// 返回链表的长度
size_t getLength(const LinkedList *list) {
    return list->size; // 返回链表的节点个数
}

// 在指定位置插入元素
void insertAt(LinkedList *list, size_t index, int element) {
    // 检查插入位置是否有效
    if (index > list->size) {
        return; // 忽略无效的插入位置
    }

    // 创建新节点
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->data = element;

    // 如果插入位置是头节点
    if (index == 0) {
        newNode->next = list->head;
        list->head = newNode; // 新节点成为头节点
    } else {
        // 找到插入位置的前一个节点
        Node *prevNode = list->head;
        for (size_t i = 0; i < index - 1; i++) {
            prevNode = prevNode->next;
        }

        // 插入新节点
        newNode->next = prevNode->next;
        prevNode->next = newNode; // 新节点插入在前一节点之后
    }

    list->size++; // 更新节点个数
}

// 在末尾插入元素
void insertEnd(LinkedList *list, int element) {
    insertAt(list, list->size, element); // 在链表末尾插入元素
}

// 删除指定位置的元素并返回被删除的元素
int deleteAt(LinkedList *list, size_t index) {
    // 检查删除位置是否有效
    if (index >= list->size) {
        return -1; // 忽略无效的删除位置
    }

    int deletedElement;

    // 如果删除位置是头节点
    if (index == 0) {
        Node *temp = list->head;
        list->head = temp->next;
        deletedElement = temp->data;
        free(temp); // 释放被删除节点的内存
    } else {
        // 找到删除位置的前一个节点
        Node *prevNode = list->head;
        for (size_t i = 0; i < index - 1; i++) {
            prevNode = prevNode->next;
        }

        // 删除节点
        Node *temp = prevNode->next;
        prevNode->next = temp->next;
        deletedElement = temp->data;
        free(temp); // 释放被删除节点的内存
    }

    list->size--; // 更新节点个数
    return deletedElement;
}

// 删除末尾元素并返回被删除的元素
int deleteEnd(LinkedList *list) {
    return deleteAt(list, list->size - 1); // 删除链表末尾的元素
}

// 获取指定位置的元素
int getElementAt(const LinkedList *list, size_t index) {
    // 检查索引位置是否有效
    if (index >= list->size) {
        return -1; // 返回无效的索引
    }

    // 遍历找到指定位置的节点
    Node *currentNode = list->head;
    for (size_t i = 0; i < index; i++) {
        currentNode = currentNode->next;
    }

    return currentNode->data; // 返回指定位置的元素
}

// 修改指定位置的元素
void modifyAt(LinkedList *list, size_t index, int newValue) {
    // 检查修改位置是否有效
    if (index >= list->size) {
        return; // 忽略无效的修改位置
    }

    // 遍历找到指定位置的节点
    Node *currentNode = list->head;
    for (size_t i = 0; i < index; i++) {
        currentNode = currentNode->next;
    }

    currentNode->data = newValue; // 修改节点的值
}

// 释放链表内存
void destroyLinkedList(LinkedList *list) {
    // 遍历释放每个节点的内存
    Node *currentNode = list->head;
    while (currentNode != NULL) {
        Node *temp = currentNode;
        currentNode = currentNode->next;
        free(temp); // 释放每个节点的内存
    }

    list->head = NULL; // 头节点置为空
    list->size = 0;    // 节点个数置为0
}

// 主函数,测试链表操作
int main() {
    LinkedList myList; // 声明链表

    initLinkedList(&myList); // 初始化链表
    printf("初始化链表成功!\n");

    insertEnd(&myList, 1); // 链表尾部插入元素1
    insertEnd(&myList, 2); // 链表尾部插入元素2
    printf("向链表插入了2个元素\n");

    printf("链表长度为: %zu\n", getLength(&myList)); // 获取链表长度

    insertAt(&myList, 1, 3); // 在索引1处插入元素3
    printf("在索引1处插入元素3\n");

    printf("链表长度为: %zu\n", getLength(&myList)); // 再次获取链表长度

    printf("索引1处的元素为: %d\n", getElementAt(&myList, 1)); // 获取索引1处的元素

    modifyAt(&myList, 0, 4); // 修改索引0处的元素
    printf("把索引0处的元素修改为4\n");

    printf("删除索引1处的元素,该元素值是: %d\n", deleteAt(&myList, 1)); // 删除索引1处的元素

    destroyLinkedList(&myList); // 销毁链表
    printf("链表销毁成功!\n");

    return 0;
}

 

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

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

相关文章

Linux库概念及相关编程(动态库-静态库)

Linux库概念及相关编程 分文件编程案例 分文件编程是指将程序按功能模块划分成不同的文件进行编写&#xff0c;这种方法有以下好处&#xff1a; 功能责任划分&#xff1a;每个文件对应一个功能模块&#xff0c;职责明确&#xff0c;易于理解和维护。方便调试&#xff1a;可以…

绝地求生PUBG点击开始游戏一直在加载不读条计时间的解决办法

绝地求生PUBG作为一款引领潮流的大逃杀游戏&#xff0c;凭借其紧张刺激的对抗体验赢得了全球玩家的喜爱。 即使是游戏已经上线很长时间了&#xff0c;但是游戏现在依旧是很火爆&#xff0c;还有很多玩家下载游戏进行游玩。然而&#xff0c;一些为玩家在游戏中遇到了点击开始游戏…

java版本ERP管理系统源码 Spring Cloud ERP_ERP系统_erp软件_ERP管理系统

在当今数字化时代&#xff0c;企业对高效、稳定且易于扩展的管理系统的需求日益增长。为了满足这一需求&#xff0c;我们精心打造了一款基于Java技术的ERP&#xff08;Enterprise Resource Planning&#xff09;管理系统。该系统充分利用了Spring Cloud Alibaba、Spring Boot、…

Butterfly主题文章标题改成转动小风车

效果 标题级别不同小风车颜色不同&#xff0c;鼠标移入会有转动变慢及变色效果。 新建css 建议在/source下创建诸如img/css/js等文件夹&#xff0c;存放文章或网站用的素材&#xff0c;分门别类后续也方便维护。 Hexo打包的时候&#xff0c;会自动把/source下的文件&#…

JavaScript基础知识5(对象)

JavaScript基础知识5&#xff08;对象&#xff09; 对象创建对象使用对象字面量使用 new Object() 访问和修改属性点表示法方括号表示法 动态添加和删除属性添加属性删除属性 对象方法对象的遍历常用属性和方法数学常量数学函数三角函数 使用示例生成随机整数计算圆的面积求最大…

Zabbix 配置 VMware 监控

Zabbix监控VMware 官方文档&#xff1a;https://www.zabbix.com/documentation/current/en/manual/vm_monitoring Zabbix 可以使用低级发现规则自动发现 VMware 虚拟机管理程序和虚拟机&#xff0c;并根据预定义的主机原型创建主机来监控它们。Zabbix 还包括用于监控 VMware …

VirtualBox的windows server 2016设置主机和虚拟机共享文件夹

文章目录 安装步骤1. windows server 2016安装增强功能2.上述安装完成之后&#xff0c;需要做共享文件夹&#xff0c;在宿主机&#xff0c;新建一个test文件夹&#xff0c;做共享设置&#xff0c;如下图&#xff1a;3.然后打开虚拟机&#xff0c;设置文件共享 安装步骤 1. win…

字节码编程javassist之定义方法和返回值

写在前面 源码 。 本文看下如何使用javassist来定义方法和返回值。 1&#xff1a;源码 package com.dahuyou.javassist.generateFieldAndMethod;import javassist.*;import java.lang.reflect.Method;public class JustDoIt222 {public static void main(String[] args) thr…

跨平台Ribbon UI组件QtitanRibbon全新发布v6.7.0——支持Qt 6.6.3

没有Microsoft在其办公解决方案中提供的界面&#xff0c;就无法想象现代应用程序&#xff0c;这个概念称为Ribbon UI&#xff0c;目前它是使应用程序与时俱进的主要属性。QtitanRibbon是一款遵循Microsoft Ribbon UI Paradigm for Qt技术的Ribbon UI组件&#xff0c;QtitanRibb…

SOLIDWORKS分期许可(订阅形式),降低前期的投入成本!

SOLIDWORKS 分期许可使您能够降低前期软件成本&#xff0c;同时提供对 SOLIDWORKS 新版本和升级程序的即时访问&#xff0c;以及在每个期限结束时调整产品的灵活性&#xff0c;帮助您跟上市场需求和竞争压力的步伐。 目 录&#xff1a; ★ 1 什么是SOLIDWORKS分期许可 ★ 2 …

Cube-Studio:开源大模型全链路一站式中台

开源项目&#xff0c;欢迎star哦&#xff0c;https://github.com/data-infra/cube-studio 一款真正意义的 LLMOps 框架 LLMOps&#xff08;Large Language Model Operations&#xff09;是一个涵盖了大型语言模型&#xff08;如GPT系列&#xff09;开发、部署、维护和优化的一…

【EI会议/稳定检索】2024年应用数学、化学研究与物理工程国际会议(AMPE 2024)

2024 International Conference on Applied Mathematics, Chemical Research, and Physical Engineering 2024年应用数学、化学研究与物理工程国际会议(AMPE 2024) 【会议信息】 会议简称&#xff1a;AMPE 2024 大会时间&#xff1a;点击查看 截稿时间&#xff1a;官网查看 大…

pp 二 物料bom (CS01 CS02 CS03)

02&#xff1a;bom建了以后不能做生产&#xff0c;也不能下达计划 03&#xff1a;不能下达成本&#xff0c;下达订单 bom里面的存储地点高于物料主数据里面的存储地点&#xff08;mrp视图2&#xff09; 生产存储地点作为组件角度是一个发料得存储地点 但是作为一个成品则是成…

Restore Equipment

Restore Equipment 魔兽世界 - 盗号申请 - 恢复装备流程 魔兽和网易真的不行啊 1&#xff09;这个装备本来就是兑换的竟然可以卖NPC 2&#xff09;针对这个情况竟然无法挽回 3&#xff09;设计理念真的不得不吐槽一下 4&#xff09;策划真的不咋样&#xff0c;要是有机会我要自…

mssql查询历史执行过的语句日志

SELECT deqs.creation_time,dest.text AS [SQL Text],deqs.execution_count,deqs.total_elapsed_time,deqs.total_worker_time FROM sys.dm_exec_query_stats AS deqs CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest--where dest.text like %这个是我的条件&#…

数学建模----滑翔伞伞翼面积的设计及运动状态描述

摘要 滑翔伞作为一项融合了挑战、冒险和刺激于一体的运动&#xff0c;近年来在全球范围内受到了广泛的关注。滑翔伞在救援、探险、体育、娱乐、环保和交通等领域的应用展现了其重要价值。然而&#xff0c;中国在滑翔伞领域尚未取得突破&#xff0c;缺乏全球影响力和竞争力。因此…

Keepalived+LVS实现负责均衡,高可用的集群

Keepalived的设计目标是构建高可用的LVS负载均衡群集&#xff0c;可以调用ipvsadm工具来创建虚拟服务器&#xff0c;管理服务器池&#xff0c;而不仅仅用作双机热备。使用Keepalived构建LVS群集更加简便易用&#xff0c;主要优势体现在&#xff1a;对LVS负责调度器实现热备切换…

iPad电容笔什么牌子好?2024最值得买的五款高性价比电容笔推荐!

现在平板和电容笔在一定程度上可以替代传统的笔和纸&#xff0c;不仅减少纸张浪费&#xff0c;还可以导入教材和习题册。只需携带它们就无需携带厚重的书本&#xff0c;这种环保、便捷、方便的特点吸引了越来越多的用户。但电容笔品牌的不断涌现&#xff0c;也让用户更加难以抉…

零基础STM32单片机编程入门(七)定时器PWM波输出实战含源码视频

文章目录 一.概要二.PWM产生框架图三.CubeMX配置一个TIME输出1KHZ&#xff0c;占空比50%PWM波例程1.硬件准备2.创建工程3.测量波形结果 四.CubeMX工程源代码下载五.讲解视频链接地址六.小结 一.概要 脉冲宽度调制(PWM)&#xff0c;是英文“Pulse Width Modulation”的缩写&…

配置基于用户认证的虚拟主机

添加账号abc [rootlocalhost conf.d]# htpasswd -c /etc/httpd/zhanghao abc New password: Re-type new password: Adding password for user abc添加账号tom [rootlocalhost conf.d]# htpasswd /etc/httpd/zhanghao tom New password: Re-type new password: Adding pa…