【块状链表C++】文本编辑器(指针中 引用 的使用)

news2024/11/25 4:30:06

》》》算法竞赛

/**
 * @file            
 * @author          jUicE_g2R(qq:3406291309)————彬(bin-必应)
 *						一个某双流一大学通信与信息专业大二在读	
 * 
 * @brief           一直在竞赛算法学习的路上
 * 
 * @copyright       2023.9
 * @COPYRIGHT			 原创技术笔记:转载需获得博主本人同意,且需标明转载源
 * @language        C++
 * @Version         1.0还在学习中  
 */
  • UpData Log👆 2023.9.23 更新进行中
  • Statement0🥇 一起进步
  • Statement1💯 有些描述是个人理解,可能不够标准,但能达其意

技术提升站点

文章目录

  • 》》》算法竞赛
  • 技术提升站点
  • 20 块状链表
    • 20-1 背景
    • 20-2 实现
      • 20-2-0 准备工作:指针芝士的储备
        • [点击学习 指针、取地址、解引用、引用 相关知识](https://blog.csdn.net/qq_73928885/article/details/133191263?spm=1001.2014.3001.5501)
      • 20-2-1 第一步:建立结构
      • 20-2-2 第二步:获取信息
      • 20-2-3 第三步:定位
      • 20-2-4 第四步:对 结点(分块) 的操作
    • 20-3 文本编辑器(HDU 4008)

20 块状链表

我认为叫 结点数组 比较好

20-1 背景

块状 的形成使用的就是 分块 的数据结构

分块使用到的 数组结构 可以极大的加速检索的效率

链表 使用的就是 基础数据结构

使用 这种结构 进行插入和删除等修改操作 就较数组 效率更高

基于这两者各自的优势互补下,这种链表串起来的分块可以结合形成一种 检索很快,同时修改效率也很高的 高级数据结构 :块状链表

在这里插入图片描述

20-2 实现

根据上图可知,这种数据结构的主体是链表,链上的结点(也就是一个分块)指向一个数组。

20-2-0 准备工作:指针芝士的储备

首先,这里涉及一个指针的芝士:

指针 *取地址 &解引用 *引用 &

点击学习 指针、取地址、解引用、引用 相关知识

20-2-1 第一步:建立结构

  • 动态链表 套 动态数组:就是 l i s t list list v e c t o r vector vector
list<vector<char>> Link;                                        //Link是一条链,链上的 每一个结点 是 一个分块(分块 = 结点!!!)
typedef list<vector<char>>::iterator it;		                //重定义 迭代器(指针)类型的名字
int block_size=2500;                                            //分块的标准大小

20-2-2 第二步:获取信息

inline int Size(it node){return (node->size());}                //返回结点(分块)的大小
inline it  Next(it node){return ++node;}                        //返回下一个分块

20-2-3 第三步:定位

inline it Find(int& pos){                                       //查找(接收的是pos结点的值),由于又引用pos的地址,所有可以直接对pos的值操作!!!
    for(register it i=Link.begin();; i++){                      //i 是 用于遍历 链表上所有的结点 的指针
        if(i==Link.end() || pos<=Size(i))                       //i 处于所在的结点上 (遍历到末尾,不论如何都认定就在末尾结点 )
            return i;
        pos-=(Size(i));                                         //遍历的最终目的是要获得 目标 在所处分块的排位
    }
}

20-2-4 第四步:对 结点(分块) 的操作

  • 再分块操作
inline void Split(it node,int pos){                             //再分块操作:在 node 这个分块的 排位为pos处,将 块 一分为二(小块1,小块2)【pos位置划给小块2】
    if (pos==Size(node))                                        //如果 位置pos 是该分块的末尾,则无法进行再分块操作
        return;
    Link.insert(Next(node), vector<char>(node->begin()+pos, node->end()));  //把 小块2 分给 下一个分块
    node->erase(node->begin()+pos, node->end());                //清除 小块2(否则会出现重复的现象)
}
  • 合并小块
inline void Merge(it node){
    node->insert(node->end(), Next(node)->begin(), Next(node)->end());      //在 前一个分块 的末尾 接上 下一个分块
    Link.erase(Next(node));									//清除 被合并的分块(否则会出现重复的现象)
}
  • 维护链(块状链表 的 核心)
inline void Update(void){                                       //修改链上结点后,对链的状态进行更新
    for(register it i=Link.begin(); i!=Link.end(); i++){
        while(Size(i)>=(block_size<<1))                         //如果 分块 的大小 大于 两个block_size 则要再分块
            Split(i, Size(i) - block_size);
        while(Next(i) != Link.end() && Size(i)+Size(Next(i))<=block_size)   //(前提:下一个分块不是链的末尾)前后分块大小之和 比 block_size 小,则合并
            Merge(i);
        while(Next(i) != Link.end() && Next(i)->empty())        //清除最后一个空块 
            Link.erase(Next(i));                                    
    }
}

补充知识:.insert() 插入(方法)

//插入一个数:向 vector容器 的 pos位置(1) 插入一个数 val(0)
vector<int> arr{1,2};
arr.insert(1,0);			//.insert(pos, val)//{0,1,2}

//插入一串数:直接向 容器 的pos位置(2,即arr.end()) 插入另一个容器(或其中的部分)
vector<int> arr{1,2};	vector<int> ins{3,4,5};
arr.insert(arr.end(), ins.begin(). ins.end());//{1,2,3,4,5}
  • 插入
inline void Insert(int pos, const vector<char>& ch){
    register it node=Find(pos);                                 //找到 目标 在所处分块的排位
    if(!Link.empty())                                           //必须保证 分块 不为空,否则没法进行 再分块 操作
        Split(node, pos);
    Link.insert(Next(node), ch);                                //把 字符 插入两个小块 之间
    Update();
}
  • 删除
inline void Delete(int L, int R){								//两个Split处理的结果只是[L,R),还需一步!!!
    register it node_l, node_r;
    //注:传入 Find函数中 的pos值 与 传入 Split函数 的pos值 不同 
    node_l=Find(L);     Split(node_l, L);
    node_r=Find(R);     Split(node_r, R);						//注:将 R-1对应的元素 划给 当前的node_r分块 ,而将 R对应的元素划给了 Next(node_r)分块!!!这点很多blog没写清楚
    node_r++;                                                   //右端点
    while(Next(node_l)!=node_r)                                 //将 node_l到node_r 的 结点依次清除
        Link.erase(Next(node_l));
    Update();
}

20-3 文本编辑器(HDU 4008)

题目描述

很久很久以前, D O S 3. x DOS3.x DOS3.x 的程序员们开始对 E D L I N EDLIN EDLIN 感到厌倦。于是,人们开始纷纷改用自己写的文本编辑器⋯⋯

多年之后,出于偶然的机会,小明找到了当时的一个编辑软件。进行了一些简单的测试后,小明惊奇地发现:那个软件每秒能够进行上万次编辑操作(当然,你不能手工进行这样的测试) !于是,小明废寝忘食地想做一个同样的东西出来。你能帮助他吗?

为了明确目标,小明对“文本编辑器”做了一个抽象的定义:

文本:由 0 0 0 个或多个 ASCII 码在闭区间 [ 32 32 32, 126 126 126] 内的字符构成的序列。

光标:在一段文本中用于指示位置的标记,可以位于文本首部,文本尾部或文本的某两个字符之间。

文本编辑器:由一段文本和该文本中的一个光标组成的,支持如下操作的数据结构。如果这段文本为空,我们就说这个文本编辑器是空的。

操作名称输入文件中的格式功能
Move ( k ) \text{Move}(k) Move(k)Move k将光标移动到第 k k k 个字符之后,如果 k = 0 k=0 k=0,将光标移到文本开头
Insert ( n , s ) \text{Insert}(n,s) Insert(n,s)Insert n s在光标处插入长度为 n n n 的字符串 s s s,光标位置不变 n ≥ 1 n\geq1 n1
Delete ( n ) \text{Delete}(n) Delete(n)Delete n删除光标后的 n n n 个字符,光标位置不变, n ≥ 1 n \geq 1 n1
Get ( n ) \text{Get}(n) Get(n)Get n输出光标后的 n n n 个字符,光标位置不变, n ≥ 1 n \geq 1 n1
Prev ( ) \text{Prev}() Prev()Prev光标前移一个字符
Next ( ) \text{Next}() Next()Next光标后移一个字符

你的任务是:

  • 建立一个空的文本编辑器。

  • 从输入文件中读入一些操作并执行。

  • 对所有执行过的 GET 操作,将指定的内容写入输出文件。

输入格式

输入文件 editor.in 的第一行是指令条数 t t t,以下是需要执行的 t t t 个操作。其中:

为了使输入文件便于阅读, Insert 操作的字符串中可能会插入一些回车符, 请忽略掉它们(如果难以理解这句话,可以参照样例) 。

除了回车符之外,输入文件的所有字符的 ASCII 码都在闭区间 [ 32 32 32, 126 126 126] 内。且

行尾没有空格。

这里我们有如下假定:

  • MOVE 操作不超过 50000 50000 50000 个, INSERTDELETE 操作的总个数不超过 4000 4000 4000PREVNEXT 操作的总个数不超过 200000 200000 200000

  • 所有 INSERT 插入的字符数之和不超过 2 M 2M 2M 1 M = 1024 × 1024 1M=1024\times 1024 1M=1024×1024 字节) ,正确的输出文件长度不超过 3 M 3M 3M 字节。

  • DELETE 操作和 GET 操作执行时光标后必然有足够的字符。 MOVEPREVNEXT 操作必然不会试图把光标移动到非法位置。

  • 输入文件没有错误。

