[数据结构] - - - 链表

一、定义

链表:是一种常见的线性数据结构,它通过一组节点(Node)来存储数据,每个节点包含两部分:数据域和指针域。

在这里插入图片描述

1.1 链表的基本概念

  • 节点(Node):链表的最小单元,包含数据域(Data)与指针域(Pointer)两部分。

    • 数据域:用于存储数据,可以是任意类型(如整数、字符串、对象灯)。

    • 指针域:用于存储指向下一个节点的地址(或引用)。在某些链表(如双向链表)中,还可能包含指向前一个节点的地址。

  • 头节点(Head):链表的第一个节点,通常用一个指针(如head)来标识链表的起始位置。

  • 尾节点(Tail):链表的最后一个节点,其指针域通常指向null,表示链表的结束。

二、类型

链表可以分为以下几种类型:单链表、双向链表、循环链表、双向循环链表等。

2.1 单链表(Singly Linked List)

单链表是链表的一种最基本形式,它由一系列节点组成,每个节点包含两部分:数据域和指针域。单链表的特点是每个节点的指针域只指向下一个节点,形成一个线性的结构。

示例结构
在这里插入图片描述

特点

  • 只能从头节点开始向后遍历,不能反向遍历。
// 定义单链表的节点结构体
typedef struct Node {
    int data;               // 数据域
    struct Node* next;      // 指向下一个节点的指针
} Node;

2.2 双向链表(Doubly Linked List)

双向链表(Doubly Linked List)是一种更灵活的链表结构,与单链表相比,它在每个节点中增加了指向前一个节点的指针。

示例结构
在这里插入图片描述

特点

  • 1、双向遍历:可以从任意节点向前或向后遍历,操作更加灵活。

  • 2、插入和删除操作高效:

    • 插入和删除节点时,只需要修改相邻节点的指针,时间复杂度为O(1)。

    • 由于有前驱指针,插入和删除操作更加直观。

  • 3、存储开销较大:每个节点需要存储两个指针(prev和next),增加了存储空间的开销。

  • 4、实现复杂:操作需要同时处理两个指针,代码实现相对复杂。

// 定义双向链表的节点结构体
typedef struct Node {
    int data;               // 数据域
    struct Node* prev;      // 指向前一个节点的指针
    struct Node* next;      // 指向后一个节点的指针
} Node;

2.3 循环链表(Circular Linked Lis)

循环链表是一种特殊的链表结构,其特点是尾节点的指针指向头节点,形成一个环形结构。

示例结构
在这里插入图片描述

特点

  • 1、环形结构:

    • 尾节点的next指针指向头节点,而不是NULL。

    • 没有明显的“尾节点”,链表形成一个闭环。

  • 2、遍历特性:

    • 从任意节点出发,可以遍历整个链表。

    • 需要特别注意循环条件,避免无限循环。

  • 3、操作效率:

    • 插入和删除操作的时间复杂度为O(1),但需要特别处理循环特性。

    • 适合需要频繁遍历或操作的场景

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

2.4 循环双向链表

循环双向链表是一种更复杂的链表结构,它结合了双向链表和循环链表的特点。每个节点不仅包含指向下一个节点的指针,还包含指向前一个节点的指针,同时链表的尾节点的next指针指向头节点,头节点的prev指针指向尾节点,形成一个闭环。

示例结构
在这里插入图片描述

特点

  • 1、双向遍历:可以从任意节点向前或向后遍历。

  • 2、循环结构:尾节点的next指针指向头节点,头节点的prev指针指向尾节点。

  • 3、操作高效:插入和删除操作的时间复杂度为O(1),且操作更直观。

  • 4、灵活性高:适合需要频繁插入、删除以及双向遍历的场景。

  • 5、实现复杂:需要同时处理两个指针,并且在操作时需要特别注意循环条件。

// 定义循环双向链表的节点结构体
typedef struct Node {
    int data;                      // 数据域
    struct Node* prev;             // 指向前一个节点的指针
    struct Node* next;             // 指向后一个节点的指针
} Node;

