【数据结构】线性表(四)双向链表的各种操作(插入、删除、查找、修改、遍历打印)

news2024/11/20 23:15:49

目录

线性表的定义及其基本操作(顺序表插入、删除、查找、修改)

四、线性表的链接存储结构

1. 单链表

2. 循环链表

3. 双向链表

a. 双向链表节点结构

b. 创建一个新的节点

c. 在链表末尾插入节点

d. 在指定位置插入节点

e. 删除指定位置的节点

f. 遍历并打印链表

g. 主函数

h. 代码整合

五、复杂性分析

1. 空间效率比较

2. 时间效率比较



 

线性表的定义及其基本操作(顺序表插入、删除、查找、修改)

        一个线性表是由零个或多个具有相同类型的结点组成的有序集合。

         按照线性表结点间的逻辑顺序依次将它们存储于一组地址连续的存储单元中的存储方式被称为线性表的顺序存储方式。按顺序存储方式存储的线性表具有顺序存储结构,一般称之为顺序表。换言之,在程序中采用定长的一维数组,按照顺序存储方式存储的线性表,被称为顺序表。

【数据结构】线性表(一)线性表的定义及其基本操作(顺序表插入、删除、查找、修改)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_63834988/article/details/132089038?spm=1001.2014.3001.5501

四、线性表的链接存储结构

1. 单链表

        顺序表的优点是存取速度快。但是,无论是插入一个结点,还是删除一个结点,都需要调整一批结点的地址。要克服该缺点,就必须给出一种不同于顺序存储的存储方式。用链接存储方式存储的线性表被称为链表,可以克服上述缺点。

        链表中的结点用存储单元(若干个连续字节)来存放,存储单元之间既可以是(存储空间上)连续的,也可以是不连续的,甚至可以零散地分布在存储空间中的任何位置。换言之,链表中结点的逻辑次序和物理次序之间并无必然联系。最重要的是,链表可以在不移动结点位置的前提下根据需要随时添加删除结点,动态调整。

【数据结构】线性表(二)单链表及其基本操作(创建、插入、删除、修改、遍历打印)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_63834988/article/details/133914875?spm=1001.2014.3001.5501

2. 循环链表

           从单链表的一个结点出发,只能访问到链接在它后面的结点,而无法访问位于它前面的结点,这对一些实际应用很不方便。解决的办法是把链接结构“循环化”,即把表尾结点的next域存放指向哨位结点的指针,而不是存放空指针NULL,这样的单链表被称为循环链表。循环链表使用户可以从链表的任何位置开始,访问链表中的任意结点。

【数据结构】线性表(三)循环链表的各种操作(创建、插入、查找、删除、修改、遍历打印、释放内存空间)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_63834988/article/details/133914085?spm=1001.2014.3001.5501

3. 双向链表

        在循环链表中,从一个结点出发,必须遍历整个链表,方可找到其前驱结点,时间复杂度为O(n) . 双向链表将很好地解决该问题。所谓双向链表,系指链表中任一结点P都是由data域、左指针域left(pre)和右指针域right(next)构成的,左指针域和右指针域分别存放P的左右两边相邻结点的地址信息。

        双向链表的优点是可以在常量时间内删除或插入一个节点,因为只需要修改节点的前后指针,而不需要像单向链表那样遍历到指定位置。而在单向链表中,删除或插入一个节点需要先找到前一个节点,然后修改指针。然而,双向链表相对于单向链表需要更多的内存空间来存储额外的指针。另外,由于多了一个指针,插入和删除节点时需要更多的操作。

a. 双向链表节点结构

typedef struct Node {
    int data;
    struct Node* prev;
    struct Node* next;
} Node;

        包含一个整数data以及两个指针prevnext,分别指向前一个节点和后一个节点。

b. 创建一个新的节点

Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}

        创建一个新的节点,它接受一个整数作为参数,并分配内存来存储节点。然后,节点的data被设置为传入的整数,prevnext指针被初始化为NULL,最后返回新创建的节点指针。

c. 在链表末尾插入节点

void append(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;
        newNode->prev = current;
    }
}
  • 创建一个新的节点,并将传入的整数作为节点的数据。
  • 检查链表是否为空
    • 如果为空,将链表头指针指向新节点;
    • 否则,遍历链表找到最后一个节点,将最后一个节点的next指针指向新节点,新节点的prev指针指向最后一个节点。

d. 在指定位置插入节点