对 C++ 选手的提示:经测试,最大的测试数据使用 fstream 进行输入有可能会比使用 stdio 慢约 1 1 1 秒。

输出格式

输出文件 editor.out 的每行依次对应输入文件中每条 Get 指令的输出。

样例输入 #1

15
Insert 26
abcdefghijklmnop
qrstuv wxy
Move 15
Delete 11
Move 5
Insert 1
^
Next
Insert 1
_
Next
Next
Insert 4
.\/.
Get 4
Prev
Insert 1
^
Move 0
Get 22

样例输出 #1

abcde^_^f.\/.ghijklmno
#include<bits/stdc++.h>
using namespace std;
const int N=1e3;
list<vector<char>> Link;                                        //Link是一条链,链上的 每一个结点 是 一个分块(分块 = 结点!!!)
typedef list<vector<char>>::iterator it;		                //重定义 迭代器(指针)类型的名字
int block_size=2500;                                            //分块的标准大小
inline int Size(it node){return (node->size());}                //返回结点(分块)的大小
inline it  Next(it node){return ++node;}                        //返回下一个分块
/*-----------------定位-----------------------------*/
inline it Find(int& pos){                                       //查找(接收的是pos结点的值),由于又引用pos的地址,所有可以直接对pos的值操作!!!
    for(register it i=Link.begin();; i++){                      //i 是 用于遍历 链表上所有的结点 的指针
        if(i==Link.end() || pos<=Size(i))                       //i 处于所在的结点上 (遍历到末尾,不论如何都认定就在末尾结点 )
            return i;
        pos-=(Size(i));                                         //遍历的最终目的是要获得 目标 在所处分块的排位
    }
}