三、存储方式

链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

在这里插入图片描述

这个链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。

四、操作

链表的操作主要包括插入、删除、查找、遍历等。

以单链表为例,以下是单链表操作的C语言实现。

4.1 定义单链表的节点结构

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

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

4.2 初始化单链表

void initList(Node** head) {
    *head = NULL;  // 初始化头指针为NULL
}

4.3 创建新节点

Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));  // 动态分配内存
    if (newNode == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    newNode->data = data;  // 初始化数据
    newNode->next = NULL;  // 初始化指针
    return newNode;
}

4.4 插入节点

在这里插入图片描述

4.4.1 在链表头部插入节点

void insertAtHead(Node** head, int data) {
    Node* newNode = createNode(data);  // 创建新节点
    newNode->next = *head;
    *head = newNode;
}

4.4.2 在链表尾部插入节点

在链表尾部插入节点时,需要遍历到链表的最后一个节点,并将新节点连接到尾部。

void insertAtTail(Node** head, int data) {
    Node* newNode = createNode(data);  // 创建新节点
    if (*head == NULL) {  // 如果链表为空
        *head = newNode;  // 新节点成为头节点
    } else {
        Node* current = *head;
        while (current->next != NULL) {  // 遍历到链表尾部
            current = current->next;
        }
        current->next = newNode;  // 将新节点连接到尾部
    }
}

4.4.3 在指定位置插入节点

在指定位置插入节点时,需要找到插入位置的前一个节点,并将新节点插入。

void insertAtPosition(Node** head,  int data, int position) {
	if(position < 0) {
		printf("无效的位置\n");
		return;
	}
    Node* newNode = createNode(data);  // 创建新节点
    if (position == 0) {  // 在头部插入
        newNode->next = *head;
        *head = newNode;
    } else {
        Node* current = *head;
        int count = 0;
        while (current != NULL && count < position - 1) {  // 找到插入位置的前一个节点
            current = current->next;
            count++;
        }
        if (current == NULL) {
            printf("位置超出范围!\n");
            free(newNode);
            return;
        }
        newNode->next = current->next;
        current->next = newNode;
    }
}

4.5 删除节点

在这里插入图片描述
要想删除C节点,只要将B节点的next指针指向D节点就可以了。

4.5.1 删除头节点

void deleteHead(Node** head) {
    if (*head == NULL) {
        printf("链表为空\n");
        return;
    }
    Node* temp = *head;
    *head = (*head)->next;
    free(temp);
}

4.5.2 删除尾节点

void deleteTail(Node** head) {
    if (*head == NULL) {
        printf("链表为空\n");
        return;
    }
    if ((*head)->next == NULL) {
        free(*head);
        *head = NULL;
        return;
    }
    Node* temp = *head;
    while (temp->next->next != NULL) {
        temp = temp->next;
    }
    free(temp->next);
    temp->next = NULL;
}

4.5.3 删除指定位置的节点

void deleteAtPosition(Node** head, int position) {
    if (*head == NULL) {
        printf("链表为空\n");
        return;
    }
    if (position < 0) {
        printf("无效的位置\n");
        return;
    }
    if (position == 0) {
        Node* temp = *head;
        *head = (*head)->next;
        free(temp);
        return;
    }
    Node* temp = *head;
    for (int i = 0; i < position - 1 && temp->next != NULL; i++) {
        temp = temp->next;
    }
    if (temp->next == NULL) {
        printf("位置超出范围\n");
        return;
    }
    Node* nodeToDelete = temp->next;
    temp->next = temp->next->next;
    free(nodeToDelete);
}

4.6 查找指定值的节点

查找指定值的节点时,需要遍历链表并返回节点的指针。

Node* searchNode(Node* head, int data) {
    Node* current = head;
    while (current != NULL) {  // 遍历链表查找目标节点
        if (current->data == data) {
            return current;  // 找到节点,返回指针
        }
        current = current->next;
    }
    return NULL;  // 未找到节点,返回NULL
}

