链表(linked_list)的理解以及实现

news2024/9/22 1:11:41

链表的概念:

链表是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接。引用记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点。 

可以看出:链表物理结构不是连续的

链表与数组:

链表(带头双向循环链表)与数组(顺序表)在功能,结构上的不同:

数组链表
存储方式连续内存空间分散内存空间
容量扩展长度不可变可灵活扩展
内存效率元素占用内存少、但可能浪费空间元素占用内存多
访问元素O(1)O(n)
添加元素O(n)O(1)
删除元素O(n)O(1)

链表的分类:

类型分类:

  • 单链表(Singly Linked List):

   - 每个节点包含数据和指向下一个节点的指针。

   - 单向链表,只能从表头向表尾遍历。

  • 双链表(Doubly Linked List):

   - 每个节点包含数据和两个指针,分别指向前一个节点和后一个节点。

   - 双向链表,可以从任一节点开始向前或向后遍历。

  • 循环链表(Circular Linked List):

   - 单链表或双链表的变体,最后一个节点的指针指向表头或前一个节点。

   - 循环单链表:最后一个节点指向表头。

   - 循环双链表:最后一个节点的后指针指向表头,表头的前指针指向最后一个节点。

  • 双向循环链表(Doubly Circular Linked List):

   - 双链表的循环版本,第一个节点的前指针和最后一个节点的后指针都指向表头。

  • 栈式链表(Stack Implementation Using Linked List):

   - 使用链表实现的栈,遵循后进先出(LIFO)原则。

   - 通常使用单链表实现,新元素总是添加到链表的头部。

  • 队列式链表(Queue Implementation Using Linked List):

   - 使用链表实现的队列,遵循先进先出(FIFO)原则。

   - 通常使用双链表实现,新元素添加到链表的尾部,移除元素从头部。

  • 有序链表(Ordered Linked List):

   - 链表中的节点按照特定的顺序(如数值大小)进行排序。

  • 无序链表(Unordered Linked List):

   - 链表中的节点没有特定的顺序。

  • 动态链表(Dynamic Linked List):

   - 可以在运行时动态地添加或删除节点。

  • 静态链表(Static Linked List):

    - 节点的数量在创建时就固定了,不能动态地添加或删除节点。

  • 哈希链表(Hash Linked List):

    - 哈希表的实现方式之一,通过链表解决哈希冲突。

  • 索引链表(Indexed Linked List):

    - 链表中包含指向特定节点的索引,可以快速访问链表中的元素。

结构分类:

其中:较为常见的整体结构还是单向,环形,双向连表:

 

链表的结构⾮常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:

 

虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构: 单链表 双向带头循环链表
  • ⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多
  • 带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带来很多优势,实现反⽽简单了

实现图解:

在实现链表的过程中,主要还是还是数据得插入删除

插入:

删除: 

单链表的实现:

在此主要实现带头单向不循环链表

在这里,带头带的其实就是哨兵位,哨兵位是一种特殊的节点,通常被添加到链表的开始或末尾,以简化某些操作,比如避免对空链表的特殊情况处理。哨兵节点通常不存储有效的数据,或者存储一个默认值

#include <iostream>
#include <stdexcept> // 用于抛出异常

// 定义节点模板结构体
template <typename T>
struct Node {
    T data; // 存储数据
    Node<T>* next; // 指向下一个节点的指针

    // 构造函数
    Node(T val = T())
        : data(val)
        , next(nullptr)
    {}
};

// 定义带头结点的单向不循环链表类
template <typename T>
class LinkedListWithHead {
private:
    Node<T>* head; // 头结点,哨兵节点

public:
    // 构造函数
    LinkedListWithHead()
    {
        head = new Node<T>(); // 创建哨兵节点
    }

    // 析构函数
    ~LinkedListWithHead()
    {
        Node<T>* current = head->next;
        while (current != nullptr)
        {
            Node<T>* next = current->next;
            delete current;
            current = next;
        }
        delete head; // 释放哨兵节点
    }

    // 在链表末尾添加新节点
    void append(const T& value)
    {
        Node<T>* newNode = new Node<T>(value);
        Node<T>* last = head;
        while (last->next != nullptr)
        {
            last = last->next;
        }
        last->next = newNode;
    }