/*------------对 结点(分块) 的操作---------------------------*/
inline void Split(it node,int pos){                             //再分块操作:在 node 这个分块的 排位为pos处,将 块 一分为二(小块1,小块2)【pos位置划给小块1】
    if (pos==Size(node))                                        //如果 位置pos 是该分块的末尾,则无法进行再分块操作
        return;
    Link.insert(Next(node), vector<char>(node->begin()+pos, node->end()));  //把 小块2 分给 下一个分块
    node->erase(node->begin()+pos, node->end());                //清除 小块2(否则会出现重复的现象)
}
inline void Merge(it node){
    node->insert(node->end(), Next(node)->begin(), Next(node)->end());      //在 前一个分块 的末尾 接上 下一个分块
    Link.erase(Next(node));                                     //清除 被合并的分块(否则会出现重复的现象)
}

inline void Update(void){                                       //修改链上结点后,对链的状态进行更新
    for(register it i=Link.begin(); i!=Link.end(); i++){
        while(Size(i)>=(block_size<<1))                         //如果 分块 的大小 大于 两个block_size 则要再分块
            Split(i, Size(i) - block_size);
        while(Next(i) != Link.end() && Size(i)+Size(Next(i))<=block_size)   //(前提:下一个分块不是链的末尾)前后分块大小之和 比 block_size 小,则合并
            Merge(i);
        while(Next(i) != Link.end() && Next(i)->empty())        //清除最后一个空块 
            Link.erase(Next(i));                                    
    }
}