void insert(Node** head, int data, int position) {
    Node* newNode = createNode(data);
    if (position == 0) {
        newNode->next = *head;
        if (*head != NULL) {
            (*head)->prev = newNode;
        }
        *head = newNode;
    } else {
        Node* current = *head;
        int i;
        for (i = 0; i < position - 1 && current != NULL; i++) {
            current = current->next;
        }
        if (current == NULL) {
            printf("Invalid position\n");
            return;
        }
        newNode->next = current->next;
        newNode->prev = current;
        if (current->next != NULL) {
            current->next->prev = newNode;
        }
        current->next = newNode;
    }
}
  • 创建一个新的节点,并将传入的整数作为节点的数据。
  • 如果插入位置为0,表示在链表头插入新节点
    • 将新节点的next指针指向链表的头节点
    • 如果链表不为空,将链表的头节点的prev指针指向新节点,最后将链表的头指针指向新节点。
  • 如果插入位置不为0
    • 首先遍历链表找到插入位置的前一个节点
    • 如果找到了位置或者遍历到链表末尾都没有找到指定位置,则输出"Invalid position"并返回。
    • 否则,将新节点的next指针指向当前节点的next指针所指向的节点,新节点的prev指针指向当前节点
    • 如果当前节点的next指针不为空,将当前节点的next指针所指向的节点的prev指针指向新节点,最后将当前节点的next指针指向新节点。

e. 删除指定位置的节点

void delete(Node** head, int position) {
    if (*head == NULL) {
        printf("List is empty\n");
        return;
    }
    Node* temp = *head;
    if (position == 0) {
        *head = (*head)->next;
        if (*head != NULL) {
            (*head)->prev = NULL;
        }
        free(temp);
    } else {
        int i;
        for (i = 0; i < position && temp != NULL; i++) {
            temp = temp->next;
        }
        if (temp == NULL) {
            printf("Invalid position\n");
            return;
        }
        temp->prev->next = temp->next;
        if (temp->next != NULL) {
            temp->next->prev = temp->prev;
        }
        free(temp);
    }
}
  • 如果链表为空,输出"List is empty"并返回。
  • 如果要删除的节点是链表的头节点
    • 将链表的头指针指向头节点的下一个节点,如果链表不为空,将新的头节点的prev指针指向NULL,然后释放被删除的节点的内存。
  • 如果要删除的节点不是头节点
    • 首先遍历链表找到要删除的节点
    • 如果找到了指定位置的节点或者遍历到链表末尾都没有找到,则输出"Invalid position"并返回。
    • 否则,将要删除节点的前一个节点的next指针指向要删除节点的下一个节点,如果要删除节点的下一个节点不为空,将要删除节点的下一个节点的prev指针指向要删除节点的前一个节点。
    • 最后释放要删除节点的内存。

f. 遍历并打印链表

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

        从链表的头节点开始,通过不断访问next指针,打印每个节点的数据,并移动到下一个节点,直到遍历完整个链表。

g. 主函数

int main() {
    Node* head = NULL;
    printList(head);

    // 在链表末尾插入节点
    append(&head, 1);
    append(&head, 2);
    append(&head, 3);
    printList(head);

    // 在指定位置插入节点
    insert(&head, 4, 1);
    printList(head);

    // 删除指定位置的节点
    delete(&head, 2);
    printList(head);

    return 0;
}

        

h. 代码整合

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

// 双向链表节点结构
typedef struct Node {
    int data;
    struct Node* prev;
    struct Node* next;
} Node;

// 创建一个新的节点
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}

// 在链表末尾插入节点
void append(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;
        newNode->prev = current;
    }
}

// 在指定位置插入节点
void insert(Node** head, int data, int position) {
    Node* newNode = createNode(data);
    if (position == 0) {
        newNode->next = *head;
        if (*head != NULL) {
            (*head)->prev = newNode;
        }
        *head = newNode;
    } else {
        Node* current = *head;
        int i;
        for (i = 0; i < position - 1 && current != NULL; i++) {
            current = current->next;
        }
        if (current == NULL) {
            printf("Invalid position\n");
            return;
        }
        newNode->next = current->next;
        newNode->prev = current;
        if (current->next != NULL) {
            current->next->prev = newNode;
        }
        current->next = newNode;
    }
}

// 删除指定位置的节点
void delete(Node** head, int position) {
    if (*head == NULL) {
        printf("List is empty\n");
        return;
    }
    Node* temp = *head;
    if (position == 0) {
        *head = (*head)->next;
        if (*head != NULL) {
            (*head)->prev = NULL;
        }
        free(temp);
    } else {
        int i;
        for (i = 0; i < position && temp != NULL; i++) {
            temp = temp->next;
        }
        if (temp == NULL) {
            printf("Invalid position\n");
            return;
        }
        temp->prev->next = temp->next;
        if (temp->next != NULL) {
            temp->next->prev = temp->prev;
        }
        free(temp);
    }
}

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