    // 在链表头部添加新节点
    void prepend(const T& value)
    {
        Node<T>* newNode = new Node<T>(value);
        newNode->next = head->next;
        head->next = newNode;
    }

    // 根据值删除节点
    void remove(const T& value)
    {
        Node<T>* current = head;
        while (current->next != nullptr && current->next->data != value)
        {
            current = current->next;
        }
        if (current->next != nullptr)
        {
            Node<T>* toDelete = current->next;
            current->next = current->next->next;
            delete toDelete;
        }
        else
        {
            throw std::runtime_error("Value not found in the list.");
        }
    }

    // 查找节点是否存在
    bool contains(const T& value) const
    {
        Node<T>* current = head->next;
        while (current != nullptr)
        {
            if (current->data == value)
            {
                return true;
            }
            current = current->next;
        }
        return false;
    }

    // 打印链表中的所有元素
    void print() const
    {
        Node<T>* current = head->next;
        while (current != nullptr)
        {
            std::cout << current->data << " -> ";
            current = current->next;
        }
        std::cout << "null" << std::endl;
    }
};

  • remove 方法:删除链表中第一个匹配值的节点。如果找不到该值,则抛出一个 std::runtime_error 异常。
  • contains 方法:检查链表中是否存在具有给定值的节点,并返回一个布尔值。

请注意,remove 方法中,我们首先遍历链表直到找到要删除的节点。如果找到,我们将其从链表中移除并释放内存。如果链表中没有该值,我们抛出一个异常。contains 方法则是遍历链表检查是否存在具有给定值的节点。

双向链表的实现:

在此主要实现带头双向循环链表:

实现一个带头结点的双向循环链表涉及到创建一个链表,其中每个节点除了包含数据外,还有两个指针分别指向前一个节点和后一个节点。头结点是一个哨兵节点,它的 nextprev 指针都指向链表的第一个实际数据节点,而链表的最后一个数据节点的 next 指向头结点,prev 指向最后一个数据节点自身。

下面是C++模板实现带头结点的双向循环链表的示例代码:

#pragma once
#include <iostream>
#include <stdexcept> // 用于抛出异常

// 定义节点模板结构体
template <typename T>
struct Node {
    T data;
    Node<T>* prev;
    Node<T>* next;

    // 构造函数,为data提供默认参数值
    Node(T val = T()) : data(val), prev(nullptr), next(nullptr) {}
};

// 定义带头结点的双向循环链表类
template <typename T>
class CircularDoublyLinkedList {
private:
    Node<T>* head; // 头结点,哨兵节点

public:
    // 构造函数
    CircularDoublyLinkedList() {
        head = new Node<T>(); // 创建哨兵节点
        head->next = head->prev = head; // 哨兵节点形成循环
    }

    // 析构函数
    ~CircularDoublyLinkedList() {
        Node<T>* current = head->next;
        while (current != head) {
            Node<T>* temp = current;
            current = current->next;
            delete temp;
        }
        delete head; // 释放哨兵节点
    }

    // 在链表末尾添加新节点
    void append(const T& value) {
        Node<T>* newNode = new Node<T>(value);
        newNode->prev = head->prev;
        newNode->next = head;
        head->prev->next = newNode;
        head->prev = newNode;
    }

    // 删除链表中的指定节点
    void remove(Node<T>* node) {
        if (node == head) {
            head = head->next; // 哨兵节点指向下一个节点
        }
        node->prev->next = node->next;
        node->next->prev = node->prev;
        delete node;
    }

    // 打印链表中的所有元素
    void print() const {
        Node<T>* current = head->next;
        if (head->next == head) {
            std::cout << "List is empty." << std::endl;
            return;
        }
        do {
            std::cout << current->data << " ";
            current = current->next;
        } while (current != head->next);
        std::cout << std::endl;
    }

    // 根据值查找节点,如果找到则返回节点指针,否则返回nullptr
    Node<T>* find(const T& value) const {
        Node<T>* current = head->next;
        if (head->next == head) {
            return nullptr; // 链表为空
        }
        do {
            if (current->data == value) return current;
            current = current->next;
        } while (current != head->next);
        return nullptr;
    }