inline void Insert(int pos, const vector<char>& ch){
    register it node=Find(pos);                                 //找到 目标 在所处分块的排位
    if(!Link.empty())                                           //必须保证 分块 不为空,否则没法进行 再分块 操作
        Split(node, pos);
    Link.insert(Next(node), ch);                                //把 字符 插入两个小块 之间
    Update();
}
inline void Delete(int L, int R){								//两个Split处理的结果只是[L,R),还需一步!!!
    register it node_l, node_r;
    //注:传入 Find函数中 的pos值 与 传入 Split函数 的pos值 不同 
    node_l=Find(L);     Split(node_l, L);
    node_r=Find(R);     Split(node_r, R);						//注:将 R-1对应的元素 划给 当前的node_r分块 ,而将 R对应的元素划给了 Next(node_r)分块!!!这点很多blog没写清楚
    node_r++;                                                   //右端点
    while(Next(node_l)!=node_r)                                 //将 node_l到node_r 的 结点依次清除
        Link.erase(Next(node_l));
    Update();
}

inline void Output(int L, int R){
    register it node_l=Find(L), node_r=Find(R);
    for(register it pi=node_l;; pi++){
        int first;     pi==node_l ? first=L : first=0;
        int last;      pi==node_r ? last=R  : last=Size(pi);
        for(int i=first; i<last;i++)                            //打印
            putchar(pi->at(i));
        if(pi==node_r)  break;                                  //遍历到右边界退出循环
    }  
}

int main(void){
    vector<char> ch;    int len,pos,n;
    cin>>n;
    while(n--){
        char op[7];     cin>>op;
        if(op[0]=='M')  cin>>pos;       //Move移动操作:将光标移动到第 k 个字符之后
        if(op[0]=='I'){                 //Insert插入操作:在光标处插入长度为 len 的字符串 s,光标位置不变 n>=1
            ch.clear(); cin>>len;   ch.resize(len);
            for(int i=0;i<len;i++){
                ch[i]=getchar();
                while((int)ch[i]<32 || (int)ch[i]>126)//循环直到读到合法数据
                    ch[i]=getchar();
            }
            Insert(pos, ch);
        }
        if(op[0]=='D'){ cin>>len;        Delete(pos, pos+len);} //Delete删除操作:删除光标后的 len 个字符,光标位置不变,n>=1
        if(op[0]=='G'){ cin>>len;        Output(pos, pos+len);} //输出光标后的 len 个字符,光标位置不变,n>=1
        if(op[0]=='P')  pos--;                                  //光标前移一个字符
        if(op[0]=='N')  pos++;                                  //光标后移一个字符
    }
    return 0;
}

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

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

相关文章

稀疏奖励问题解决方案总览

方案简介 HER (Hindsight Experience Replay) - 2017年 思想 HER&#xff08;Hindsight Experience Replay&#xff09;是一种特别设计用于解决稀疏奖励问题的强化学习算法。它主要用于那些具有高度稀疏奖励和延迟奖励的任务&#xff0c;特别是在连续动作空间中&#xff0c;如机…