// 主函数
int main() {
    Node* head = NULL;
    printList(head);

    // 在链表末尾插入节点
    append(&head, 1);
    append(&head, 2);
    append(&head, 3);
    printList(head);

    // 在指定位置插入节点
    insert(&head, 4, 1);
    printList(head);

    // 删除指定位置的节点
    delete(&head, 2);
    printList(head);

    return 0;
}

五、复杂性分析

        到目前为止,本系列已详细介绍了线性表的两种存储方式——顺序存储和链接存储,下面从空间和时间复杂性两方面对二者进行比较分析。

1. 空间效率比较

        顺序表所占用的空间来自于申请的数组空间,数组大小是事先确定的,很明显当表中的元素较少时,顺序表中的很多空间处于闲置状态,造成了空间的浪费;

        链表所占用的空间是根据需要动态申请的,不存在浪费空间的问题,但是链表需要在每个结点上附加一个指针域,从而增加一定的空间开销。

2. 时间效率比较

        线性表的基本操作是存取、插入和删除。对于顺序表,随机存取是非常容易的,但是每插入或删除一个元素,都需要移动若干元素。

        对于链表,无法实现随机存取,必须要从表头开始遍历链表,直到发现要存取的元素,但是链表的插入和删除操作却非常简便,只需要修改几个指针。

  • 当经常需要对线性表进行插入、删除操作时,链表的时间效率较高;
    • 双向链表在某些场景下更加灵活和高效,特别是需要频繁的插入和删除操作时。然而,在内存有限的情况下,单向链表可能更为合适。
  • 当经常需要对线性表进行存取且存取操作比插入、删除操作更为频繁时,顺序表的时间效率较高

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

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

相关文章

LeetCode算法刷题(python) Day42|09动态规划|62.不同路径、63. 不同路径 II