    // 根据值删除节点
    void removeByValue(const T& value) {
        Node<T>* nodeToDelete = find(value);
        if (nodeToDelete != nullptr) {
            remove(nodeToDelete);
        }
    }
};

在这个实现中,定义了 Node 结构体和 CircularDoublyLinkedList 类。CircularDoublyLinkedList 类包括添加节点到头部和末尾、删除节点、打印链表、查找节点以及根据值删除节点的方法。链表使用哨兵节点简化了头部和末尾的操作。

请注意,remove 方法接受一个 Node 指针作为参数,它将该节点从链表中移除并释放内存。removeByValue 方法使用 find 方法来查找具有给定值的节点,如果找到了就调用 remove 方法来删除它。

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

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

相关文章

在Ubuntu 13.10上安装Hadoop的方法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 先决条件 本教程的唯一先决条件是安装了 Ubuntu 13.10 x64 的 VPS。 您需要通过以下两种方式之一在命令行中执行命令&#xff1a; 使用…

AI芯片:高性能卷积计算中的数据复用

随着深度学习的飞速发展&#xff0c;对处理器的性能要求也变得越来越高&#xff0c;随之涌现出了很多针对神经网络加速设计的AI芯片。卷积计算是神经网络中最重要的一类计算&#xff0c;本文分析了高性能卷积计算中的数据复用&#xff0c;这是AI芯片设计中需要优化的重点之一&a…

XSS游戏前五关

分享一个XSS游戏的链接 XSS Game 第一关&#xff1a; 这边有一个innerHTML属性&#xff0c;我们查看官方文档 我们找到了它存在的漏洞&#xff0c;直接利用 https://sandbox.pwnfunction.com/warmups/ma-spaghet.html?somebody<img src1 onerror"alert(1337)&quo…

工具推荐篇:《Chat-PPT一键AI生成专属风格演示文稿》

引言 在当今快节奏的工作环境中&#xff0c;制作高质量的演示文稿既是一项挑战也是一门艺术。传统的PPT制作往往需要花费大量的时间和精力&#xff0c;尤其是在寻找合适的模板、设计布局和选择色彩搭配等方面。 今天给大家推荐一款AI一键制作高质量PPT的工具。 AI如何改变PP…

CANoe软件中Trace窗口的筛选栏标题不显示(空白)的解决方法

文章目录 问题描述原因分析解决方案扩展知识总结问题描述 不知道什么情况,CANoe软件中Trace窗口的筛选栏标题突然不显示了,一片空白。现象如下: 虽然不影响CANoe软件的使用,但是观感上非常难受,对于强迫症患者非常不友好。 原因分析 按照常规思路,尝试了: 1、重启CAN…

8月强化|30天带刷张宇18讲核心重点!

不偏不难就不是张宇了&#xff01; 张宇老师本来就以“偏难怪”著称&#xff0c;无独有偶&#xff0c;24考研真题也是“偏难怪”&#xff01; 所以&#xff0c;24考研结束之后&#xff0c;大家欧鼓吹张宇「封神」 先不说张宇老师是不是真的符合考研的趋势&#xff0c;但是跟…

解决 git clone 失败问题

使用 git clone 指令&#xff0c;从 G i t H u b GitHub GitHub克隆项目时失败&#xff0c;提示信息为&#xff1a; fatal: unable to access https://github.com/***/***: Failed to connect to github.com port 443 after 21083 ms:Couldnt connect to server解决方法 出现…

Chromium编译指南2024 - Android篇:从Linux版切换到Android版(六)

1.引言 在前面的章节中&#xff0c;我们介绍了如何获取 Chromium for Android 的源代码。然而&#xff0c;您可能已经在本地拥有了用于 Linux 版的 Chromium 源代码&#xff0c;并希望切换到 Android 版进行编译和开发。为了避免重新拉取大量的代码&#xff0c;您可以通过配置…

趋动科技成为GSMA 5G IN创新会员,专注于软件定义AI算力技术

趋动科技 趋动科技作为软件定义AI算力技术的领导厂商&#xff0c;专注于为全球用户提供国际领先的数据中心级AI算力虚拟化和资源池化软件及解决方案。趋动科技的 OrionX AI 算力资源池化软件能够帮助用户提高资源利用率和降低TCO&#xff0c;提高算法工程师的工作效率。凭借标…

