c++链表(list)

news/2024/9/20 9:21:25/ 标签: c++, 开发语言

前言

链表作为一个常见的数据结构,在高频插入删除的场景下有独特的优势,在内存的使用上也极少有浪费可以按需申请。今天我们就来简单的学习一下这种数据结构,链表也有很多不同的实现,我们这里和标准库保持一致,实现带头双线循环链表

具体list类的描述可以参考list - C++ Reference (cplusplus.com)

在不同的编译器下string类的实现各有差异,这里我们使用的是Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.8.5

链表的结构

在正式学习链表之前,我们先来简单的认识一下带头双向循环链表的结构。

带头指的是有哨兵位的头节点,哨兵位指的是一个没有任何数据的节点,作用是标识首位节点(如果是单向指标识头节点),因为有哨兵位的存在,很多操作得以简化

双向指的是每个节点都有标识上一个节点和下一个节点的地址

循环指的是尾节点不在指向nullptr而是指向哨兵位

首先,我们需要实现一个节点的类,用来描述节点的属性

template<class T>class listNode{public://listNode() {}listNode(const T& a=T()){data = a;}//private:listNode* next = nullptr;listNode* previous = nullptr;T data = T();};

 这里我们还是使用类模板,以适应存储各种类型的数据

全部属性用public描述,因为这个类在后面要经常被使用到,用struct可以实现一样的效果,用友元类也可以使用private描述,这里就简化这些操作了

此时如果我们需要再链表中使用节点,需要先声明节点的属性

typedef listNode<T> Node;

现在我们就可以来搭建一个链表类的框架出来

#pragma once
#include <iostream>
#include <algorithm>
//#include <assert.h>using namespace std;namespace zzzyh
{template<class T>class list{
public:
private:Node* head;size_t sz = 0;};
}

其中head表示哨兵位的地址,size用来标识有效元素的个数

 构造函数

allocator是空间配置器,这里我们还是先忽略这个点

第一个是默认构造函数

list(){empty_init();}

这里empty_init是初始化哨兵位,因为经常用到我们可以封装为一个函数

void empty_init(){head = new Node;head->next = head;head->previous = head;sz = 0;}

第二个是使用n个默认值进行初始化

第三个是使用迭代器区间进行初始化

第四个是拷贝构造

list(const list&s){empty_init();for (auto &x : s){push_back(x);}}

顺带,我们将赋值重载也实现

		list<T>& operator=(const list&s){list<T> rem(s);swap(rem);return *this;}

这里的push_back功能是尾插一个元素,我们先使用在后面再实现

这里swap可以实现两个链表的交换,还是先使用在后面我们再来实现

这里还是会有深浅拷贝的问题,只有不能两个节点指向同一块空间,不能多次释放一块空间

在最后我们介绍一个c++11引入的构造方式

这里initializer_list是一个类

这个类我们不在这里不展开只介绍使用

#define _CRT_SECURE_NO_WARNINGS 1
#include "list.h"
#include <list>using namespace std;int main()
{list<int> list1 = { 1,2,3,4,5 };list<int> list2({ 6,7,8,9,0 });for (int i : list1){cout << i << " ";}cout << endl;for (int i : list2){cout << i << " ";}cout << endl;return 0;
}

下面我们来实现一下这种功能

list(initializer_list<T> li){empty_init();for (auto& a : li){push_back(a);}}

这个构造方式不仅仅再list中适用,所有实现这个接口的容器均可以使用,具体哪些容器实现类还需要再查询文档

析构函数

析构函数可以先将使用有效的节点释放(clear)再释放哨兵位头节点

~list(){clear();delete head;}

我们再来实现一下clear

		void clear(){iterator beg = begin();while (beg != end()){beg = erase(beg);}}

这里的erase可以删除指定迭代器指向的位置,这里还是先使用在后面会实现其功能

迭代器

链表的迭代器相比于前面我们介绍的顺序表要复杂很多,因为链表的内存空间是不连续的,这就意味着++这种操作符需要重载为新的含义

我们这里可以把迭代器实行为一个类来描述,因为只有类才能实现运算符重载

template<class T>class list_iterator{public:typedef listNode<T> Node;Node* cut;list_iterator(Node* c):cut(c){}T& operator*() const{return cut->data;}T* operator->() const{return &(cut->data);}list_iterator operator++(){cut = cut->next;return *this;}list_iterator operator--() {cut = cut->previous;return *this;}list_iterator operator++(int){cut = cut->next;return cut->previous;}list_iterator operator--(int) {cut = cut->previous;return cut->previous;}list_iterator operator+(size_t i) {Node* ret = cut;while (i != 0){ret = ret->next;i--;}return ret;}list_iterator operator-(size_t i) {Node* ret = cut;while (i != 0){ret = ret->previous;i--;}return ret;}bool operator!=(list_iterator pos) const{return cut != pos.cut;}bool operator==(list_iterator pos) const{return cut == pos.cut;}};

同样我们可以实现const迭代器

template<class T>class list_const_iterator{public:typedef listNode<T> Node;Node* cut;list_const_iterator(Node* c):cut(c){}const T& operator*() const{return cut->data;}const T* operator->() const{return &(cut->data);}list_const_iterator operator++(){cut = cut->next;return *this;}list_const_iterator operator--() {cut = cut->previous;return *this;}list_const_iterator operator++(int){cut = cut->next;return cut->previous;}list_const_iterator operator--(int) {cut = cut->previous;return cut->previous;}list_const_iterator operator+(size_t i) {Node* ret = cut;while (i != 0){ret = ret->next;i--;}return list_iterator(ret);}list_const_iterator operator-(size_t i) {Node* ret = cut;while (i != 0){ret = ret->previous;i--;}return list_iterator(ret);}bool operator!=(list_const_iterator pos) const{return cut != pos.cut;}bool operator==(list_const_iterator pos) const{return cut == pos.cut;}};

此时我们发现,这两个类高度相似,我们也可以用到类模板的思想实现

template<class T,class Ref,class Ptr>class list_iterator{public:typedef listNode<T> Node;Node* cut;list_iterator(Node* c):cut(c){}Ref operator*(){return cut->data;}Ptr operator->(){return &(cut->data)	;}list_iterator operator++(){cut = cut->next;return *this;}list_iterator operator--() {cut = cut->previous;return *this;}list_iterator operator++(int){cut = cut->next;return cut->previous;}list_iterator operator--(int) {cut = cut->previous;return cut->previous;}list_iterator operator+(size_t i) {Node* ret = cut;while (i != 0){ret = ret->next;i--;}return list_iterator(ret);}list_iterator operator-(size_t i) {Node* ret = cut;while (i != 0){ret = ret->previous;i--;}return list_iterator(ret);}bool operator!=(list_iterator pos) const{return cut != pos.cut;}bool operator==(list_iterator pos) const {return cut == pos.cut;}};

如果此时我们需要再链表里使用迭代器,同样需要先声明迭代器的属性

typedef list_iterator<T,T&,T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;

同理如果上面那两种未使用类模板实现的迭代器在使用时也需要先声明再使用

typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;

此时我们可以实现begin和end了

iterator begin(){return iterator(head->next);}iterator end(){return iterator(head);}const_iterator begin() const{return const_iterator(head->next);}const_iterator end()const{return const_iterator(head);}

其他的获得迭代器的函数我们就不再介绍了,和前面介绍过的容器功能相同

 容量相关

size(),可以获得有效节点的个数,不包含哨兵位头节点

size_t size(){return sz;}

empty(),判断此链表是否为空(即没有有效元素,还是不包含哨兵位头节点)

bool empty(){return sz == 0;}

访问(查)相关

front(),可以返回首节点的值的引用

back(),可以返回最后一个节点的值的引用

增删改相关

push_back(),可以尾插一个节点

void push_back(const T& a){Node* newNode = new Node(a);if (head->next == head) {newNode->next = newNode->previous = head;head->next = head->previous = newNode;}else{newNode->next =  head;newNode->previous = head->previous;head->previous->next = newNode;head->previous = newNode;}sz++;//insert(end(), a);}

insert(),可以在指定迭代器之前的位置插入一个节点,迭代器不失效

void insert(iterator pos, const T& data){if (pos == end()){push_back(data);return;}else {Node* newNode = new Node(data);if (pos == begin()) {newNode->previous = head;newNode->next = head->next;head->next->previous = newNode;head->next = newNode;}else {newNode->previous = pos.cut->previous;newNode->next = pos.cut;pos.cut->previous->next = newNode;pos.cut->previous = newNode;}}sz++;}

push_front(),可以头插一个节点

void push_front(const T& a){insert(begin(), a);}

erase(),可以删除指定迭代器的节点,并且返回当前迭代器的下一个位置

iterator erase(iterator pos){if (pos.cut == head) {return pos;}Node* left = pos.cut->previous;Node* right = pos.cut->next;left->next = right;right->previous = left;delete pos.cut;sz--;return right;}

pop_back(),可以尾删尾节点

void pop_back(){erase(--end());}

pop_front(),可以头删头节点

void pop_front(){erase(begin());}

swap(),可以交换两个链表的内容,这里单独实现是为了提高交换效率

void swap(list& rem){swap(sz,rem.sz);swap(head, rem.head);}

迭代器失效

这里还是就有迭代器失效的问题,但只要在删除时会失效传入的迭代器,其他迭代器不受影响。标准库和我们这的结局方案相同,在调用删除时会将新的迭代器(即删除元素的下一个位置)返回,及时更新使用即可

结语

以上便是今天的全部内容。如果有帮助到你,请给我一个免费的赞。

因为这对我很重要。

编程世界的小比特,希望与大家一起无限进步。

感谢阅读!


http://www.ppmy.cn/news/1514330.html

相关文章

docker手动部署django项目Dockerfile编排-后端发布

1、首先创建一个桥接网络 docker network create auto 2、部署redis,提供celery的消息队列服务 docker run --name redis --restartalways -d --network auto -v redis:/data redis:alpine3、部署数据库 注意数据库账号密码 docker run --name mariadb --restartalways -d…

远程消息传递的艺术:NSDistantObject在Objective-C中的妙用

标题&#xff1a;远程消息传递的艺术&#xff1a;NSDistantObject在Objective-C中的妙用 引言 在Objective-C的丰富生态中&#xff0c;NSDistantObject扮演着至关重要的角色&#xff0c;特别是在处理分布式系统中的远程消息传递。它允许对象之间跨越不同地址空间进行通信&…

基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真&#xff0c;仿真输出电机转速跟踪曲线&#xff0c;PID控制器输出曲线以及Te输出曲线。 2.系统仿真结果 &…

通过访存地址获取主存数据的过程

目录 1.根据访存地址在Cache中查找数据 2.如果在Cache中命中 3.如果没有命中 4.数据送CPU 5.做几道题&#xff1a; 主要厘清思路&#xff0c;中间细节需自行补充! 1.根据访存地址在Cache中查找数据 ① 访存地址的结构会根据Cache和主存之间的映射方式不同而改变。映射方式…

【C++ 面试 - 面向对象】每日 3 题(七)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

centos7.9系统安装cloudpods并使用ceph存储(二)

1.ceph安装 1.1 环境准备 配置hosts&#xff1a; $ vim /etc/hosts 10.121.x.x node01设置ssh无密码登录&#xff1a; # ssh-keygen -t rsa # ssh-copy-id -i /root/.ssh/id_rsa node01关闭selinux、firewalld # setenforce 0 # sed -i "s#SELINUXenforcing#SELINUXd…

5步掌握Python Django+Vue二手房项目,实现房价预测与知识图谱系统

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

基于SpringBoot的在线笔记网站的设计与实现

目录 项目技术和环境 页面展示 登录注册 管理员页面 用户页面 在线网址 源代码 本系统由十大核心模块构成&#xff0c;包括用户登录与注册模块、个人中心模块、笔记分类与笔记管理模块、笔记详情展示模块、分享协作与收藏管理模块、回收站与用户管理模块&#xff0c;以及…

深入探讨量子计算领域在发展过程中所遇到的难题及其解决方案。

一、引言 量子计算作为未来科技的重要方向&#xff0c;其潜力巨大&#xff0c;但同时也面临着诸多技术挑战。这些挑战不仅制约了量子计算的进一步发展&#xff0c;也考验着科学家和工程师们的智慧和毅力。本文将探讨量子计算面临的主要技术挑战&#xff0c;并分析其可能的解决…

自己做的一个用于生成DICOM文件的服务器

框架: .ner core web api .net8.0 Program.cs代码如下 using Microsoft.AspNetCore.HttpsPolicy; using System.Diagnostics;namespace PacsServer {/* public class Program{public static void Main(string[] args){//配置服务var builder WebApplication.CreateBuilder(a…

表格滚动分页查询

1.给表格添加id&#xff0c;height"100%"是固定表头 <el-table id"attr-table-box" height"100%" :row-style"{ height: 66px }" style"width: 100%; height: 100%; " > 2.使用计算属性获取总页数 computed: { // 表…

go语言中map、slice、chan底层数据结构是怎样的

map&#xff1a;rutime包下的map中的hmap // A header for a Go map. type hmap struct {// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.// Make sure this stays in sync with the compilers definition.count int /…

信息学奥赛初赛天天练-72-NOIP2016普及组-基础题3-无向图、简单无向图、自环、平行边、顶点的度、握手定理、递归

NOIP 2016 普及组 基础题3 5 以下不是存储设备的是( ) A 光盘 B 磁盘 C 固态硬盘 D 鼠标 6 如果开始时计算机处于小写输入状态&#xff0c;现在有一只小老鼠反复按照 CapsLock、 字母键 A、字母键 S、字母键 D、字母键 F 的顺序循环按键&#xff0c;即 CapsLock、A、S、D、F、…

记录 Ant Design Table 组件使用的问题

问题一、设置 y 之后&#xff0c;侧边栏出现白边&#xff1a;https://github.com/react-component/table/issues/998 问题二、设置scroll y之后&#xff0c;x为max-content失效 github地址&#xff1a;https://github.com/ant-design/ant-design/issues/29283 临时解决方案&am…

Facebook的区块链战略:如何在社交媒体中实现去中心化

随着区块链技术的发展&#xff0c;Facebook&#xff08;现Meta&#xff09;正积极探索如何将这一技术整合进其社交平台中&#xff0c;以提升用户体验和数据安全。区块链技术以去中心化、透明性和不可篡改性为特点&#xff0c;为社交媒体带来了新的可能性。本文将探讨Facebook在…

Qml 实现仿前端的 Notification (悬浮出现页面上的通知消息)

【写在前面】 经常接触前端的朋友应该经常见到下面的控件&#xff1a; 在前端中一般称它为 Notification 或 Message&#xff0c;但本质是一种东西&#xff0c;即&#xff1a;悬浮弹出式的消息提醒框。 这种组件一般具有以下特点&#xff1a; 1、全局/局部显示&#xff1a;它不…

特征工程和训练评估流程

程序的思路&#xff1a; 从配置文件中获取到待组合的特征&#xff0c;进行特征工程返回相应的特征和数据&#xff0c;这个过程中&#xff0c;保留中间特征工程结果&#xff0c;为了避免重复执行。 分批遍历所有的特征组合&#xff0c;进行训练和评估&#xff0c;然后保存评估结…

分形比特币(Fractal Bitcoin)

文章目录 分形比特币&#xff08;Fractal Bitcoin&#xff09;背景近期发展动态 什么是RGB什么是分形比特币&#xff08;Fractal Bitcoin&#xff09;分形&#xff08;Fractal&#xff09; 数学概念 分形比特币&#xff08;Fractal Bitcoin&#xff09; 背景 自比特币上的 Or…

Answer use of function tool by OpenAI assistant in Python

题意&#xff1a;“在 Python 中使用 OpenAI 助手的函数工具的用途” 问题背景&#xff1a; I am trying to answer to OpenAI assistants function tool. “我正在尝试回答 OpenAI 助手的函数工具。” See my code below. The custom function is called "funnyfunc&qu…

冗余电源装VMware系统电源警告

双电源机器装VMware vSphere虚拟化系统&#xff0c;在系统硬件运行状况里面新增加的电源模块出现警告。 刷新一下主板双电源即可&#xff0c;解决方案如下&#xff1a; 下载浪潮一键日志收集工具 http://www.4008600011.com/archives/9195 打开程序后&#xff0c;点击下一步进…