目录 LeetCode 62. 不同路径LeetCode 63. 不同路径II LeetCode 62. 不同路径 力扣题目链接 class Solution:def uniquePaths(self, m: int, n: int) -> int:dp [[1] * n for _ in range(m)]for j in range(n):for i in range(m):if i 0 and j > 0:dp[i][j] dp[i][j-1…

Python实现番茄小说内容下载

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 环境使用: Python 3.8 Pycharm 模块使用: requests --> pip install requests re parsel 代码展示&#xff1a; 导入模块 # 导入数据请求模块 import…

[自定义 Vue 组件] 小尾巴 Logo 组件 TailLogo

文字归档于&#xff1a;https://www.yuque.com/u27599042/coding_star/apt6y731ybmxgu5g 组件效果 组件依赖 自定义字符串工具函数 stringIsNull https://www.yuque.com/u27599042/coding_star/slncupw7un3ce7cb import {stringIsNull} from "/utils/string_utils.js&q…

python进制和编码

一、进制 计算机中底层所有的数据都是以 010101的形式存在&#xff08;图片、文本、视频等&#xff09;。 二、进制转换 v1 bin(25) # 十进制转换为二进制 print(v1) # "0b11001"v2 oct(23) # 十进制转换为八进制 print(v2) # "0o27"v3 hex(28) # 十进…

ThinkPHP 8.x MVC 数据库用户增加功能demo实现

ThinkPHP MVC 数据库用户增加功能实现 在thinkphp 多应用的项目中&#xff0c; 采用MVC的架构实现 增加用户的功能。 在多应用下的controller下创建UserInfo.php ,创建一个和用户信息相关操作的控制器。 定义一个插入信息的方法,这里叫insertUserInfo 我们要执行一个用户插入…

Apache Doris (四十五): Doris数据更新与删除 - Sequence 列

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. 基本原理

EtherNet/IP转Modbus TCP协议网关的接口

远创智控的YC-EIPM-TCP网关产品&#xff0c;它有什么作用呢&#xff1f;一起来了解一下吧&#xff01; 远创智控YC-EIPM-TCP网关产品可以通过各种数据接口和工业领域的仪表、PLC、计量设备等产品连接&#xff0c;实时采集这些设备中的运行数据、状态数据等信息&#xff0c;并把…

数字秒表verilog电子秒表跑表,代码/视频

名称&#xff1a;数字秒表verilog电子秒表跑表 软件&#xff1a;Quartus 语言&#xff1a;Verilog 代码功能&#xff1a; 设计电子秒表&#xff0c;秒表时间精确到0.01秒&#xff0c;可通过按键控制秒表启动、暂停、复位。 代码需要在Mini_Star开发板验证。 开发板资料&…

【springboot异常处理】

这里写自定义目录标题 1&#xff0c;Java Servlet规范异常处理触发异常 2&#xff0c;tomcat 异常处理实现tomcat请求处理流程异常发生时核心处理类 3&#xff0c;springmvc 异常定制以及扩展请求处理流程使用ControllerAdvice & ExceptionHandler 配置全局异常处理器未配置…

全面的Docker快速入门教程

前言&#xff1a; 都2023年了&#xff0c;你还在为了安装一个开发或者部署环境、软件而花费半天的时间吗&#xff1f;你还在解决开发环境能够正常访问&#xff0c;而发布正式环境无法正常访问的问题吗&#xff1f;你还在为持续集成和持续交付&#xff08;CI / CD&#xff09;工…

【C++11】右值引用、移动构造、移动赋值、完美转发 的原理介绍

文章目录 一、概念1.1 左值1.2 左值引用1.3 什么是右值&#xff1f;1.4 什么是右值引用&#xff1f;对于参数左值还是右值的不同&#xff0c;是被重载支持的左值引用的使用场景 和 缺陷 二、移动语义2.1 移动拷贝构造2.2 移动赋值 三、右值引用 与 STL3.1 移动拷贝构造 和 赋值…

Web攻防03_MySQL注入_数据请求

文章目录 PHP-MYSQL-数据请求类型1、数字型(无符号干扰)2、字符型&#xff08;有符号干扰&#xff09;3、搜索型&#xff08;有多符号干扰&#xff09;4、框架型&#xff08;有各种符号干扰&#xff09; PHP-MYSQL-数据请求方法数据请求方法GET&#xff1a;POST&#xff1a;Coo…

运筹系列86:MIP问题的建模tips

1. Either-or constraint 添加辅助变量y。 比如 Either 3 x 1 2 x 2 ≤ 18 3x_12x_2 \le 18 3x1​2x2​≤18 or x 1 4 x 2 ≤ 16 x_14x_2 \le 16 x1​4x2​≤16 可以用 3 x 1 2 x 2 ≤ 18 M y 3x_12x_2 \le 18My 3x1​2x2​≤18My x 1 4 x 2 ≤ 16 M ( 1 − y ) x_1…

【vue】el-carousel实现视频自动播放与自动切换到下一个视频功能:

文章目录 一、原因:二、实现代码:三、遇到的问题&#xff1a;【1】问题&#xff1a;el-carousel页面的视频不更新【2】问题&#xff1a;多按几次左按钮&#xff0c;其中跳过没有播放的视频没有销毁&#xff0c;造成再次自动播放时会跳页 一、原因: 由于后端无法实现将多条视频拼…

2023年:哪些Trello的替代品值得关注?

你还记得你的第一块Trello板吗&#xff1f;它可能帮助你记录了大学申请、培训目标&#xff0c;甚至是圣诞礼物这些待办事项。对于我们中的许多人来说&#xff0c;Trello 是我们尝试的第一个工作管理工具。无论是跟踪高中作业&#xff0c;组织家庭任务&#xff0c;还是可视化工作…

瑞芯微RKNN开发·yolov5

官方预训练模型转换 下载yolov5-v6.0分支源码解压到本地&#xff0c;并配置基础运行环境。下载官方预训练模型 yolov5n.ptyolov5s.ptyolov5m.pt… 进入yolov5-6.0目录下&#xff0c;新建文件夹weights&#xff0c;并将步骤2中下载的权重文件放进去。修改models/yolo.py文件 …

【C++】继承 ⑥ ( 继承中的构造函数和析构函数 | 类型兼容性原则 | 父类指针 指向 子类对象 | 使用 子类对象 为 父类对象 进行初始化 )

文章目录 一、public 公有继承 - 示例分析1、类型兼容性原则2、类型兼容性原则应用场景 二、类型兼容性原则 - 示例分析1、父类指针 指向 子类对象2、使用 子类对象 为 父类对象 进行初始化3、完整代码示例 一、public 公有继承 - 示例分析 1、类型兼容性原则 类型兼容性原则 :…

测试开发之性能篇 —— 性能测试设计

很多朋友接触性能测试是从工具开始的&#xff0c;比如流行的JMeter、Loadrunner等。熟悉一个测试工具&#xff0c;有助于对性能测试的过程、方法和机制有个直观的理解。 我们知道&#xff0c;无论是什么类型的测试&#xff0c;其目标不外乎两个&#xff0c;一是为了证明系统满…

直播带货前途渺茫了

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 种种迹象表明电商行业和直播带货将受到冲击。直播带货前途渺茫了&#xff0c;相信很快就有政策出来了&#xff0c;针对电商这块的&#xff0c;支持实体、支持取消直播带货。 (1)目前&#xff0c;…

C++对多继承的理解

学到C时我们知道了继承但是一般都是使用单继承为主&#xff0c;单继承就是一个子类只能继承一个父类而多继承是指一个子类可以同时继承多个父类。 菱形继承 菱形继承是多继承中的一个特殊情况。当一个子类同时继承两个具有共同父类的类时&#xff0c;就会出现菱形继承问题。但…