谷歌、火狐、Edge浏览器使用allWebPlugin中间件加载ActiveX控件

安装allWebPlugin中间件 1、请从下面地址下载allWebPlugin中间件产品 链接&#xff1a;百度网盘 请输入提取码百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百…

数据结构与算法分析winform算术表达式求值计算

数据结构与算法分析算术表达式求值计算 数据结构与算法分析 实验三 算术表达式求值计算 要求&#xff1a;创建Form窗体&#xff0c;输入算术表达式&#xff0c;计算出表达式结果。 基本思路&#xff1a; &#xff08;1&#xff09;将表达式串拆分成操作数和操作符混合的字符…

【数据结构】使用C语言建立邻接矩阵表示有向图

有向图的邻接矩阵构建 有向图的定义 先回顾下有向图的定义&#xff1a; 有向图是一副具有方向性的图&#xff0c;是有一组顶点和一组有方向的边组成的&#xff0c;每条方向的边都连接着一对有序的顶点。 有向图的邻接矩阵的特点 有向图邻接矩阵中第i行非零元素的个数为第i个顶…

背部筋膜炎怎么根治

背部筋膜炎是一种常见的疾病&#xff0c;背部筋膜炎的症状主要包括&#xff1a; 1、疼痛&#xff1a;这是背部筋膜炎最明显的症状&#xff0c;疼痛可表现为酸痛、胀痛或刺痛&#xff0c;轻重不一。疼痛通常在劳累后加重&#xff0c;休息后减轻。 2、僵硬&#xff1a;由于无菌…

Java Web —— 第五天(请求响应1)

postman Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件 作用:常用于进行接口测试 简单参数 原始方式 在原始的web程序中&#xff0c;获取请求参数&#xff0c;需要通过HttpServletRequest 对象手动获 http://localhost:8080/simpleParam?nameTom&a…

部署fiji(ImageJ)

本文介绍fiji的部署。 1.从 https://github.com/fiji/fiji ​​​​​​下载 zip包&#xff0c;下载后解压&#xff0c;本人解压的路径是H:\fiji-master&#xff0c;后面都用这个路径。 1. 打开Intellij idea&#xff0c;点击 import project 点击OK后&#xff0c;一路next &…

Azkaban学习笔记

1 Azkaban概述 为什么需要工作流调度系统&#xff1f; 1&#xff09;一个完整的数据分析系统通常都是由大量任务单元组成&#xff1a;Shell脚本程序&#xff0c;Java程序&#xff0c;MapReduce程序、Hive脚本等 2&#xff09;各任务单元之间存在时间先后及前后依赖关系 3&#…

MATLAB基于深度学习的车辆检测系统

如今机器视觉领域深度学习算法已经大行其道&#xff0c;也让人工智能的实现不再那么遥不可及&#xff0c;但是在目标检测领域&#xff0c;让计算机超越人类还需让更多的人参与进来继续努力。如今众多的高校&#xff0c;甚至中小学已经将人工智能纳入了学习科目&#xff0c;这确…

排序(基数,堆,归并)

基数排序 定义0-9十个桶&#xff0c;先排序个数&#xff0c;在排序十位&#xff0c;依次向下&#xff08;桶就是二维数组&#xff09; 按照个位先排一次 个位已经有序了&#xff0c;桶内遵循先进先出 没有十位放到0里 取出 百位 这样排序就完成了。放进取出几次&#xff0c;取…

多线程执行的3种场景示例代码

1.环境 语言&#xff1a;java jdk版本&#xff1a;1.8 2.三种线程池场景使用 2.1 固定线程数执行&#xff0c;每个线程只执行1次&#xff0c;最后全部执行完毕后再进入最终方法处理收尾 public static void testEveryThreadFixedExecuteOne() {int threadNum 4;ThreadPoolExe…

C++ | 探索C++多态:虚函数与抽象类的奥秘

目录 二、多态&#xff1a;统一接口下的行为多样性 1、多态的概念 2、多态的实现和构成条件 1、虚函数&#xff08;Virtual Function&#xff09; 2、虚函数重写 3、抽象类与接口继承 4、重载、覆盖(重写)、隐藏(重定义)的对比 重载&#xff08;Overloading&#xff09; 覆盖&a…