查找节点,返回节点位置(位置从0开始),未找到返回-1。

int searchNode(Node* head, int key) {
    int position = 0;
    Node* temp = head;
    while (temp != NULL) {
        if (temp->data == key) {
            return position;
        }
        temp = temp->next;
        position++;
    }
    return -1;
}

4.7 遍历并打印链表

void printList(Node* head) {
    Node* current = head;
    while (current != NULL) {  // 遍历链表
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

五、性能分析

链表与数组的区别

1、存储方式:

  • 数组:

    • 连续存储:数组的元素在内存中是连续存储的,每个元素的地址可以通过起始地址和偏移量直接计算。

    • 固定大小:数组的大小在声明时通常需要固定,虽然动态数组可以动态扩展,但底层仍然需要重新分配内存并复制数据。

  • 链表:

    • 离散存储:链表的节点可以分散在内存的任意位置,每个节点通过指针连接。

    • 动态大小:链表的大小可以动态变化,插入或删除节点时不需要移动其他元素,只需修改指针。

2、访问效率:

  • 数组:随机访问:数组支持通过索引直接访问任意位置的元素,时间复杂度为O(1)。

  • 链表:顺序访问:链表不支持随机访问,访问某个节点需要从头节点开始逐个遍历,时间复杂度为O(n)。

3、插入和删除操作

  • 数组:插入和删除:在数组中插入或删除元素时,通常需要移动大量元素以保持连续性,时间复杂度为O(n)。例如,在数组头部插入元素时,需要将所有元素向后移动一位。

  • 链表:插入和删除:链表的插入和删除操作只需修改相邻节点的指针,时间复杂度为O(1)(如果已知插入或删除位置的指针)。例如,在链表头部插入或删除节点时,只需修改头指针。

4、内存使用

  • 数组:

    • 内存分配:数组需要预先分配固定大小的内存,即使某些位置未使用,也会占用内存。

    • 内存碎片:数组需要连续内存空间,可能导致内存碎片化问题,尤其是在频繁动态分配和释放时。

  • 链表:

    • 动态分配:链表的节点是动态分配的,每个节点只占用实际需要的内存,内存使用更灵活。

    • 额外开销:链表的每个节点需要额外存储指针(单链表需要一个指针,双向链表需要两个指针),增加了内存开销。

5、扩展性和灵活性

  • 数组:

    • 固定大小:数组的大小在声明时固定,扩展数组需要重新分配内存并复制数据。

    • 适用场景:适合存储大小固定且需要频繁随机访问的数据。

  • 链表:

    • 动态大小:链表的大小可以动态变化,适合存储大小不确定或频繁变化的数据。

    • 适用场景:适合需要频繁插入和删除操作的场景,如实现队列、栈、动态集合等。

特性数组链表
存储方式连续存储离散存储
访问效率随机访问O(1)顺序访问O(n)
插入/删除效率O(n),需要移动元素O(1),只需修改指针
内存使用预分配,可能浪费内存动态分配,更灵活,但有指针开销
扩展性固定大小,扩展需要复制数据动态大小,易于扩展
适用场景随机访问频繁,大小固定插入/删除频繁,大小不确定

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux的动态库与静态库

目录 动静态库的基本原理 认识动静态库 动静态库各自的特征 静态库 动态库 动静态库与内存 静态库的加载方式 动态库的加载方式 加载到物理内存的细节 静态库的打包与使用 打包 使用 动态库的打包与使用 打包 使用 我以前写的一篇文章中就用网吧与在宿舍自己组装电…

图漾PercipioIPTool软件使用

文章目录 前期准备1.PercipioIPTool软件1.1 更改网络适配器1.2 更改自动获取IP1.3设置静态IP 前期准备 1.一根超五类及其以上规格网线&#xff08;cat5e、cat6…&#xff09; 2.相机&#xff0c;配套网线和IO线 3.配套软件PercipioViewer或者PercipioIPTool软件(Windows环境使…

EasyRTC嵌入式WebRTC技术与AI大模型结合:从ICE框架优化到AI推理

实时通信技术在现代社会中扮演着越来越重要的角色&#xff0c;从视频会议到在线教育&#xff0c;再到远程医疗&#xff0c;其应用场景不断拓展。WebRTC作为一项开源项目&#xff0c;为浏览器和移动应用提供了便捷的实时通信能力。而EasyRTC作为基于WebRTC的嵌入式解决方案&…

《白帽子讲 Web 安全:点击劫持》

目录 摘要&#xff1a; 一、点击劫持概述 二、点击劫持的实现示例&#xff1a;诱导用户收藏指定淘宝商品 案例 构建恶意页面&#xff1a; 设置绝对定位和z - index&#xff1a; 控制透明度&#xff1a; 三、其他相关攻击技术 3.1图片覆盖攻击与 XSIO 3.2拖拽劫持与数据…

计算机网络---SYN Blood(洪泛攻击)

文章目录 三次握手过程SYN Flood攻击原理防御措施协议层优化网络层拦截系统配置调整 TCP协议是 TCP/IP 协议栈中一个重要的协议&#xff0c;平时我们使用的浏览器&#xff0c;APP等大多使用 TCP 协议通讯的&#xff0c;可见 TCP 协议在网络中扮演的角色是多么的重要。 TCP 协议…

GitCode 助力 python-office:开启 Python 自动化办公新生态

项目仓库&#xff1a;https://gitcode.com/CoderWanFeng1/python-office 源于需求洞察&#xff0c;打造 Python 办公神器 项目作者程序员晚枫在运营拥有 14w 粉丝的 B 站账号 “Python 自动化办公社区” 时&#xff0c;敏锐察觉到非程序员群体对 Python 学习的强烈需求。在数字…

Trae智能协作AI编程工具IDE:如何在MacBook Pro下载、安装和配置使用Trae?

Trae智能协作AI编程工具IDE&#xff1a;如何在MacBook Pro下载、安装和配置使用Trae&#xff1f; 一、为什么选择Trae智能协作IDE&#xff1f; 在AI编程新时代&#xff0c;Trae通过以下突破性功能重新定义开发体验&#xff1a; 双向智能增强&#xff1a;AI不仅提供代码补全&a…

Qt空项目代码解释

一、 背景 创建的是一个 QWidget 项目。 二、main.cpp 1、图片 2、代码解释 &#xff08;1&#xff09;QApplication Qt 图形化界面中一定有 QApplication &#xff08;2&#xff09;Widget w; 是 QWidget 的子类。 &#xff08;3&#xff09;w.show(); 继承父类的显示…

Codeforces Round 1007 (Div. 2)(ABCD1)

A. The Play Never Ends 翻译&#xff1a; 让我们来介绍一种双人游戏--乒乓球&#xff0c;在这种游戏中&#xff0c;胜负永远分明&#xff0c;不可能出现平局。 索赛、福福和浩海三人想用一生的时间打乒乓球。他们决定用以下方式永远打下去&#xff1a; 在每场比赛中&#xff…

多元数据直观表示(R语言)

一、实验目的&#xff1a; 通过上机试验&#xff0c;掌握R语言实施数据预处理及简单统计分析中的一些基本运算技巧与分析方法&#xff0c;进一步加深对R语言简单统计分析与图形展示的理解。 数据&#xff1a; 链接: https://pan.baidu.com/s/1kMdUWXuGCfZC06lklO5iXA 提取码: …

蓝桥备赛(六)- C/C++输入输出

一、OJ题目输入情况汇总 OJ&#xff08;online judge&#xff09; 接下来会有例题 &#xff0c; 根据一下题目 &#xff0c; 对这些情况进行分析 1.1 单组测试用例 单在 --> 程序运行一次 &#xff0c; 就处理一组 练习一&#xff1a;计算 (ab)/c 的值 B2009 计算 (ab)/c …

Immich自托管服务的本地化部署与随时随地安全便捷在线访问数据

文章目录 前言1.关于Immich2.安装Docker3.本地部署Immich4.Immich体验5.安装cpolar内网穿透6.创建远程链接公网地址7.使用固定公网地址远程访问 前言 小伙伴们&#xff0c;你们好呀&#xff01;今天要给大家揭秘一个超炫的技能——如何把自家电脑变成私人云相册&#xff0c;并…

B/B+树与mysql索引

数据结构操作网站&#xff1a;https://www.cs.usfca.edu/~galles/visualization/Algorithms.html B树 算法平均最差空间O(n)O(n)搜索O(log n)O(log n)插入O(log n)O(log n)删除O(log n)O(log n) B树 算法平均最差空间O(n)O(n)搜索O(log n)O(log n)插入O(log n)O(log n)删除O(…

【智能音频新风尚】智能音频眼镜+FPC,打造极致听觉享受!【新立电子】

智能音频眼镜&#xff0c;作为一款将时尚元素与前沿科技精妙融合的智能设备&#xff0c;这种将音频技术与眼镜形态完美结合的可穿戴设备&#xff0c;不仅解放了用户的双手&#xff0c;更为人们提供了一种全新的音频交互体验。新立电子FPC在智能音频眼镜中的应用&#xff0c;为音…

0x02 js、Vue、Ajax

文章目录 js核心概念js脚本引入html的方式基础语法事件监听 Vuevue简介v-forv-bindv-if&v-showv-model&v-on Ajax js 核心概念 JavaScript&#xff1a;是一门跨平台、面向对象的脚本语言&#xff0c;用来控制网页行为实现交互效果&#xff0c;由ECMAScript、BOM、DOM…

初探WebAssembly

WebAssembly: 网页应用的性能革命 ​互联网技术日新月异&#xff0c;Web应用已经从简单的网页跃升为功能丰富的平台。然而&#xff0c;JavaScript作为Web的主力语言&#xff0c;在处理计算密集型任务时仍然存在性能瓶颈。今天&#xff0c;我们来聊一聊可能改变Web格局的技术—…

Hadoop之01:HDFS分布式文件系统

HDFS分布式文件系统 1.目标 理解分布式思想学会使用HDFS的常用命令掌握如何使用java api操作HDFS能独立描述HDFS三大组件namenode、secondarynamenode、datanode的作用理解并独立描述HDFS读写流程HDFS如何解决大量小文件存储问题 2. HDFS 2.1 HDFS是什么 HDFS是Hadoop中的一…

ctfshow刷题笔记—栈溢出—pwn61~pwn64

目录 前言 一、pwn61&#xff08;输出了什么&#xff1f;&#xff09; 二、pwn62&#xff08;短了一点&#xff09; 三、pwn63(又短了一点) 四、pwn64(有时候开启某种保护并不代表这条路不通) 五、一些shellcode 前言 这几道都是与shellcode有关的题&#xff0c;实在是…

React Native 原理

React Native 是一个跨平台移动应用开发框架&#xff0c;它允许开发者使用 JavaScript 和 React 来开发 iOS 和 Android 原生应用。React Native 的核心原理是通过 桥接&#xff08;Bridge&#xff09; 技术&#xff0c;使用 JavaScript 来控制原生组件&#xff0c;并将应用逻辑…

SwiftUI之状态管理全解析

文章目录 引言一、`@State`1.1 基本概念1.2 初始化与默认值1.3 注意事项二、`@Binding`2.1 基本概念2.2 初始化与使用2.3 注意事项三、`@ObservedObject`3.1 基本概念3.2 初始化与使用3.3 注意事项四、`@EnvironmentObject`4.1 基本概念4.2 初始化与使用4.3 注意事项五、`@Stat…