IDEA设置注释快捷键进行 注释对齐

给大家推荐一个嘎嘎好用的功能~ 相信大家在使用IDE写代码的时候&#xff0c;经常用到 Ctrl / 来注释代码吧&#xff0c;但是默认的是将注释在行首对齐&#xff0c;看着很让人不舒服。但是下面的操作会将注释会和当前代码对齐&#xff0c;还会自动保留一个空格&#xff0c;真的…

【用unity实现100个游戏之13】复刻类泰瑞利亚生存建造游戏——包括建造系统和库存系统

文章目录 前言素材人物瓦片其他 一、建造系统1. 定义物品类2. 绘制地图3. 实现瓦片选中效果4. 限制瓦片选择5. 放置物品功能6. 清除物品7. 生成和拾取物品功能 二、库存系统1. 简单绘制UI2. 零代码控制背包的开启关闭3. 实现物品的拖拽拖拽功能拖拽恢复问题 4. 拖拽放置物品5. …

【C语言精髓 之 指针】指针*、取地址、解引用*、引用

/*** file * author jUicE_g2R(qq:3406291309)————彬(bin-必应)* 一个某双流一大学通信与信息专业大二在读 * copyright 2023.9* COPYRIGHT 原创技术笔记&#xff1a;转载需获得博主本人同意&#xff0c;且需标明转载源* language …

人工智能驱动的自然语言处理:解锁文本数据的价值

文章目录 什么是自然语言处理&#xff1f;NLP的应用领域1. 情感分析2. 机器翻译3. 智能助手4. 医疗保健5. 舆情分析 使用Python进行NLP避免NLP中的陷阱结论 &#x1f389;欢迎来到AIGC人工智能专栏~人工智能驱动的自然语言处理&#xff1a;解锁文本数据的价值 ☆* o(≧▽≦)o *…

flutter web 优化和flutter_admin_template

文章目录 Flutter Admin TemplateLive demo: https://githubityu.github.io/live_flutter_adminWeb 优化 Setup登录注册英文 亮色主题 中文 暗黑主题管理员登录权限 根据权限动态添加路由 第三方依赖License最后参考学习 Flutter Admin Template Responsive web with light/da…

C++ 学习系列 -- std::vector (未完待续)

一 std::vector 是什么&#xff1f; vector 是c 中一种序列式容器&#xff0c;与前面说的 array 类似&#xff0c;其内存分配是连续的&#xff0c;但是与 array 不同的地方在于&#xff0c;vector 在运行时是可以动态扩容的&#xff0c;此外 vector 提供了许多方便的操作&…

世界前沿技术发展报告2023《世界信息技术发展报告》(四)电子信息技术

&#xff08;四&#xff09;电子信息技术 1. 概述2. 微电子技术2.1 精细制程芯片2.1.1 中国台积电发布2纳米制程工艺细节2.1.2 美国英特尔公司称2030年芯片晶体管密度将达到目前的10倍2.1.3 韩国三星电子率先实现3纳米制程芯片量产2.1.4 日本丰田、索尼等8家公司合资成立高端芯…

【李沐深度学习笔记】矩阵计算(1)

课程地址和说明 线性代数实现p4 本系列文章是我学习李沐老师深度学习系列课程的学习笔记&#xff0c;可能会对李沐老师上课没讲到的进行补充。 本节是第一篇 矩阵计算 标量导数 导数刻画的是函数在某点的瞬时变化率 这东西都是考研学过的&#xff0c;快速略过&#xff0c;如…

网站接入公网并配置域名访问【详细教程】

网站接入公网并配置域名访问【详细教程】 安装Nginx上传网页文件配置Nginx腾讯云配置域名映射接入公网备案流程 本教程将以腾讯云服务器和腾讯云域名为例&#xff0c;介绍如何快速将网站接入公网并配置域名访问。我们将使用xshell工具进行操作&#xff0c;并涵盖安装nginx、上传…

Unity之VR如何实现跟随视角的UI

前言 我们在制作VR项目的时候,大部分时候,是把UI固定到一个位置,比如桌子或者空中,这么做固然稳定,但是当我们有以下需求的时候,固定位置的UI可能会不适用: 1.场景较小,操作物体占用了很大体积,没有固定的可以清晰显示完整UI的位置。 2.需要频繁的前后左右,更换姿势…

Unity3D 使用LineRenderer自由画线

原理 一个LineRenderer是一次画线&#xff0c;需要使用对象池一帧记录一个鼠标位置 代码 这是线绘制器的代码&#xff0c;依赖于笔者写过的一个简易对象池 传送门&#xff1a;>>对象池 using EasyAVG; using System; using System.Collections.Generic; using UnityEn…

指针和数组笔试题的透析

指针---进阶篇&#xff08;三&#xff09; 一、前言二、一维数组例题透析&#xff1a;三、指针笔试题1.例一&#xff1a;2.例二&#xff1a;3.例三&#xff1a;4.例四&#xff1a;5.例五&#xff1a;6.例六&#xff1a; 一、前言 那么好了好了&#xff0c;宝子们&#xff0c;从…

王道408计组汇编语言部分学习总结

x86汇编语言指令基础 x86处理器中程序计数器PC 通常被称为IP 高级语言—>汇编语言—>机器语言 x86架构CPU&#xff0c;有哪些寄存器 EAX通用寄存器EBXECXEDXESI 变址寄存器 变址寄存器可用于线性表、字符串的处理EDIEBP堆栈基指针堆栈寄存器用于实现函数调用 ESP堆栈…

LESS的叶绿素荧光模拟实现与操作

LESS的叶绿素荧光模拟实现与操作 前情提要FLUSPECT模型荧光的三维面元冠层辐射传输过程日光诱导叶绿素荧光模拟 前情提要 本文默认您对LESS (LargE-Scale remote sensing data and image Simulation framework) 模型和叶绿素荧光(Sun-Induced chlorophyll Fluorescence, SIF)有…

2023华为杯研究生数学建模研赛E题出血脑卒中完整论文(含28个详细预处理数据及结果表格)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了全国研究生数学建模竞赛&#xff08;数模研赛&#xff09;E题完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。…

高级运维学习(十)系统安全

kali 实际上它就是一个预安装了很多安全工具的Debian Linux [rootmyhost ~]# kali reset kali reset OK. 该虚拟机系统用户名为:kali,密码为:kali 基础配置 $ ip a s # 查看网络IP地址&#xff0c;本例中查看到的是192.168.88.40 $ sudo systemctl start ssh # 启s…

java面试题-并发编程基础

1.线程的基础知识 1.1 线程和进程的区别&#xff1f; 难易程度&#xff1a;☆☆ 出现频率&#xff1a;☆☆☆ 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至 CPU&#xff0c;数据加载至内存。在指令运行过程中还需要…

驱动开发,基于gpio子系统编写LED灯的驱动,亮灭控制

1.gpio子系统介绍 一个芯片厂商生产出芯片后会给linux提供一个当前芯片中gpio外设的驱动&#xff0c;我们当前只需要调用对应的厂商驱动即可完成硬件的控制。而linux内核源码中的gpio厂商驱动有很多&#xff0c;这里linux内核对厂商驱动做了一些封装&#xff0c;提供了一系列的…

中秋国庆内卷之我爱学习C++

文章目录 前言Ⅰ. 内联函数0x00 内联函数和宏的比较0x01 内联函数的概念0x02 内联函数的特性 Ⅱ. auto&#xff08;C 11)0x00 auto的概念0x01 auto的用途 Ⅲ. 范围for循环(C11)0x00 基本用法0x01 范围for循环(C11)的使用条件 Ⅳ. 指针空值nullptr(C11)0x00 概念 前言 亲爱的夏…